def test_bad_tell(self): # Tests errors if wrong sizes are passed to tell r = pints.toy.RosenbrockError() e = pints.SequentialEvaluator(r) x0 = [0, 0] # Give wrong initial number opt = method(x0) xs = opt.ask() fxs = e.evaluate(xs) self.assertRaisesRegex(ValueError, r'of length \(1 \+ n_parameters\)', opt.tell, fxs[:-1]) # Give wrong intermediate answer opt = method(x0) opt.tell(e.evaluate(opt.ask())) x = opt.ask()[0] fx = e.evaluate([x]) self.assertRaisesRegex(ValueError, 'only a single evaluation', opt.tell, [fx, fx]) # Give wrong answer in shrink step with self.assertRaisesRegex(ValueError, 'length n_parameters'): opt = method(x0) for i in range(500): opt.tell(e.evaluate(opt.ask())) if opt._shrink: xs = opt.ask() fxs = e.evaluate(xs) opt.tell(fxs[:-1]) break
def _initialise_evaluator(self, f): """ Initialises parallel runners, if desired. """ # Create evaluator object if self._parallel: # Use at most n_workers workers n_workers = self._n_workers evaluator = pints.ParallelEvaluator(f, n_workers=n_workers) else: evaluator = pints.SequentialEvaluator(f) return evaluator
def test_sequential(self): # Create test data xs = np.random.normal(0, 10, 100) ys = [f(x) for x in xs] # Test sequential evaluator e = pints.SequentialEvaluator(f) self.assertTrue(np.all(ys == e.evaluate(xs))) # Function must be callable self.assertRaises(ValueError, pints.SequentialEvaluator, 3) # Argument must be sequence self.assertRaises(ValueError, e.evaluate, 1) # Test args e = pints.SequentialEvaluator(f_args, [10, 20]) self.assertEqual(e.evaluate([1]), [31]) # Args must be a sequence self.assertRaises(ValueError, pints.SequentialEvaluator, f_args, 1)
def test_parallel(self): # Create test data xs = np.random.normal(0, 10, 100) ys = [f(x) for x in xs] # Test parallel evaluator e = pints.ParallelEvaluator(f) self.assertTrue(np.all(ys == e.evaluate(xs))) # Function must be callable self.assertRaises(ValueError, pints.ParallelEvaluator, 3) # Argument must be sequence self.assertRaises(ValueError, e.evaluate, 1) # Test args e = pints.SequentialEvaluator(f_args, [10, 20]) self.assertEqual(e.evaluate([1]), [31]) # Args must be a sequence self.assertRaises(ValueError, pints.ParallelEvaluator, f_args, args=1) # n-workers must be >0 self.assertRaises(ValueError, pints.ParallelEvaluator, f, 0) # max tasks must be >0 self.assertRaises(ValueError, pints.ParallelEvaluator, f, 1, 0) # Exceptions in called method should trigger halt, cause new exception e = pints.ParallelEvaluator(ioerror_on_five, n_workers=2) self.assertRaisesRegex(Exception, 'Exception in subprocess', e.evaluate, [1, 2, 5]) e.evaluate([1, 2]) # System exit e = pints.ParallelEvaluator(system_exit_on_four, n_workers=2) self.assertRaisesRegex(Exception, 'Exception in subprocess', e.evaluate, [1, 2, 4]) e.evaluate([1, 2])
def run(self): """ Runs the optimisation, returns a tuple ``(xbest, fbest)``. """ # Check stopping criteria has_stopping_criterion = False has_stopping_criterion |= (self._max_iterations is not None) has_stopping_criterion |= (self._max_unchanged_iterations is not None) has_stopping_criterion |= (self._threshold is not None) if not has_stopping_criterion: raise ValueError('At least one stopping criterion must be set.') # Iterations and function evaluations iteration = 0 evaluations = 0 # Unchanged iterations count (used for stopping or just for # information) unchanged_iterations = 0 # Create evaluator object if self._parallel: # Get number of workers n_workers = self._n_workers # For population based optimisers, don't use more workers than # particles! if isinstance(self._optimiser, PopulationBasedOptimiser): n_workers = min(n_workers, self._optimiser.population_size()) evaluator = pints.ParallelEvaluator(self._function, n_workers=n_workers) else: evaluator = pints.SequentialEvaluator(self._function) # Keep track of best position and score fbest = float('inf') # Internally we always minimise! Keep a 2nd value to show the user fbest_user = fbest if self._minimising else -fbest # Set up progress reporting next_message = 0 # Start logging logging = self._log_to_screen or self._log_filename if logging: if self._log_to_screen: # Show direction if self._minimising: print('Minimising error measure') else: print('Maximising LogPDF') # Show method print('Using ' + str(self._optimiser.name())) # Show parallelisation if self._parallel: print('Running in parallel with ' + str(n_workers) + ' worker processes.') else: print('Running in sequential mode.') # Show population size pop_size = 1 if isinstance(self._optimiser, PopulationBasedOptimiser): pop_size = self._optimiser.population_size() if self._log_to_screen: print('Population size: ' + str(pop_size)) # Set up logger logger = pints.Logger() if not self._log_to_screen: logger.set_stream(None) if self._log_filename: logger.set_filename(self._log_filename, csv=self._log_csv) # Add fields to log max_iter_guess = max(self._max_iterations or 0, 10000) max_eval_guess = max_iter_guess * pop_size logger.add_counter('Iter.', max_value=max_iter_guess) logger.add_counter('Eval.', max_value=max_eval_guess) logger.add_float('Best') self._optimiser._log_init(logger) logger.add_time('Time m:s') # Start searching timer = pints.Timer() running = True try: while running: # Get points xs = self._optimiser.ask() # Calculate scores fs = evaluator.evaluate(xs) # Perform iteration self._optimiser.tell(fs) # Check if new best found fnew = self._optimiser.fbest() if fnew < fbest: # Check if this counts as a significant change if np.abs(fnew - fbest) < self._min_significant_change: unchanged_iterations += 1 else: unchanged_iterations = 0 # Update best fbest = fnew # Update user value of fbest fbest_user = fbest if self._minimising else -fbest else: unchanged_iterations += 1 # Update evaluation count evaluations += len(fs) # Show progress if logging and iteration >= next_message: # Log state logger.log(iteration, evaluations, fbest_user) self._optimiser._log_write(logger) logger.log(timer.time()) # Choose next logging point if iteration < self._message_warm_up: next_message = iteration + 1 else: next_message = self._message_interval * ( 1 + iteration // self._message_interval) # Update iteration count iteration += 1 # # Check stopping criteria # # Maximum number of iterations if (self._max_iterations is not None and iteration >= self._max_iterations): running = False halt_message = ('Halting: Maximum number of iterations (' + str(iteration) + ') reached.') # Maximum number of iterations without significant change halt = (self._max_unchanged_iterations is not None and unchanged_iterations >= self._max_unchanged_iterations) if halt: running = False halt_message = ('Halting: No significant change for ' + str(unchanged_iterations) + ' iterations.') # Threshold value if self._threshold is not None and fbest < self._threshold: running = False halt_message = ('Halting: Objective function crossed' ' threshold: ' + str(self._threshold) + '.') # Error in optimiser error = self._optimiser.stop() if error: # pragma: no cover running = False halt_message = ('Halting: ' + str(error)) except (Exception, SystemExit, KeyboardInterrupt): # pragma: no cover # Unexpected end! # Show last result and exit print('\n' + '-' * 40) print('Unexpected termination.') print('Current best score: ' + str(fbest)) print('Current best position:') for p in self._optimiser.xbest(): print(pints.strfloat(p)) print('-' * 40) raise time_taken = timer.time() # Log final values and show halt message if logging: logger.log(iteration, evaluations, fbest_user) self._optimiser._log_write(logger) logger.log(time_taken) if self._log_to_screen: print(halt_message) # Save post-run statistics self._evaluations = evaluations self._iterations = iteration self._time = time_taken # Return best position and score return self._optimiser.xbest(), fbest_user
def run(self): """ Runs the MCMC sampler(s) and returns the result. By default, this method returns an array of shape ``(n_chains, n_iterations, n_parameters)``. If storing chains to memory has been disabled with :meth:`set_chain_storage`, then ``None`` is returned instead. """ # Check stopping criteria has_stopping_criterion = False has_stopping_criterion |= (self._max_iterations is not None) if not has_stopping_criterion: raise ValueError('At least one stopping criterion must be set.') # Iteration and evaluation counting iteration = 0 n_evaluations = 0 # Choose method to evaluate f = self._log_pdf if self._needs_sensitivities: f = f.evaluateS1 # Create evaluator object if self._parallel: # Use at most n_workers workers n_workers = min(self._n_workers, self._n_chains) evaluator = pints.ParallelEvaluator(f, n_workers=n_workers) else: evaluator = pints.SequentialEvaluator(f) # Initial phase if self._needs_initial_phase: for sampler in self._samplers: sampler.set_initial_phase(True) # Storing evaluations to memory or disk prior = None store_evaluations = \ self._evaluations_in_memory or self._evaluation_files if store_evaluations: # Bayesian inference on a log-posterior? Then separate out the # prior so we can calculate the loglikelihood if isinstance(self._log_pdf, pints.LogPosterior): prior = self._log_pdf.log_prior() # Store last accepted logpdf, per chain current_logpdf = np.zeros(self._n_chains) current_prior = np.zeros(self._n_chains) # Write chains to disk chain_loggers = [] if self._chain_files: for filename in self._chain_files: cl = pints.Logger() cl.set_stream(None) cl.set_filename(filename, True) for k in range(self._n_parameters): cl.add_float('p' + str(k)) chain_loggers.append(cl) # Write evaluations to disk eval_loggers = [] if self._evaluation_files: for filename in self._evaluation_files: cl = pints.Logger() cl.set_stream(None) cl.set_filename(filename, True) if prior: # Logposterior in first column, to be consistent with the # non-bayesian case cl.add_float('logposterior') cl.add_float('loglikelihood') cl.add_float('logprior') else: cl.add_float('logpdf') eval_loggers.append(cl) # Set up progress reporting next_message = 0 # Start logging logging = self._log_to_screen or self._log_filename if logging: if self._log_to_screen: print('Using ' + str(self._samplers[0].name())) print('Generating ' + str(self._n_chains) + ' chains.') if self._parallel: print('Running in parallel with ' + str(n_workers) + ' worker processess.') else: print('Running in sequential mode.') if self._chain_files: print('Writing chains to ' + self._chain_files[0] + ' etc.') if self._evaluation_files: print('Writing evaluations to ' + self._evaluation_files[0] + ' etc.') # Set up logger logger = pints.Logger() if not self._log_to_screen: logger.set_stream(None) if self._log_filename: logger.set_filename(self._log_filename, csv=self._log_csv) # Add fields to log max_iter_guess = max(self._max_iterations or 0, 10000) max_eval_guess = max_iter_guess * self._n_chains logger.add_counter('Iter.', max_value=max_iter_guess) logger.add_counter('Eval.', max_value=max_eval_guess) for sampler in self._samplers: sampler._log_init(logger) logger.add_time('Time m:s') # Pre-allocate arrays for chain storage if self._chains_in_memory: # Store full chains samples = np.zeros( (self._n_chains, self._max_iterations, self._n_parameters)) else: # Store only the current iteration samples = np.zeros((self._n_chains, self._n_parameters)) # Pre-allocate arrays for evaluation storage if self._evaluations_in_memory: if prior: # Store posterior, likelihood, prior evaluations = np.zeros( (self._n_chains, self._max_iterations, 3)) else: # Store pdf evaluations = np.zeros((self._n_chains, self._max_iterations)) # Some samplers need intermediate steps, where None is returned instead # of a sample. Samplers can run asynchronously, so that one returns # None while another returns a sample. # To deal with this, we maintain a list of 'active' samplers that have # not reach `max_iterations` yet, and store the number of samples we # have in each chain. if self._single_chain: active = list(range(self._n_chains)) n_samples = [0] * self._n_chains # Start sampling timer = pints.Timer() running = True while running: # Initial phase # Note: self._initial_phase_iterations is None when no initial # phase is needed if iteration == self._initial_phase_iterations: for sampler in self._samplers: sampler.set_initial_phase(False) if self._log_to_screen: print('Initial phase completed.') # Get points if self._single_chain: xs = [self._samplers[i].ask() for i in active] else: xs = self._samplers[0].ask() # Calculate logpdfs fxs = evaluator.evaluate(xs) # Update evaluation count n_evaluations += len(fxs) # Update chains if self._single_chain: # Single chain # Check and update the individual chains xs_iterator = iter(xs) fxs_iterator = iter(fxs) for i in list(active): # new list: active may be modified x = next(xs_iterator) fx = next(fxs_iterator) y = self._samplers[i].tell(fx) if y is not None: # Store sample in memory if self._chains_in_memory: samples[i][n_samples[i]] = y else: samples[i] = y # Update current evaluations if store_evaluations: # Check if accepted, if so, update log_pdf and # prior to be logged accepted = np.all(y == x) if accepted: current_logpdf[i] = fx if prior is not None: current_prior[i] = prior(y) # Calculate evaluations to log e = current_logpdf[i] if prior is not None: e = [ e, current_logpdf[i] - current_prior[i], current_prior[i] ] # Store evaluations in memory if self._evaluations_in_memory: evaluations[i][n_samples[i]] = e # Write evaluations to disk if self._evaluation_files: if prior is None: eval_loggers[i].log(e) else: eval_loggers[i].log(*e) # Stop adding samples if maximum number reached n_samples[i] += 1 if n_samples[i] == self._max_iterations: active.remove(i) # This is an intermediate step until the slowest sampler has # produced a new sample since the last `iteration`. intermediate_step = min(n_samples) <= iteration else: # Multi-chain methods # Get all chains samples at once ys = self._samplers[0].tell(fxs) intermediate_step = ys is None if not intermediate_step: # Store samples in memory if self._chains_in_memory: samples[:, iteration] = ys else: samples = ys # Update current evaluations if store_evaluations: es = [] for i, y in enumerate(ys): # Check if accepted, if so, update log_pdf and # prior to be logged accepted = np.all(xs[i] == y) if accepted: current_logpdf[i] = fxs[i] if prior is not None: current_prior[i] = prior(ys[i]) # Calculate evaluations to log e = current_logpdf[i] if prior is not None: e = [ e, current_logpdf[i] - current_prior[i], current_prior[i] ] es.append(e) # Write evaluations to memory if self._evaluations_in_memory: for i, e in enumerate(es): evaluations[i, iteration] = e # Write evaluations to disk if self._evaluation_files: if prior is None: for i, eval_logger in enumerate(eval_loggers): eval_logger.log(es[i]) else: for i, eval_logger in enumerate(eval_loggers): eval_logger.log(*es[i]) # If no new samples were added, then no MCMC iteration was # performed, and so the iteration count shouldn't be updated, # logging shouldn't be triggered, and stopping criteria shouldn't # be checked if intermediate_step: continue # Write samples to disk if self._chains_in_memory: for i, chain_logger in enumerate(chain_loggers): chain_logger.log(*samples[i][iteration]) else: for i, chain_logger in enumerate(chain_loggers): chain_logger.log(*samples[i]) # Show progress if logging and iteration >= next_message: # Log state logger.log(iteration, n_evaluations) for sampler in self._samplers: sampler._log_write(logger) logger.log(timer.time()) # Choose next logging point if iteration < self._message_warm_up: next_message = iteration + 1 else: next_message = self._message_interval * ( 1 + iteration // self._message_interval) # Update iteration count iteration += 1 # Check requested number of samples if (self._max_iterations is not None and iteration >= self._max_iterations): running = False halt_message = ('Halting: Maximum number of iterations (' + str(iteration) + ') reached.') # Log final state and show halt message if logging: logger.log(iteration, n_evaluations) for sampler in self._samplers: sampler._log_write(logger) logger.log(timer.time()) if self._log_to_screen: print(halt_message) # Store generated chains in memory if self._chains_in_memory: self._samples = samples # Store evaluations in memory if self._evaluations_in_memory: self._evaluations = evaluations # Return generated chains return samples if self._chains_in_memory else None
def sample_initial_points(function, n_points, random_sampler=None, boundaries=None, max_tries=50, parallel=False, n_workers=None): """ Samples ``n_points`` parameter values to use as starting points in a sampling or optimisation routine on the given ``function``. How the initial points are determined depends on the arguments supplied. In order of precedence: 1. If a method ``random_sampler`` is provided then this will be used to draw the random samples. 2. If no sampler method is given but ``function`` is a :class:`LogPosterior` then the method ``function.log_prior().sample()`` will be used. 3. If no sampler method is supplied and ``function`` is not a :class:`LogPosterior` and if ``boundaries`` are provided then the method ``boundaries.sample()`` will be used to draw samples. A ``ValueError`` is raised if none of the above options are available. Each sample ``x`` is tested to ensure that ``function(x)`` returns a finite result within ``boundaries`` if these are supplied. If not, a new sample will be drawn. This is repeated at most ``max_tries`` times, after which an error is raised. Parameters ---------- function : A :class:`pints.ErrorMeasure` or a :class:`pints.LogPDF` that evaluates points in the parameter space. If the latter, it is optional that ``function`` be of type :class:`LogPosterior`. n_points : int The number of initial values to generate. random_sampler : A function that when called returns draws from a probability distribution of the same dimensionality as ``function``. The only argument to this function should be an integer specifying the number of draws. boundaries : An optional set of boundaries on the parameter space of class :class:`pints.Boundaries`. max_tries : int Number of attempts to find a finite initial value across all ``n_points``. By default this is 50 per point. parallel : bool Whether to evaluate ``function`` in parallel (defaults to False). n_workers : int Number of workers on which to run parallel evaluation. """ is_not_logpdf = not isinstance(function, pints.LogPDF) is_not_errormeasure = not isinstance(function, pints.ErrorMeasure) # Check function if is_not_logpdf and is_not_errormeasure: raise ValueError( 'function must be either pints.LogPDF or pints.ErrorMeasure.') # Check boundaries if boundaries is not None: if not isinstance(boundaries, pints.Boundaries): raise ValueError('boundaries must be a pints.Boundaries object.') elif boundaries.n_parameters() != function.n_parameters(): raise ValueError('boundaries must match dimension of function.') # Check or set random sampler if random_sampler is None: if isinstance(function, pints.LogPosterior): random_sampler = function.log_prior().sample elif boundaries is not None: random_sampler = boundaries.sample else: raise ValueError( 'If function is not a pints.LogPosterior and no boundaries' ' are given then a random_sampler must be supplied.') elif not callable(random_sampler): raise ValueError( 'random_sampler must be a callable function, if supplied.') # Check number of initial points if n_points < 1: raise ValueError('Number of initial points must be 1 or more.') # Set up parallelisation if parallel: n_workers = min(pints.ParallelEvaluator.cpu_count(), n_points) evaluator = pints.ParallelEvaluator(function, n_workers=n_workers) else: evaluator = pints.SequentialEvaluator(function) # Go! x0 = [] n_tries = 0 while len(x0) < n_points and n_tries < max_tries: xs = random_sampler(n_points - len(x0)) fxs = evaluator.evaluate(xs) for i, x in enumerate(xs): fx = fxs[i] if np.isfinite(fx): if boundaries is None or boundaries.check(x): x0.append(x) n_tries += 1 if len(x0) < n_points: raise RuntimeError( 'Initialisation failed since function not finite or within ' + 'bounds at initial points after ' + str(max_tries) + ' attempts.') return x0
def run(self): """See :meth:`Optimiser.run()`.""" # Default search parameters # TODO Allow changing before run() with method call parallel = True # Search is terminated after max_iter iterations # TODO Allow changing before run() with method call max_iter = 10000 # Or if the result doesn't change significantly for a while # TODO Allow changing before run() with method call max_unchanged_iterations = 100 # TODO Allow changing before run() with method call min_significant_change = 1e-11 # TODO Allow changing before run() with method call unchanged_iterations = 0 # Parameter space dimension d = self._dimension # Population size # TODO Allow changing before run() with method call # If parallel, round up to a multiple of the reported number of cores n = 4 + int(3 * np.log(d)) if parallel: cpu_count = multiprocessing.cpu_count() n = (((n - 1) // cpu_count) + 1) * cpu_count # Set up progress reporting in verbose mode nextMessage = 0 if self._verbose: if parallel: print('Running in parallel mode with population size ' + str(n)) else: print('Running in sequential mode with population size ' + str(n)) # Apply wrapper to implement boundaries if self._boundaries is None: def xtransform(x): return x else: xtransform = pints.TriangleWaveTransform(self._boundaries) # Create evaluator object if parallel: evaluator = pints.ParallelEvaluator(self._function) else: evaluator = pints.SequentialEvaluator(self._function) # Learning rates # TODO Allow changing before run() with method call eta_mu = 1 # TODO Allow changing before run() with method call eta_A = 0.6 * (3 + np.log(d)) * d**-1.5 # Pre-calculated utilities us = np.maximum(0, np.log(n / 2 + 1) - np.log(1 + np.arange(n))) us /= np.sum(us) us -= 1 / n # Center of distribution mu = np.array(self._x0, copy=True) # Initial square root of covariance matrix A = np.eye(d) * self._sigma0 # Identity matrix for later use I = np.eye(d) # Best solution found xbest = mu fbest = float('inf') # Start running for iteration in range(1, 1 + max_iter): # Create new samples zs = np.array([np.random.normal(0, 1, d) for i in range(n)]) xs = np.array([mu + np.dot(A, zs[i]) for i in range(n)]) # Evaluate at the samples fxs = evaluator.evaluate(xtransform(xs)) # Order the normalized samples according to the scores order = np.argsort(fxs) zs = zs[order] # Update center Gd = np.dot(us, zs) mu += eta_mu * np.dot(A, Gd) # Update best if needed if fxs[order[0]] < fbest: # Check if this counts as a significant change fnew = fxs[order[0]] if np.sum(np.abs(fnew - fbest)) < min_significant_change: unchanged_iterations += 1 else: unchanged_iterations = 0 # Update best xbest = xs[order[0]] fbest = fnew else: unchanged_iterations += 1 # Show progress in verbose mode: if self._verbose and iteration >= nextMessage: print(str(iteration) + ': ' + str(fbest)) if iteration < 3: nextMessage = iteration + 1 else: nextMessage = 20 * (1 + iteration // 20) # Stop if no change for too long if unchanged_iterations >= max_unchanged_iterations: if self._verbose: print('Halting: No significant change for ' + str(unchanged_iterations) + ' iterations.') break # Update root of covariance matrix Gm = np.dot(np.array([np.outer(z, z).T - I for z in zs]).T, us) A *= scipy.linalg.expm(np.dot(0.5 * eta_A, Gm)) # Show stopping criterion if self._verbose and unchanged_iterations < max_unchanged_iterations: print('Halting: Maximum iterations reached.') # Get final score at mu fmu = self._function(xtransform(mu)) if fmu < fbest: if self._verbose: print('Final score at mu beats best sample') xbest = mu fbest = fmu # Show final value if self._verbose: print(str(iteration) + ': ' + str(fbest)) # Return best solution return xtransform(xbest), fbest
def run(self): """ Runs the optimisation, returns a tuple ``(x_best, f_best)``. An optional ``callback`` function can be passed in that will be called at the end of every iteration. The callback should take the arguments ``(iteration, optimiser)``, where ``iteration`` is the iteration count (an integer) and ``optimiser`` is the optimiser object. """ # Can only run once for each controller instance if self._has_run: raise RuntimeError("Controller is valid for single use only") self._has_run = True # Check stopping criteria has_stopping_criterion = False has_stopping_criterion |= (self._max_iterations is not None) has_stopping_criterion |= (self._unchanged_max_iterations is not None) has_stopping_criterion |= (self._max_evaluations is not None) has_stopping_criterion |= (self._threshold is not None) if not has_stopping_criterion: raise ValueError('At least one stopping criterion must be set.') # Iterations and function evaluations iteration = 0 evaluations = 0 # Unchanged iterations count (used for stopping or just for # information) unchanged_iterations = 0 # Choose method to evaluate f = self._function if self._needs_sensitivities: f = f.evaluateS1 # Create evaluator object if self._parallel: # Get number of workers n_workers = self._n_workers # For population based optimisers, don't use more workers than # particles! if isinstance(self._optimiser, PopulationBasedOptimiser): n_workers = min(n_workers, self._optimiser.population_size()) evaluator = pints.ParallelEvaluator(f, n_workers=n_workers) else: evaluator = pints.SequentialEvaluator(f) # Keep track of current best and best-guess scores. fb = fg = float('inf') # Internally we always minimise! Keep a 2nd value to show the user. fb_user, fg_user = (fb, fg) if self._minimising else (-fb, -fg) # Keep track of the last significant change f_sig = float('inf') # Set up progress reporting next_message = 0 # Start logging logging = self._log_to_screen or self._log_filename if logging: if self._log_to_screen: # Show direction if self._minimising: print('Minimising error measure') else: print('Maximising LogPDF') # Show method print('Using ' + str(self._optimiser.name())) # Show parallelisation if self._parallel: print('Running in parallel with ' + str(n_workers) + ' worker processes.') else: print('Running in sequential mode.') # Show population size pop_size = 1 if isinstance(self._optimiser, PopulationBasedOptimiser): pop_size = self._optimiser.population_size() if self._log_to_screen: print('Population size: ' + str(pop_size)) # Set up logger logger = pints.Logger() if not self._log_to_screen: logger.set_stream(None) if self._log_filename: logger.set_filename(self._log_filename, csv=self._log_csv) # Add fields to log max_iter_guess = max(self._max_iterations or 0, 10000) max_eval_guess = max( self._max_evaluations or 0, max_iter_guess * pop_size) logger.add_counter('Iter.', max_value=max_iter_guess) logger.add_counter('Eval.', max_value=max_eval_guess) logger.add_float('Best') logger.add_float('Current') self._optimiser._log_init(logger) logger.add_time('Time m:s') # Start searching timer = pints.Timer() running = True try: while running: # Get points xs = self._optimiser.ask() # Calculate scores fs = evaluator.evaluate(xs) # Perform iteration self._optimiser.tell(fs) # Update current scores fb = self._optimiser.f_best() fg = self._optimiser.f_guessed() fb_user, fg_user = (fb, fg) if self._minimising else (-fb, -fg) # Check for significant changes f_new = fg if self._use_f_guessed else fb if np.abs(f_new - f_sig) >= self._unchanged_threshold: unchanged_iterations = 0 f_sig = f_new else: unchanged_iterations += 1 # Update evaluation count evaluations += len(fs) # Show progress if logging and iteration >= next_message: # Log state logger.log(iteration, evaluations, fb_user, fg_user) self._optimiser._log_write(logger) logger.log(timer.time()) # Choose next logging point if iteration < self._message_warm_up: next_message = iteration + 1 else: next_message = self._message_interval * ( 1 + iteration // self._message_interval) # Update iteration count iteration += 1 # # Check stopping criteria # # Maximum number of iterations if (self._max_iterations is not None and iteration >= self._max_iterations): running = False halt_message = ('Maximum number of iterations (' + str(iteration) + ') reached.') # Maximum number of iterations without significant change halt = (self._unchanged_max_iterations is not None and unchanged_iterations >= self._unchanged_max_iterations) if running and halt: running = False halt_message = ('No significant change for ' + str(unchanged_iterations) + ' iterations.') # Maximum number of evaluations if (self._max_evaluations is not None and evaluations >= self._max_evaluations): running = False halt_message = ( 'Maximum number of evaluations (' + str(self._max_evaluations) + ') reached.') # Threshold value halt = (self._threshold is not None and f_new < self._threshold) if running and halt: running = False halt_message = ('Objective function crossed threshold: ' + str(self._threshold) + '.') # Error in optimiser error = self._optimiser.stop() if error: # pragma: no cover running = False halt_message = str(error) elif self._callback is not None: self._callback(iteration - 1, self._optimiser) except (Exception, SystemExit, KeyboardInterrupt): # pragma: no cover # Unexpected end! # Show last result and exit print('\n' + '-' * 40) print('Unexpected termination.') print('Current score: ' + str(fg_user)) print('Current position:') # Show current parameters x_user = self._optimiser.x_guessed() if self._transformation is not None: x_user = self._transformation.to_model(x_user) for p in x_user: print(pints.strfloat(p)) print('-' * 40) raise # Stop timer self._time = timer.time() # Log final values and show halt message if logging: if iteration - 1 < next_message: logger.log(iteration, evaluations, fb_user, fg_user) self._optimiser._log_write(logger) logger.log(self._time) if self._log_to_screen: print('Halting: ' + halt_message) # Save post-run statistics self._evaluations = evaluations self._iterations = iteration # Get best parameters if self._use_f_guessed: x = self._optimiser.x_guessed() f = self._optimiser.f_guessed() else: x = self._optimiser.x_best() f = self._optimiser.f_best() # Inverse transform search parameters if self._transformation is not None: x = self._transformation.to_model(x) # Return best position and score return x, f if self._minimising else -f
def run(self): """ Runs the MCMC sampler(s) and returns a number of markov chains, each representing the distribution of the given log-pdf. """ # Check stopping criteria has_stopping_criterion = False has_stopping_criterion |= (self._max_iterations is not None) if not has_stopping_criterion: raise ValueError('At least one stopping criterion must be set.') # Iteration and evaluation counting iteration = 0 evaluations = 0 # Choose method to evaluate f = self._log_pdf if self._needs_sensitivities: f = f.evaluateS1 # Create evaluator object if self._parallel: # Use at most n_workers workers n_workers = min(self._n_workers, self._chains) evaluator = pints.ParallelEvaluator(f, n_workers=n_workers) else: evaluator = pints.SequentialEvaluator(f) # Initial phase if self._needs_initial_phase: for sampler in self._samplers: sampler.set_initial_phase(True) # Write chains to disk chain_loggers = [] if self._chain_files: for filename in self._chain_files: cl = pints.Logger() cl.set_stream(None) cl.set_filename(filename, True) for k in range(self._n_parameters): cl.add_float('p' + str(k)) chain_loggers.append(cl) # Write evaluations to disk eval_loggers = [] if self._evaluation_files: # Bayesian inference on a log-posterior? Then separate out the # prior so we can calculate the loglikelihood prior = None if isinstance(self._log_pdf, pints.LogPosterior): prior = self._log_pdf.log_prior() # Set up loggers for filename in self._evaluation_files: cl = pints.Logger() cl.set_stream(None) cl.set_filename(filename, True) if prior: # Logposterior in first column, to be consistent with the # non-bayesian case cl.add_float('logposterior') cl.add_float('loglikelihood') cl.add_float('logprior') else: cl.add_float('logpdf') eval_loggers.append(cl) # Store last accepted logpdf, per chain current_logpdf = np.zeros(self._chains) current_prior = np.zeros(self._chains) # Set up progress reporting next_message = 0 # Start logging logging = self._log_to_screen or self._log_filename if logging: if self._log_to_screen: print('Using ' + str(self._samplers[0].name())) print('Generating ' + str(self._chains) + ' chains.') if self._parallel: print('Running in parallel with ' + str(n_workers) + ' worker processess.') else: print('Running in sequential mode.') if self._chain_files: print('Writing chains to ' + self._chain_files[0] + ' etc.') if self._evaluation_files: print('Writing evaluations to ' + self._evaluation_files[0] + ' etc.') # Set up logger logger = pints.Logger() if not self._log_to_screen: logger.set_stream(None) if self._log_filename: logger.set_filename(self._log_filename, csv=self._log_csv) # Add fields to log max_iter_guess = max(self._max_iterations or 0, 10000) max_eval_guess = max_iter_guess * self._chains logger.add_counter('Iter.', max_value=max_iter_guess) logger.add_counter('Eval.', max_value=max_eval_guess) for sampler in self._samplers: sampler._log_init(logger) logger.add_time('Time m:s') # Create chains # TODO Pre-allocate? chains = [] # Start sampling timer = pints.Timer() running = True while running: # Initial phase # Note: self._initial_phase_iterations is None when no initial # phase is needed if iteration == self._initial_phase_iterations: for sampler in self._samplers: sampler.set_initial_phase(False) if self._log_to_screen: print('Initial phase completed.') # Get points if self._single_chain: xs = [sampler.ask() for sampler in self._samplers] else: xs = self._samplers[0].ask() # Calculate logpdfs fxs = evaluator.evaluate(xs) # Update evaluation count evaluations += len(fxs) # Update chains intermediate_step = False if self._single_chain: samples = np.array( [s.tell(fxs[i]) for i, s in enumerate(self._samplers)]) none_found = [x is None for x in samples] if any(none_found): assert (all(none_found)) # Can't mix None w. samples intermediate_step = True else: samples = self._samplers[0].tell(fxs) intermediate_step = samples is None # If no new samples were added, then no MCMC iteration was # performed, and so the iteration count shouldn't be updated, # logging shouldn't be triggered, and stopping criteria shouldn't # be checked if intermediate_step: continue # Add new samples to the chains chains.append(samples) # Write samples to disk for k, chain_logger in enumerate(chain_loggers): chain_logger.log(*samples[k]) # Write evaluations to disk if self._evaluation_files: for k, eval_logger in enumerate(eval_loggers): if np.all(xs[k] == samples[k]): current_logpdf[k] = fxs[k] if prior is not None: current_prior[k] = prior(xs[k]) eval_logger.log(current_logpdf[k]) if prior is not None: eval_logger.log(current_logpdf[k] - current_prior[k]) eval_logger.log(current_prior[k]) # Show progress if logging and iteration >= next_message: # Log state logger.log(iteration, evaluations) for sampler in self._samplers: sampler._log_write(logger) logger.log(timer.time()) # Choose next logging point if iteration < self._message_warm_up: next_message = iteration + 1 else: next_message = self._message_interval * ( 1 + iteration // self._message_interval) # Update iteration count iteration += 1 # # Check stopping criteria # # Maximum number of iterations if (self._max_iterations is not None and iteration >= self._max_iterations): running = False halt_message = ('Halting: Maximum number of iterations (' + str(iteration) + ') reached.') # TODO Add more stopping criteria # Log final state and show halt message if logging: logger.log(iteration, evaluations) for sampler in self._samplers: sampler._log_write(logger) logger.log(timer.time()) if self._log_to_screen: print(halt_message) # Swap axes in chains, to get indices # [chain, iteration, parameter] chains = np.array(chains) chains = chains.swapaxes(0, 1) # Return generated chains return chains
def _run(self, result): import pints import numpy as np # Show method name #log.info('Using method: ' + self._method) # Get method class method = getattr(pints, self._method) # Get problem score, xtrue, x0, sigma0, boundaries = self._problem() # Evaluate at true parameters ftrue = score(xtrue) # Create optimiser optimiser = method(x0, sigma0, boundaries) # Count iterations and function evaluations iterations = 0 evaluations = 0 unchanged_iterations = 0 # Create sequential evaluator (allowing external parallelisation) evaluator = pints.SequentialEvaluator(score) # Keep track of best position and score fbest = float('inf') # Start searching running = True while running: xs = optimiser.ask() fs = evaluator.evaluate(xs) optimiser.tell(fs) # Check if new best found fnew = optimiser.fbest() if fnew < fbest: # Check if this counts as a significant change if np.abs(fnew - fbest) < 1e-9: unchanged_iterations += 1 else: unchanged_iterations = 0 # Update best position fbest = fnew else: unchanged_iterations += 1 # Update iteration and evaluation count iterations += 1 evaluations += len(fs) # Maximum number of iterations if iterations >= 5000: running = False # Maximum number of iterations without significant change if unchanged_iterations >= 200: running = False # Error in optimiser error = optimiser.stop() if error: running = False # Store solution result['xbest'] = optimiser.xbest() result['fbest'] = fbest # Store solution, relative to likelihood at 'true' solution # This should be 1 or below 1 if the optimum was found result['fbest_relative'] = fbest / ftrue # Store status result['status'] = 'done'
def run(self): """ Runs the ABC sampler. """ if self._max_iterations is None: raise ValueError("At least one stopping criterion must be set.") # Iteration and evaluation counting iteration = 0 evaluations = 0 accepted_count = 0 # Choose method to evaluate f = self._error_measure # Create evaluator if self._parallel: n_workers = self._n_workers evaluator = pints.ParallelEvaluator(f, n_workers=n_workers) else: evaluator = pints.SequentialEvaluator(f) # Set up progress reporting next_message = 0 # Start logging logging = self._log_to_screen or self._log_filename if logging: if self._log_to_screen: print('Using ' + str(self._sampler.name())) if self._parallel: print('Running in parallel with ' + str(n_workers) + ' worker processess.') else: print('Running in sequential mode.') # Set up logger logger = pints.Logger() if not self._log_to_screen: logger.set_stream(None) if self._log_filename: logger.set_filename(self._log_filename, csv=self._log_csv) # Add fields to log max_iter_guess = max(self._max_iterations or 0, 10000) max_eval_guess = max_iter_guess logger.add_counter('Iter.', max_value=max_iter_guess) logger.add_counter('Eval.', max_value=max_eval_guess) logger.add_float('Acceptance rate') self._sampler._log_init(logger) logger.add_time('Time m:s') # Start sampling timer = pints.Timer() running = True # Specifying the number of samples we want to get # from the prior at once. It depends on whether we # are using parallelisation and how many workers # are being used. if self._parallel: n_requested_samples = self._n_workers else: n_requested_samples = 1 samples = [] # Sample until we find an acceptable sample while running: accepted_vals = None while accepted_vals is None: # Get points from prior xs = self._sampler.ask(n_requested_samples) # Simulate and get error fxs = evaluator.evaluate(xs) evaluations += self._n_workers # Tell sampler errors and get list of acceptable parameters accepted_vals = self._sampler.tell(fxs) accepted_count += len(accepted_vals) for val in accepted_vals: samples.append(val) iteration += 1 # Log progress if logging and iteration >= next_message: # Log state logger.log(iteration, evaluations, (accepted_count / evaluations)) self._sampler._log_write(logger) logger.log(timer.time()) # Choose next logging point if iteration < self._message_warm_up: next_message = iteration + 1 else: next_message = self._message_interval * ( 1 + iteration // self._message_interval) if iteration >= self._max_iterations: running = False halt_message = ('Halting: Maximum number of iterations (' + str(iteration) + ') reached. Only (' + str(accepted_count) + ') sample were ' + 'obtained') elif accepted_count >= self._n_samples: running = False halt_message = ('Halting: target number of samples (' + str(accepted_count) + ') reached.') # Log final state and show halt message if logging: logger.log(iteration, evaluations) self._sampler._log_write(logger) logger.log(timer.time()) if self._log_to_screen: print(halt_message) samples = np.array(samples) return samples
def run(self): """See :meth:`Optimiser.run()`.""" # Global/local search balance # TODO Allow changing before run() with method call r = 0.5 # Check at least one stopping criterion is set if (self._max_iterations == 0 and self._max_unchanged_iterations == 0): raise ValueError('At least one stopping criterion must be set.') # Unchanged iterations count (used for stopping or just for # information) unchanged_iterations = 0 # Parameter space dimension d = self._dimension # Population size # TODO Allow changing before run() with method call # If parallel, round up to a multiple of the reported number of cores n = 4 + int(3 * np.log(d)) if self._parallel: cpu_count = multiprocessing.cpu_count() n = min(3, (((n - 1) // cpu_count) + 1)) * cpu_count # Set up progress reporting in verbose mode nextMessage = 0 if self._verbose: if self._parallel: print('Running in parallel mode with population size ' + str(n)) else: print('Running in sequential mode with population size ' + str(n)) # Set parameters based on global/local balance r amax = 4.1 almax = r * amax agmax = amax - almax # Initialize swarm xs = [] # Particle coordinate vectors vs = [] # Particle velocity vectors fs = [] # Particle scores fl = [] # Best local score pl = [] # Best local position fg = 0 # Best global score pg = 0 # Best global position # Set initial positions xs.append(np.array(self._x0, copy=True)) if self._boundaries is None: for i in range(1, n): xs.append(np.random.normal(self._x0, self._sigma0)) else: for i in range(1, n): xs.append(self._boundaries._lower + np.random.uniform(0, 1, d) * (self._boundaries._upper - self._boundaries._lower)) # Set initial velocities for i in range(n): vs.append(self._sigma0 * np.random.uniform(0, 1, d)) # Set initial scores and local best for i in range(n): fs.append(float('inf')) fl.append(float('inf')) pl.append(xs[i]) # Set global best position and score fg = float('inf') pg = xs[0] # Apply wrapper to score function to implement boundaries function = self._function if self._boundaries is not None: function = pints.InfBoundaryTransform(function, self._boundaries) # Create evaluator object if self._parallel: evaluator = pints.ParallelEvaluator(self._function) else: evaluator = pints.SequentialEvaluator(self._function) # Start searching running = True iteration = 0 while running: # Calculate scores fs = evaluator.evaluate(xs) # Update particles for i in range(n): # Update best local position and score if fs[i] < fl[i]: fl[i] = fs[i] pl[i] = np.array(xs[i], copy=True) # Calculate "velocity" al = np.random.uniform(0, almax, d) ag = np.random.uniform(0, agmax, d) vs[i] += al * (pl[i] - xs[i]) + ag * (pg - xs[i]) # Update position e = xs[i] + vs[i] if self._boundaries is not None: # To reduce the amount of time spent outside the bounds of # the search space, the velocity of any particle outside # the bounds is reduced by a factor # (1 / (1 + number of boundary violations)). if not self._boundaries.check(e): vs[i] *= 1 / (1 + np.sum(e)) xs[i] += vs[i] # Update global best i = np.argmin(fl) if fl[i] < fg: # Check if this counts as a significant change fnew = fl[i] if np.sum(np.abs(fnew - fg)) < self._min_significant_change: unchanged_iterations += 1 else: unchanged_iterations = 0 # Update best fg = fnew pg = np.array(pl[i], copy=True) else: unchanged_iterations += 1 # Show progress in verbose mode: if self._verbose and iteration >= nextMessage: print(str(iteration) + ': ' + str(fg)) if iteration < 3: nextMessage = iteration + 1 else: nextMessage = 20 * (1 + iteration // 20) # Update iteration count iteration += 1 # Check stopping criteria # Maximum number of iterations if self._max_iterations and iteration >= self._max_iterations: running = False if self._verbose: print('Halting: Maximum number of iterations (' + str(iteration) + ' reached.') # Maximum number of iterations without significant change if (self._max_unchanged_iterations and unchanged_iterations >= self._max_unchanged_iterations): running = False if self._verbose: print('Halting: No significant change for ' + str(unchanged_iterations) + ' iterations.') # Show final value if self._verbose: print(str(iteration) + ': ' + str(fg)) # Return best position and score return pg, fg
def run(self, returnLL=False): """ Runs the MCMC sampler(s) and returns a number of markov chains, each representing the distribution of the given log-pdf. """ # Check stopping criteria has_stopping_criterion = False has_stopping_criterion |= (self._max_iterations is not None) if not has_stopping_criterion: raise ValueError('At least one stopping criterion must be set.') # Iteration and evaluation counting iteration = 0 evaluations = 0 # Create evaluator object if self._parallel: # Use at most n_workers workers n_workers = min(self._n_workers, self._chains) evaluator = pints.ParallelEvaluator(self._log_pdf, n_workers=n_workers) else: evaluator = pints.SequentialEvaluator(self._log_pdf) # Initial phase if self._needs_initial_phase: for sampler in self._samplers: sampler.set_initial_phase(True) # Set up progress reporting next_message = 0 # Start logging logging = self._log_to_screen or self._log_filename if logging: if self._log_to_screen: print('Using ' + str(self._samplers[0].name())) print('Generating ' + str(self._chains) + ' chains.') if self._parallel: print('Running in parallel with ' + str(n_workers) + ' worker processess.') else: print('Running in sequential mode.') # Set up logger logger = pints.Logger() if not self._log_to_screen: logger.set_stream(None) if self._log_filename: logger.set_filename(self._log_filename, csv=self._log_csv) # Add fields to log max_iter_guess = max(self._max_iterations or 0, 10000) max_eval_guess = max_iter_guess * self._chains logger.add_counter('Iter.', max_value=max_iter_guess) logger.add_counter('Eval.', max_value=max_eval_guess) for sampler in self._samplers: sampler._log_init(logger) logger.add_time('Time m:s') # Create chains # TODO Pre-allocate? # TODO Thinning # TODO Advanced logging LLs = [] chains = [] # Start sampling timer = pints.Timer() running = True while running: # Initial phase if (self._needs_initial_phase and iteration == self._initial_phase_iterations): for sampler in self._samplers: sampler.set_initial_phase(False) if self._log_to_screen: print('Initial phase completed.') # Get points if self._single_chain: xs = [sampler.ask() for sampler in self._samplers] else: xs = self._samplers[0].ask() # Calculate scores fxs = evaluator.evaluate(xs) # Perform iteration(s) if self._single_chain: samples = np.array( [s.tell(fxs[i]) for i, s in enumerate(self._samplers)]) else: samples = self._samplers[0].tell(fxs) if returnLL: LLs.append(evaluator.evaluate(samples)) chains.append(samples) # Update evaluation count evaluations += len(fxs) # Show progress if logging and iteration >= next_message: # Log state logger.log(iteration, evaluations) for sampler in self._samplers: sampler._log_write(logger) logger.log(timer.time()) # Choose next logging point if iteration < self._message_warm_up: next_message = iteration + 1 else: next_message = self._message_rate * ( 1 + iteration // self._message_rate) # Update iteration count iteration += 1 # # Check stopping criteria # # Maximum number of iterations if (self._max_iterations is not None and iteration >= self._max_iterations): running = False halt_message = ('Halting: Maximum number of iterations (' + str(iteration) + ') reached.') # TODO Add more stopping criteria # Log final state and show halt message if logging: logger.log(iteration, evaluations) for sampler in self._samplers: sampler._log_write(logger) logger.log(timer.time()) if self._log_to_screen: print(halt_message) # Swap axes in chains, to get indices # [chain, iteration, parameter] chains = np.array(chains) chains = chains.swapaxes(0, 1) if returnLL: LLs = np.array(LLs) #LLs = LLs.swapaxes(0, 1) return chains, LLs # Return generated chains return chains
def run(self): """See: :meth:`pints.Optimiser.run()`.""" # Import cma (may fail!) # Only the first time this is called in a running program incurs # much overhead. import cma # Get BestSolution in cma 1.x and 2.x # try: # from cma import BestSolution # except ImportError: # from cma.optimization_tools import BestSolution # Default search parameters # TODO Allow changing before run() with method call parallel = True # Parameter space dimension d = self._dimension # Population size # TODO Allow changing before run() with method call # If parallel, round up to a multiple of the reported number of cores # In IPOP-CMAES, this will be used as the _initial_ population size n = 4 + int(3 * np.log(d)) if parallel: cpu_count = multiprocessing.cpu_count() n = (((n - 1) // cpu_count) + 1) * cpu_count # Search is terminated after max_iter iterations # TODO Allow changing before run() with method call max_iter = 10000 # CMA-ES default: 100 + 50 * (d + 3)**2 // n**0.5 # Or if successive iterations do not produce a significant change # TODO Allow changing before run() with method call # max_unchanged_iterations = 100 min_significant_change = 1e-11 # unchanged_iterations = 0 # CMA-ES max_unchanged_iterations fixed value: 10 + 30 * d / n # Create evaluator object if parallel: evaluator = pints.ParallelEvaluator(self._function) else: evaluator = pints.SequentialEvaluator(self._function) # Set up simulation options = cma.CMAOptions() # Set boundaries if self._boundaries is not None: options.set( 'bounds', [list(self._boundaries._lower), list(self._boundaries._upper)]) # Set stopping criteria options.set('maxiter', max_iter) options.set('tolfun', min_significant_change) # options.set('ftarget', target) # Tell CMA not to worry about growing step sizes too much options.set('tolfacupx', 10000) # CMA-ES wants a single standard deviation as input, use the smallest # in the vector (if the user passed in a scalar, this will be the # value used). sigma0 = np.min(self._sigma0) # Tell cma-es to be quiet if not self._verbose: options.set('verbose', -9) # Set population size options.set('popsize', n) if self._verbose: print('Population size ' + str(n)) # Search es = cma.CMAEvolutionStrategy(self._x0, sigma0, options) while not es.stop(): candidates = es.ask() es.tell(candidates, evaluator.evaluate(candidates)) if self._verbose: es.disp() # Show result if self._verbose: es.result_pretty() # Get solution x = es.result.xbest fx = es.result.fbest # No result found? Then return hint and score of hint if x is None: return self._x0, self._function(self._x0) # Return proper result return x, fx