class ParticleSwarmOptimizer(Searcher): """ A searcher that models a "flock" of individuals roaming the search space. Individuals make decisions about where to go next based both on their own experience and on the experience of the whole group. For more information, see https://github.com/timm/sbse14/wiki/pso#details and http://en.wikipedia.org/wiki/Particle_swarm_optimization """ def __init__(self, *args, **kwargs): super(ParticleSwarmOptimizer, self).__init__(*args, **kwargs) self._flock = tuple(self._new_particle() for _ in range(self.spec.population_size)) self._evals = len(self._flock) self._current_flock_energies = NumberLog(p.energy for p in self._flock) self._best = min(self._flock, key=lambda x: x.energy) self._lives = 4 self._best_flock = None def _new_particle(self): return Particle(self.model, self.spec.phi1, self.spec.phi2) def _update(self): self._prev_flock_energies = self._current_flock_energies for p in self._flock: p._update(self._best) self._evals += len(self._flock) self._current_flock_energies = NumberLog(p.energy for p in self._flock) self._best = min(self._best, *self._current_flock_energies) if self._current_flock_energies.better(self._prev_flock_energies) or self._best_flock is None: self._best_flock = self._flock else: self._lives -= 1 def run(self, text_report=False): for i in range(self.spec.generations): self._update() if self._lives <= 0 or self._evals >= self.spec.iterations: break best_flock_energies = NumberLog(p.energy for p in self._best_flock) return SearchReport(best=self._best, best_era=best_flock_energies, evaluations=self._evals, searcher=self.__class__, spec=self.spec, report=None)
def run(self, text_report=True): init_xs = tuple(self.model.random_input_vector() for _ in xrange(self.spec.population_size)) get_energy = lambda x: x.energy best_era = None report = base.StringBuilder() if text_report else base.NullObject() self._population = tuple(self.model.compute_model_io(xs) for xs in init_xs) best = min(self._population, key=get_energy) self._evals, lives = 0, 4 for gen in xrange(self.spec.iterations): if self._evals > self.spec.iterations or lives <= 0: break prev_best_energy = best.energy self._population = self._breed_next_generation() best_in_generation = min(self._population, key=get_energy) best = min(best, best_in_generation, key=get_energy) report += str(best.energy) report += ('+' if x.energy < prev_best_energy else '.' for x in self._population) report += '\n' energies = NumberLog(inits=(c.energy for c in self._population)) try: improved = energies.better(prev_energies) except NameError: improved = False prev_energies = energies # noqa: flake8 doesn't catch use above if improved: best_era = energies else: lives -= 1 if best_era is None: best_era = energies return SearchReport(best=best.energy, best_era=best_era, evaluations=self._evals, searcher=self.__class__, spec=self.spec, report=None)
class ParticleSwarmOptimizer(Searcher): """ A searcher that models a "flock" of individuals roaming the search space. Individuals make decisions about where to go next based both on their own experience and on the experience of the whole group. For more information, see https://github.com/timm/sbse14/wiki/pso#details and http://en.wikipedia.org/wiki/Particle_swarm_optimization """ def __init__(self, *args, **kwargs): super(ParticleSwarmOptimizer, self).__init__(*args, **kwargs) self._flock = tuple(self._new_particle() for _ in range(self.spec.population_size)) self._evals = len(self._flock) self._current_flock_energies = NumberLog(p.energy for p in self._flock) self._best = min(self._flock, key=lambda x: x.energy) self._lives = 4 self._best_flock = None def _new_particle(self): return Particle(self.model, self.spec.phi1, self.spec.phi2) def _update(self): self._prev_flock_energies = self._current_flock_energies for p in self._flock: p._update(self._best) self._evals += len(self._flock) self._current_flock_energies = NumberLog(p.energy for p in self._flock) self._best = min(self._best, *self._current_flock_energies) if self._current_flock_energies.better( self._prev_flock_energies) or self._best_flock is None: self._best_flock = self._flock else: self._lives -= 1 def run(self, text_report=False): for i in range(self.spec.generations): self._update() if self._lives <= 0 or self._evals >= self.spec.iterations: break best_flock_energies = NumberLog(p.energy for p in self._best_flock) return SearchReport(best=self._best, best_era=best_flock_energies, evaluations=self._evals, searcher=self.__class__, spec=self.spec, report=None)
def run(self, text_report=True): n_candiates = self.spec.n_candiates self._frontier = [ self.model.random_model_io() for _ in xrange(n_candiates) ] self._evals, lives = 0, 4 for _ in xrange(self.spec.generations): if lives <= 0: break old_frontier_energies = NumberLog(x.energy for x in self._frontier) self._update_frontier() new_frontier_energies = NumberLog(x.energy for x in self._frontier) if not new_frontier_energies.better(old_frontier_energies): lives -= 1 return SearchReport( best=min(self._frontier, key=lambda x: x.energy).energy, best_era=NumberLog(inits=(x.energy for x in self._frontier)), evaluations=self._evals, searcher=self.__class__, spec=self.spec, report=None)
def run(self, text_report=True): n_candiates = self.spec.n_candiates self._frontier = [self.model.random_model_io() for _ in xrange(n_candiates)] self._evals, lives = 0, 4 for _ in xrange(self.spec.generations): if lives <= 0: break old_frontier_energies = NumberLog(x.energy for x in self._frontier) self._update_frontier() new_frontier_energies = NumberLog(x.energy for x in self._frontier) if not new_frontier_energies.better(old_frontier_energies): lives -= 1 return SearchReport( best=min(self._frontier, key=lambda x: x.energy).energy, best_era=NumberLog(inits=(x.energy for x in self._frontier)), evaluations=self._evals, searcher=self.__class__, spec=self.spec, report=None)
class SimulatedAnnealer(Searcher): """ A searcher that works by mostly-dumb stochastic search that starts with lots of random jumps, then makes fewer random jumps, simulating a cooling process. See http://en.wikipedia.org/wiki/Simulated_annealing and https://github.com/timm/sbse14/wiki/sa for more information. """ def __init__(self, *args, **kwargs): super(SimulatedAnnealer, self).__init__(*args, **kwargs) self._current = self.model.random_model_io() self._best = self._current # assumes current is immutable self._lives = 4 self._best_era = None self._current_era_energies = NumberLog(max_size=None) def run(self, text_report=True): """ Run the SimulatedAnnealer on the model specified at object instantiation time. """ self._report = StringBuilder() if text_report else NullObject() evals = None for k in range(self.spec.iterations): if self._lives <= 0 and self.spec.terminate_early: evals = k break self._update(k / self.spec.iterations) if k % self.spec.era_length == 0 and k != 0: self._end_era() if evals is None: evals = self.spec.iterations return SearchReport(best=self._best.energy, evaluations=evals, best_era=self._best_era, spec=self.spec, searcher=self.__class__, report=self._report) def _mutate(self, xs): return tuple(xs[i] if random.random() < self.spec.p_mutation else v for i, v in enumerate(self.model.random_input_vector())) def _get_neighbor(self, model_io): neighbor = None while neighbor is None: gen = self._mutate(model_io.xs) try: neighbor = self.model(tuple(gen), io=True) except ModelInputException: pass return neighbor def _end_era(self): self._report += ('\n', '{: .2}'.format(self._best.energy), ' ') if not self._best_era: self._best_era = self._current_era_energies try: improved = self._current_era_energies.better( self._prev_era_energies) except AttributeError: improved = False if improved: self._best_era = self._current_era_energies else: self._lives -= 1 self._prev_era_energies = self._current_era_energies self._current_era_energies = NumberLog(max_size=None) def _update(self, temperature): """update the state of the annealer""" # generate new neighbor neighbor = self._get_neighbor(self._current) self._current_era_energies += neighbor.energy # compare neighbor and update best if neighbor.energy < self._best.energy: self._best, self._current = neighbor, neighbor self._report += '!' if neighbor.energy < self._current.energy: self._current = neighbor self._report += '+' else: # if neighbor is worse than current, we still jump there sometimes cnorm = self.model.normalize(self._current.energy) nnorm = self.model.normalize(neighbor.energy) # occasionally jump to neighbor, even if it's a bad idea if self._good_idea(cnorm, nnorm, temperature) < random.random(): self._current = neighbor self._report += '?' self._report += '.' def _good_idea(self, old, new, temp): """ sets the threshold we compare to to decide whether to jump returns e^-((new-old)/temp) """ numerator = new - old if not 0 <= numerator <= 1: numerator = old - new try: exponent = numerator / temp except ZeroDivisionError: return 0 rv = math.exp(-exponent) if rv > 1: raise ValueError('p returning greater than one', rv, old, new, temp) return rv * self.spec.cooling_factor