From e2f866765585b827ed4fe244dbfd8795bdd87559 Mon Sep 17 00:00:00 2001 From: d-soni Date: Sun, 18 Jun 2017 17:34:38 -0700 Subject: [PATCH] begun adding tests and bug fixes --- library/EvolutionaryAlgorithm.py | 32 +++++++++++++++---------- library/GeneticAlgorithm.py | 33 ++++++++++++++++---------- library/HarmonySearch.py | 13 +++++----- library/ParticleSwarm.py | 2 +- library/SimulatedAnnealing.py | 2 +- library/StochasticHillClimb.py | 4 ++-- library/TabuSearch.py | 2 +- tests/{test_genetic.py => __init__.py} | 0 tests/test_evolutionary_algorithm.py | 30 +++++++++++++++++++++++ tests/test_genetic_algorithm.py | 18 ++++++++++++++ tests/test_harmony_search.py | 18 ++++++++++++++ 11 files changed, 117 insertions(+), 37 deletions(-) rename tests/{test_genetic.py => __init__.py} (100%) create mode 100644 tests/test_genetic_algorithm.py diff --git a/library/EvolutionaryAlgorithm.py b/library/EvolutionaryAlgorithm.py index f3afc20..a9a1591 100644 --- a/library/EvolutionaryAlgorithm.py +++ b/library/EvolutionaryAlgorithm.py @@ -1,6 +1,6 @@ from abc import ABCMeta, abstractmethod from copy import deepcopy -from random import randint, random, shuffle +from random import random, shuffle class EvolutionaryAlgorithm: @@ -131,15 +131,19 @@ def _select_n(self, n): """ shuffle(self.population) total_fitness = sum(self.fitnesses) - probs = list([self._fitness(x) / total_fitness for x in self.population]) + if total_fitness != 0: + probs = list([self._fitness(x) / total_fitness for x in self.population]) + else: + return self.population[0:n] res = [] - for _ in probs: + for _ in range(n): r = random() - sum = 0 + sum_ = 0 for i, x in enumerate(probs): - sum += probs[i] - if r < sum: - res.add(deepcopy(self.population[i])) + sum_ += probs[i] + if r <= sum_: + res.append(deepcopy(self.population[i])) + break return res @abstractmethod @@ -163,31 +167,33 @@ def _mutate(self, member): """ pass - def evolutionary_algorithm(self, verbose=True): + def run(self, verbose=True): """ Conducts evolutionary algorithm :param verbose: indicates whether or not to print progress regularly :return: best state and best objective function value """ - num_copy = int((1 - self.crossover_rate) * len(self.population)) - num_crossover = len(self.population) - num_copy self._clear() self.population = self._initial_population() + self._populate_fitness() + self.best_member, self.best_fitness = self._most_fit() + num_copy = max(int((1 - self.crossover_rate) * len(self.population)), 2) + num_crossover = len(self.population) - num_copy for i in range(self.max_steps): self.cur_steps += 1 - if (i % 100 == 0) and verbose: + if ((i + 1) % 100 == 0) and verbose: print self - self._populate_fitness() self.population = self._select_n(num_copy) + self._populate_fitness() parents = self._select_n(2) for _ in range(num_crossover): self.population.append(self._crossover(*parents)) - self.population = list([self.mutate(x) for x in self.population]) + self.population = list([self._mutate(x) for x in self.population]) self._populate_fitness() best_member, best_fitness = self._most_fit() diff --git a/library/GeneticAlgorithm.py b/library/GeneticAlgorithm.py index eea9fae..31c9f62 100644 --- a/library/GeneticAlgorithm.py +++ b/library/GeneticAlgorithm.py @@ -132,15 +132,19 @@ def _select_n(self, n): """ shuffle(self.population) total_fitness = sum(self.fitnesses) - probs = list([self._fitness(x) / total_fitness for x in self.population]) + if total_fitness != 0: + probs = list([self._fitness(x) / total_fitness for x in self.population]) + else: + return self.population[0:n] res = [] - for _ in probs: + for _ in range(n): r = random() - sum = 0 + sum_ = 0 for i, x in enumerate(probs): - sum += probs[i] - if r < sum: - res.add(deepcopy(self.population[i])) + sum_ += probs[i] + if r <= sum_: + res.append(deepcopy(self.population[i])) + break return res def _crossover(self, parent1, parent2): @@ -151,7 +155,7 @@ def _crossover(self, parent1, parent2): :param parent2: a member :return: member made by combining elements of both parents """ - partition = randint(0, len(self.population[0] - 1)) + partition = randint(0, len(self.population[0]) - 1) return parent1[0:partition] + parent2[partition:] def _mutate(self, member): @@ -164,32 +168,35 @@ def _mutate(self, member): if self.mutation_rate >= random(): idx = randint(0, len(member) - 1) member[idx] = 1 if member[idx] == 0 else 1 + return member - def genetic_algorithm(self, verbose=True): + def run(self, verbose=True): """ Conducts genetic algorithm :param verbose: indicates whether or not to print progress regularly :return: best state and best objective function value """ - num_copy = int((1 - self.crossover_rate) * len(self.population)) - num_crossover = len(self.population) - num_copy self._clear() self.population = self._initial_population() + self._populate_fitness() + self.best_member, self.best_fitness = self._most_fit() + num_copy = max(int((1 - self.crossover_rate) * len(self.population)), 2) + num_crossover = len(self.population) - num_copy for i in range(self.max_steps): self.cur_steps += 1 - if (i % 100 == 0) and verbose: + if ((i + 1) % 100 == 0) and verbose: print self - self._populate_fitness() self.population = self._select_n(num_copy) + self._populate_fitness() parents = self._select_n(2) for _ in range(num_crossover): self.population.append(self._crossover(*parents)) - self.population = list([self.mutate(x) for x in self.population]) + self.population = list([self._mutate(x) for x in self.population]) self._populate_fitness() best_member, best_fitness = self._most_fit() diff --git a/library/HarmonySearch.py b/library/HarmonySearch.py index b3bf4d5..4551591 100644 --- a/library/HarmonySearch.py +++ b/library/HarmonySearch.py @@ -87,7 +87,7 @@ def _clear(self): @abstractmethod def _random_harmony(self): """ - Generates a list of random harmonies, each represented as a list of floats + Generates a random harmony, represented as a list of floats :return: list of harmonies """ @@ -127,7 +127,7 @@ def _best_score(self): """ return argmax(self.scores) - def harmony_search(self, verbose=True): + def run(self, verbose=True): """ Conducts harmony search @@ -135,15 +135,16 @@ def harmony_search(self, verbose=True): :return: best state and objective function value of best state """ self._clear() + self._score_all() for i in range(self.max_steps): self.cur_steps += 1 - if (i % 100 == 0) and verbose: + if ((i + 1) % 100 == 0) and verbose: print self self._score_all() - selected = [] * len(self.memory[0]) + selected = [0.] * len(self.memory[0]) for i in range(len(selected)): if self.hmcr >= random(): selected_component = choice(self.memory)[i] @@ -151,9 +152,9 @@ def harmony_search(self, verbose=True): selected_component += uniform(-1, 1) * self.fw else: selected_component = self._random_harmony()[i] - selected.append(selected_component) + selected[i] = selected_component - if self._score(selected_component) > self.score(self._worst_score()): + if self._score(selected) > self._score(self.memory[self._worst_score()]): self.memory[self._worst_score()] = selected self.scores[self._worst_score()] = self._score(selected) diff --git a/library/ParticleSwarm.py b/library/ParticleSwarm.py index 7a15a90..5d47cc2 100644 --- a/library/ParticleSwarm.py +++ b/library/ParticleSwarm.py @@ -156,7 +156,7 @@ def _global_best(self): if min(self.scores) < self.global_best[0][0]: self.global_best = array([self.pos[argmin(self.scores)],] * self.swarm_size) - def swarm(self, verbose=True): + def run(self, verbose=True): """ Conducts particle swarm optimization diff --git a/library/SimulatedAnnealing.py b/library/SimulatedAnnealing.py index 3a68656..4674f3f 100644 --- a/library/SimulatedAnnealing.py +++ b/library/SimulatedAnnealing.py @@ -122,7 +122,7 @@ def _accept_neighbor(self, neighbor): p = exp(self._energy(self.current_state) - self._energy(neighbor)) / self.current_temp return True if p >= 1 else p >= random() - def anneal(self, verbose=True): + def run(self, verbose=True): """ Conducts simulated annealing diff --git a/library/StochasticHillClimb.py b/library/StochasticHillClimb.py index 50878e7..6afb6fa 100644 --- a/library/StochasticHillClimb.py +++ b/library/StochasticHillClimb.py @@ -98,9 +98,9 @@ def _accept_neighbor(self, neighbor): p = 1. / (1 + (exp(self._objective(self.current_state) - self._objective(neighbor)) / self.temp)) return True if p >= 1 else p >= random() - def anneal(self, verbose=True): + def run(self, verbose=True): """ - Conducts simulated annealing + Conducts hill climb :param verbose: indicates whether or not to print progress regularly :return: best state and best objective function value diff --git a/library/TabuSearch.py b/library/TabuSearch.py index fef449e..e9ad0f8 100644 --- a/library/TabuSearch.py +++ b/library/TabuSearch.py @@ -93,7 +93,7 @@ def _best(self, neighborhood): """ return neighborhood[argmax([self._score(x) for x in neighborhood])] - def tabu_search(self, verbose=True): + def run(self, verbose=True): """ Conducts tabu search diff --git a/tests/test_genetic.py b/tests/__init__.py similarity index 100% rename from tests/test_genetic.py rename to tests/__init__.py diff --git a/tests/test_evolutionary_algorithm.py b/tests/test_evolutionary_algorithm.py index e69de29..65ed334 100644 --- a/tests/test_evolutionary_algorithm.py +++ b/tests/test_evolutionary_algorithm.py @@ -0,0 +1,30 @@ +from random import choice, randint, random +from string import lowercase +from library.EvolutionaryAlgorithm import EvolutionaryAlgorithm + + +class Algorithm(EvolutionaryAlgorithm): + """ + Tries to get a randomly-generated string to match string "clout" + """ + def _initial_population(self): + return list(''.join([choice(lowercase) for _ in range(5)]) for _ in range(50)) + + def _fitness(self, member): + return float(sum(member[i] == "clout"[i] for i in range(5))) + + def _crossover(self, parent1, parent2): + partition = randint(0, len(self.population[0]) - 1) + return parent1[0:partition] + parent2[partition:] + + def _mutate(self, member): + if self.mutation_rate >= random(): + member = list(member) + member[randint(0,4)] = choice(lowercase) + member = ''.join(member) + return member + + +def test_algorithm(): + algorithm = Algorithm(.5, .7, 500, max_fitness=None) + algorithm.run() diff --git a/tests/test_genetic_algorithm.py b/tests/test_genetic_algorithm.py new file mode 100644 index 0000000..ec8794e --- /dev/null +++ b/tests/test_genetic_algorithm.py @@ -0,0 +1,18 @@ +from random import choice +from library.GeneticAlgorithm import GeneticAlgorithm + + +class Algorithm(GeneticAlgorithm): + """ + Tries to get a randomly-generated string to match 000111 + """ + def _initial_population(self): + return list(list([choice([0, 1]) for _ in range(6)]) for _ in range(50)) + + def _fitness(self, member): + return float(sum(member[i] == [0,0,0,1,1,1][i] for i in range(6))) + + +def test_algorithm(): + algorithm = Algorithm(.5, .7, 500, max_fitness=None) + algorithm.run() diff --git a/tests/test_harmony_search.py b/tests/test_harmony_search.py index e69de29..0190f7b 100644 --- a/tests/test_harmony_search.py +++ b/tests/test_harmony_search.py @@ -0,0 +1,18 @@ +from random import uniform +from library.HarmonySearch import HarmonySearch + + +class Algorithm(HarmonySearch): + """ + Tries to get a randomly-generated list to match [.1, .2, .3, .2, .1] + """ + def _random_harmony(self): + return list([uniform(0, 1) for _ in range(5)]) + + def _score(self, member): + return 1./ sum(abs(member[i] - [.1, .2, .3, .2, .1][i]) for i in range(5)) + + +def test_algorithm(): + algorithm = Algorithm(50, .5, .3, .01, 2000, max_score=None) + algorithm.run()