class SearchAlgorithm(ABC): """Base class for all search algorithms. Parameters ---------- config : SearchConfiguration The configuation of the search algorithm. Attributes ---------- config : SearchConfiguration The configuation of the search algorithm. generation : int The current generation, or iteration, of the search. best_seen : Individual The best Individual, with respect to total error, seen so far. population : Population The current Population of individuals. """ def __init__(self, config: SearchConfiguration): self.config = config self.generation = 0 self.best_seen = None self.init_population() def init_population(self): """Initialize the population.""" self.population = Population() for i in range(self.config.population_size): spawner = self.config.spawner genome = spawner.spawn_genome(self.config.initial_genome_size) self.population.add(Individual(genome)) @abstractmethod def step(self) -> bool: """Perform one generation (step) of the search. Return if should continue. The step method should assume an evaluated Population, and must only perform parent selection and variation (producing children). The step method should modify the search algorithms population in-place, or assign a new Population to the population attribute. """ pass def _full_step(self) -> bool: self.generation += 1 self.population.evaluate(self.config.evaluator) best_this_gen = self.population.best() if self.best_seen is None or best_this_gen.total_error < self.best_seen.total_error: self.best_seen = best_this_gen if self.best_seen.total_error <= self.config.error_threshold: return False if self.config.verbosity_config.generation >= self.config.verbosity_config.log_level and \ self.generation % self.config.verbosity_config.every_n_generations == 0: stat_logs = [] stat_logs.append("GENERATION: {g}".format( g=self.generation )) stat_logs.append("ERRORS: best={b}, median={m}, diversity={e_d}".format( b=self.population.best().total_error, m=self.population.median_error(), e_d=self.population.error_diversity() )) # stat_logs.append("Population: size={p_s}, genome_diversity={g_d}".format( # p_s=len(self.population), # g_d=self.population.genome_diversity() # )) log(self.config.verbosity_config.generation, " | ".join(stat_logs)) self.step() return True def _is_solved(self): return self.best_seen.total_error <= self.config.error_threshold def run(self): """Run the algorithm until termination.""" while self._full_step(): if self.generation >= self.config.max_generations: break verbose_solution = self.config.verbosity_config.solution_found if verbose_solution >= self.config.verbosity_config.log_level: if self._is_solved(): log(verbose_solution, "Unsimplified solution found.") else: log(verbose_solution, "No unsimplified solution found.") # Simplify the best individual for a better generalization and interpreteation. simplifier = GenomeSimplifier(self.config.evaluator, self.config.verbosity_config) simp_genome, simp_error_vector = simplifier.simplify( self.best_seen.genome, self.best_seen.error_vector, self.config.simplification_steps ) simplified_best = Individual(simp_genome) simplified_best.error_vector = simp_error_vector if verbose_solution >= self.config.verbosity_config.log_level: if self._is_solved(): log(verbose_solution, "Simplified solution found.") else: log(verbose_solution, "No simplified solution found.") return simplified_best
class SearchAlgorithm(ABC): """Base class for all search algorithms. Parameters ---------- config : SearchConfiguration The configuation of the search algorithm. Attributes ---------- config : SearchConfiguration The configuration of the search algorithm. generation : int The current generation, or iteration, of the search. best_seen : Individual The best Individual, with respect to total error, seen so far. population : Population The current Population of individuals. """ def __init__(self, config: SearchConfiguration): self.config = config self._p_context = config.parallel_context self.generation = 0 self.best_seen = None self.population = None self.init_population() def init_population(self): """Initialize the population.""" spawner = self.config.spawner init_gn_size = self.config.initial_genome_size pop_size = self.config.population_size signature = self.config.signature self.population = Population() if self._p_context is not None: gen_func = partial(_spawn_individual, self._p_context.ns.spawner, init_gn_size, signature) for indiv in self._p_context.pool.imap_unordered( gen_func, range(pop_size)): self.population.add(indiv) else: for i in range(pop_size): self.population.add( _spawn_individual(spawner, init_gn_size, signature)) @tap @abstractmethod def step(self) -> bool: """Perform one generation (step) of the search. The step method should assume an evaluated Population, and must only perform parent selection and variation (producing children). The step method should modify the search algorithms population in-place, or assign a new Population to the population attribute. """ pass def _full_step(self) -> bool: self.generation += 1 if self._p_context is not None: self.population.p_evaluate(self._p_context.ns.evaluator, self._p_context.pool) else: self.population.evaluate(self.config.evaluator) best_this_gen = self.population.best() if self.best_seen is None or best_this_gen.total_error < self.best_seen.total_error: self.best_seen = best_this_gen if self.best_seen.total_error <= self.config.error_threshold: return False self.step() return True def is_solved(self) -> bool: """Return ``True`` if the search algorithm has found a solution or ``False`` otherwise.""" return self.best_seen.total_error <= self.config.error_threshold @tap def run(self) -> Individual: """Run the algorithm until termination.""" while self._full_step(): if self.generation >= self.config.max_generations: break # Simplify the best individual for a better generalization and interpretation. simplifier = GenomeSimplifier(self.config.evaluator, self.config.signature) simp_genome, simp_error_vector = simplifier.simplify( self.best_seen.genome, self.best_seen.error_vector, self.config.simplification_steps) simplified_best = Individual(simp_genome, self.config.signature) simplified_best.error_vector = simp_error_vector self.best_seen = simplified_best return self.best_seen
class SearchAlgorithm(ABC): """Base class for all search algorithms. Parameters ---------- config : SearchConfiguration The configuation of the search algorithm. Attributes ---------- config : SearchConfiguration The configuation of the search algorithm. generation : int The current generation, or iteration, of the search. best_seen : Individual The best Individual, with respect to total error, seen so far. population : Population The current Population of individuals. """ def __init__(self, config: SearchConfiguration): self.config = config self.generation = 0 self.best_seen = None self.init_population() def init_population(self): """Initialize the population.""" self.population = Population() for i in range(self.config.population_size): spawner = self.config.spawner genome = spawner.spawn_genome(self.config.initial_genome_size) self.population.add(Individual(genome)) @abstractmethod def step(self) -> bool: """Perform one generation (step) of the search. Return if should continue. The step method should assume an evaluated Population, and must only perform parent selection and variation (producing children). The step method should modify the search algorithms population in-place, or assign a new Population to the population attribute. """ pass def _full_step(self) -> bool: self.generation += 1 self.population.evaluate(self.config.evaluator) best_this_gen = self.population.best() if self.best_seen is None or best_this_gen.total_error < self.best_seen.total_error: self.best_seen = best_this_gen if self.best_seen.total_error <= self.config.error_threshold: return False if self.config.verbosity_config.generation and \ self.generation % self.config.verbosity_config.every_n_generations == 0: stat_logs = [] stat_logs.append("GENERATION: {g}".format(g=self.generation)) stat_logs.append( "ERRORS: best={b}, median={m}, diversity={e_d}".format( b=self.population.best().total_error, m=self.population.median_error(), e_d=self.population.error_diversity())) # stat_logs.append("Population: size={p_s}, genome_diversity={g_d}".format( # p_s=len(self.population), # g_d=self.population.genome_diversity() # )) self.config.verbosity_config.generation(" | ".join(stat_logs)) self.step() return True def _is_solved(self): return self.best_seen.total_error <= self.config.error_threshold def run(self): """Run the algorithm until termination.""" while self._full_step(): if self.generation >= self.config.max_generations: break verbose_solution = self.config.verbosity_config.solution_found if verbose_solution: if self._is_solved(): verbose_solution("Unsimplified solution found.") else: verbose_solution("No unsimplified solution found.") # Simplify the best individual for a better generalization and interpreteation. simplifier = GenomeSimplifier(self.config.evaluator, self.config.verbosity_config) simp_genome, simp_error_vector = simplifier.simplify( self.best_seen.genome, self.best_seen.error_vector, self.config.simplification_steps) simplified_best = Individual(simp_genome) simplified_best.error_vector = simp_error_vector if verbose_solution: if self._is_solved(): verbose_solution("Simplified solution found.") else: verbose_solution("No simplified solution found.") return simplified_best