def test_add_to_initial_population_successful(self): class SpyFitnessFunc: def __init__(self): self.fitness_func_executions = 0 def fitness_func(self, bitstring): self.fitness_func_executions += 1 return 69.0 unused_spy_fitness_function_holder = SpyFitnessFunc() genome_factory = PassThroughGenomeFactory( Genome.new_default_genome( (5, ), unused_spy_fitness_function_holder.fitness_func)) annealing_schedule = PassThroughAnnealingSchedule(0, True) optimizer_builder = BasicAnnealerOptimizerBuilder( (5, ), NumpyRNG(), genome_factory, annealing_schedule, unused_spy_fitness_function_holder.fitness_func) spy_fitness_function_holder = SpyFitnessFunc() population_genome = Genome(spy_fitness_function_holder.fitness_func, (0, 0, 0, 0, 0)) optimizer = optimizer_builder \ .add_to_initial_population(population_genome) \ .build() self.assertAlmostEqual(optimizer.best_genome.fitness, 69.0) self.assertEqual(spy_fitness_function_holder.fitness_func_executions, 1) self.assertIsInstance(optimizer, BasicAnnealingOptimizer)
def test_multiple_population_add(self): class SpyFitnessFunc: def __init__(self): self.fitness_func_executions = 0 def fitness_func(self, bitstring): self.fitness_func_executions += 1 return 69.0 unused_spy_fitness_function_holder = SpyFitnessFunc() genome_factory = PassThroughGenomeFactory( Genome.new_default_genome( (5, ), unused_spy_fitness_function_holder.fitness_func)) convergence_criterion = PassThroughConvergenceCriterion(True) optimizer_builder = BasicOptimizerBuilder( (5, ), NumpyRNG(), genome_factory, convergence_criterion, unused_spy_fitness_function_holder.fitness_func) spy_fitness_function_holder = SpyFitnessFunc() population_genome = Genome(spy_fitness_function_holder.fitness_func, (0, 0, 0, 0, 0)) optimizer = optimizer_builder \ .add_to_initial_population(population_genome) \ .build() self.assertAlmostEqual(optimizer.best_genome.fitness, 69.0) self.assertEqual(spy_fitness_function_holder.fitness_func_executions, 1) self.assertIsInstance(optimizer, BasicOptimizer) with self.assertRaises(InitialPopulationUndefinedException): optimizer_builder.clear().build()
def test_convergence(self): def fitness_func_zero(bitstring): return 0 initial_candidate = Genome(fitness_func_zero, (0,)) convergence_criterion = PassThroughConvergenceCriterion(True) genome_builder = PassThroughGenomeFactory(Genome(fitness_func_zero, (1,))) optimizer = BasicOptimizer(initial_candidate, genome_builder, convergence_criterion) self.assertFalse(optimizer.converged) optimizer.next() self.assertTrue(optimizer.converged)
def test_add_to_initial_population_returns_self(self): def fitness_func(bitstring): self.fail() return 0.0 genome_factory = PassThroughGenomeFactory( Genome.new_default_genome((5, ), fitness_func)) annealing_schedule = PassThroughAnnealingSchedule(0, True) optimizer_builder = BasicAnnealerOptimizerBuilder( (5, ), NumpyRNG(), genome_factory, annealing_schedule, fitness_func) returned_object = optimizer_builder.add_to_initial_population( Genome.new_default_genome((5, ), fitness_func)) self.assertEqual(optimizer_builder, returned_object)
def test_add_to_initial_population_returns_self(self): def fitness_func(bitstring): self.fail() return 0.0 genome_factory = PassThroughGenomeFactory( Genome.new_default_genome((5, ), fitness_func)) convergence_criterion = PassThroughConvergenceCriterion(True) optimizer_builder = BasicOptimizerBuilder( (5, ), NumpyRNG(), genome_factory, convergence_criterion, fitness_func) returned_object = optimizer_builder.add_to_initial_population( Genome.new_default_genome((5, ), fitness_func)) self.assertEqual(optimizer_builder, returned_object)
def test_next_generation_improvement(self): def fitness_func_zero(bitstring): return 0 def fitness_func_one(bitstring): return 1 initial_candidate = Genome(fitness_func_zero, (0,)) convergence_criterion = PassThroughConvergenceCriterion(True) genome_builder = PassThroughGenomeFactory(Genome(fitness_func_one, (1,))) optimizer = BasicOptimizer(initial_candidate, genome_builder, convergence_criterion) optimizer.next() self.assertEqual(1, optimizer.best_genome.fitness)
def build(self, prior_genomes: List[Genome]) -> Genome: """ Builds a new Genome from a list of priors :param prior_genomes: list of prior Genomes :return: a new Genome object """ bitstring = self._rng.random_int_array(0, 1, self._dimensions) return Genome(self._fitness_func, bitstring, *self._args, **self._kwargs)
def new_simulated_annealer( dimensions: Tuple, max_neighbor_distance: int, initial_temperature: float, minimum_temperature: float, degradation_multiplier: float, fitness_func: Callable[..., float], *args, **kwargs) -> AbstractOptimizer: """ Creates a new optimizer that optimizes by simulated annealing using a default temperature schedule. :param dimensions: dimension of bitstrings passed into fitness_func :param max_neighbor_distance: Manhattan distance allowed between neighbors :param initial_temperature: starting temperature :param minimum_temperature: minimum temperature before convergence :param degradation_multiplier: temperature will be multiplied by this every iteration :param fitness_func: function to optimize (accepts a bitstring) :param args: will be passed into fitness_func :param kwargs: will be passed into fitness_func :return: a new optimizer """ random = NumpyRNG() neighbor_genome_factory = \ RandomNeighborGenomeFactory( dimensions, random, max_neighbor_distance, fitness_func, *args, **kwargs) annealing_schedule = \ ExponentialAnnealingSchedule( initial_temperature, minimum_temperature, degradation_multiplier) initial_candidate = \ Genome.new_default_genome( dimensions, fitness_func, *args, **kwargs) initial_candidate.run() return BasicAnnealingOptimizer( initial_candidate, neighbor_genome_factory, annealing_schedule, random)
def test_build_raises_exception_with_no_population(self): def fitness_func(bitstring): self.fail() return 0.0 genome_factory = PassThroughGenomeFactory( Genome.new_default_genome((5, ), fitness_func)) annealing_schedule = PassThroughAnnealingSchedule(0, True) optimizer_builder = BasicAnnealerOptimizerBuilder( (5, ), NumpyRNG(), genome_factory, annealing_schedule, fitness_func) with self.assertRaises(InitialPopulationUndefinedException): optimizer_builder.build()
def test_build_raises_exception_with_no_population(self): def fitness_func(bitstring): self.fail() return 0.0 genome_factory = PassThroughGenomeFactory( Genome.new_default_genome((5, ), fitness_func)) convergence_criterion = PassThroughConvergenceCriterion(True) optimizer_builder = BasicOptimizerBuilder( (5, ), NumpyRNG(), genome_factory, convergence_criterion, fitness_func) with self.assertRaises(InitialPopulationUndefinedException): optimizer_builder.build()
def test_add_to_initial_population_from_factory_successful(self): def fitness_func(bitstring): return 69.0 genome_factory = PassThroughGenomeFactory( Genome.new_default_genome((5, ), fitness_func)) annealing_schedule = PassThroughAnnealingSchedule(0, True) optimizer_builder = BasicAnnealerOptimizerBuilder( (5, ), NumpyRNG(), genome_factory, annealing_schedule, fitness_func) optimizer = optimizer_builder \ .add_to_initial_population_from_factory(genome_factory, 1) \ .build() self.assertAlmostEqual(optimizer.best_genome.fitness, 69.0) self.assertIsInstance(optimizer, BasicAnnealingOptimizer)
def add_to_initial_population_from_factory( self, genome_factory: AbstractGenomeFactory, n: int) -> AbstractOptimizerBuilder: """ Adds a given number of Genomes to the initial population of this optimizer from a factory :param genome_factory: the factory that will generate the Genome :param n: number of instances to add :return: self """ base_genome = Genome.new_default_genome(genome_factory.dimensions, self._fitness_func, *self._args, **self._kwargs) self._population += [ genome_factory.build([base_genome]) for _ in range(n) ] return self
def test_add_to_initial_population_from_factory_successful(self): def fitness_func(bitstring): return 69.0 genome_factory = PassThroughGenomeFactory( Genome.new_default_genome((5, ), fitness_func)) convergence_criterion = PassThroughConvergenceCriterion(True) optimizer_builder = BasicOptimizerBuilder( (5, ), NumpyRNG(), genome_factory, convergence_criterion, fitness_func) optimizer = optimizer_builder \ .add_to_initial_population_from_factory(genome_factory, 1) \ .build() self.assertAlmostEqual(optimizer.best_genome.fitness, 69.0) self.assertIsInstance(optimizer, BasicOptimizer)
def new_hill_climber( dimensions: Tuple, convergence_iterations: int, epsilon: float, fitness_func: Callable[..., float], *args, **kwargs) -> AbstractOptimizer: """ Builds a new optimizer that generates and evaluates a candidate, finds a neighbor of this candidate, and determines if the neighbor is more suitable than the original candidate. :param dimensions: dimension of bitstrings passed into fitness_func :param convergence_iterations: number candidates to evaluate without improvement before declaring convergence :param epsilon: minimum required improvement between candidates to continue :param fitness_func: function to optimize (accepts a bitstring) :param args: will be passed into fitness_func :param kwargs: will be passed into fitness_func :return: a new optimizer """ random = NumpyRNG() neighbor_genome_factory = \ RandomNeighborGenomeFactory( dimensions, random, 1, fitness_func, *args, **kwargs) initial_candidate = \ Genome.new_default_genome( dimensions, fitness_func, *args, **kwargs) initial_candidate.run() convergence_criterion = ConsecutiveNonImprovement(convergence_iterations, epsilon) return BasicOptimizer( initial_candidate, neighbor_genome_factory, convergence_criterion)
def test_returns_best_model(self): class CustomRandom(AbstractRandomNumberGenerator): def random_int_array(self, minimum, maximum, shape): return [0] def random(self): return 0.0 rng = CustomRandom() comparer = RouletteWheelComparer(rng) genomes = [Genome.new_default_genome((10-index,), lambda bitstring: len(bitstring)) for index in range(10)] for genome in genomes: genome.run() returned_model = comparer.compare(genomes) self.assertEqual(10, returned_model.fitness)