From e5a130e22541307f0277dbb58e0af095fa319fd2 Mon Sep 17 00:00:00 2001 From: d-soni Date: Sun, 18 Jun 2017 00:15:29 -0700 Subject: [PATCH] added docstring comments --- library/EvolutionaryAlgorithm.py | 62 +++++++++++++++++++++++++++++++ library/GeneticAlgorithm.py | 63 ++++++++++++++++++++++++++++++++ library/HarmonySearch.py | 49 +++++++++++++++++++++++++ library/ParticleSwarm.py | 59 ++++++++++++++++++++++++++++-- library/SimulatedAnnealing.py | 40 ++++++++++++++++++++ library/StochasticHillClimb.py | 38 +++++++++++++++++++ library/TabuSearch.py | 47 +++++++++++++++++++++++- 7 files changed, 353 insertions(+), 5 deletions(-) diff --git a/library/EvolutionaryAlgorithm.py b/library/EvolutionaryAlgorithm.py index 9de9987..f3afc20 100644 --- a/library/EvolutionaryAlgorithm.py +++ b/library/EvolutionaryAlgorithm.py @@ -4,6 +4,9 @@ class EvolutionaryAlgorithm: + """ + Conducts evolutionary algorithm + """ __metaclass__ = ABCMeta population = None @@ -21,6 +24,13 @@ class EvolutionaryAlgorithm: max_fitness = None def __init__(self, crossover_rate, mutation_rate, max_steps, max_fitness=None): + """ + + :param crossover_rate: probability of crossover + :param mutation_rate: probability of mutation + :param max_steps: maximum steps to run genetic algorithm for + :param max_fitness: fitness value to stop algorithm once reached + """ if isinstance(crossover_rate, float): if crossover_rate >= 0 and crossover_rate <= 1: self.crossover_rate = crossover_rate @@ -59,6 +69,11 @@ def __repr__(self): return self.__str__() def _clear(self): + """ + Resets the variables that are altered on a per-run basis of the algorithm + + :return: None + """ self.cur_steps = 0 self.population = None self.fitnesses = None @@ -67,16 +82,37 @@ def _clear(self): @abstractmethod def _initial_population(self): + """ + Generates initial population + + :return: list of members of population + """ pass @abstractmethod def _fitness(self, member): + """ + Evaluates fitness of a given member + + :param member: a member + :return: fitness of member + """ pass def _populate_fitness(self): + """ + Calculates fitness of all members of current population + + :return: None + """ self.fitnesses = list([self._fitness(x) for x in self.population]) def _most_fit(self): + """ + Finds most fit member of current population + + :return: most fit member and most fit member's fitness + """ best_idx = 0 cur_idx = 0 for x in self.fitnesses: @@ -86,6 +122,13 @@ def _most_fit(self): return self.population[best_idx], self.fitnesses[best_idx] def _select_n(self, n): + """ + Probabilistically selects n members from current population using + roulette-wheel selection + + :param n: number of members to select + :return: n members + """ shuffle(self.population) total_fitness = sum(self.fitnesses) probs = list([self._fitness(x) / total_fitness for x in self.population]) @@ -101,13 +144,32 @@ def _select_n(self, n): @abstractmethod def _crossover(self, parent1, parent2): + """ + Creates new member of population by combining two parent members + + :param parent1: a member + :param parent2: a member + :return: member made by combining elements of both parents + """ pass @abstractmethod def _mutate(self, member): + """ + Randomly mutates a member + + :param member: a member + :return: mutated member + """ pass def evolutionary_algorithm(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() diff --git a/library/GeneticAlgorithm.py b/library/GeneticAlgorithm.py index d9ca3a1..eea9fae 100644 --- a/library/GeneticAlgorithm.py +++ b/library/GeneticAlgorithm.py @@ -4,6 +4,9 @@ class GeneticAlgorithm: + """ + Conducts genetic algorithm + """ __metaclass__ = ABCMeta population = None @@ -21,6 +24,13 @@ class GeneticAlgorithm: max_fitness = None def __init__(self, crossover_rate, mutation_rate, max_steps, max_fitness=None): + """ + + :param crossover_rate: probability of crossover + :param mutation_rate: probability of mutation + :param max_steps: maximum steps to run genetic algorithm for + :param max_fitness: fitness value to stop algorithm once reached + """ if isinstance(crossover_rate, float): if crossover_rate >= 0 and crossover_rate <= 1: self.crossover_rate = crossover_rate @@ -59,6 +69,11 @@ def __repr__(self): return self.__str__() def _clear(self): + """ + Resets the variables that are altered on a per-run basis of the algorithm + + :return: None + """ self.cur_steps = 0 self.population = None self.fitnesses = None @@ -67,16 +82,38 @@ def _clear(self): @abstractmethod def _initial_population(self): + """ + Generates initial population - + members must be represented by a list of binary-values integers + + :return: list of members of population + """ pass @abstractmethod def _fitness(self, member): + """ + Evaluates fitness of a given member + + :param member: a member + :return: fitness of member + """ pass def _populate_fitness(self): + """ + Calculates fitness of all members of current population + + :return: None + """ self.fitnesses = list([self._fitness(x) for x in self.population]) def _most_fit(self): + """ + Finds most fit member of current population + + :return: most fit member and most fit member's fitness + """ best_idx = 0 cur_idx = 0 for x in self.fitnesses: @@ -86,6 +123,13 @@ def _most_fit(self): return self.population[best_idx], self.fitnesses[best_idx] def _select_n(self, n): + """ + Probabilistically selects n members from current population using + roulette-wheel selection + + :param n: number of members to select + :return: n members + """ shuffle(self.population) total_fitness = sum(self.fitnesses) probs = list([self._fitness(x) / total_fitness for x in self.population]) @@ -100,15 +144,34 @@ def _select_n(self, n): return res def _crossover(self, parent1, parent2): + """ + Creates new member of population by combining two parent members + + :param parent1: a member + :param parent2: a member + :return: member made by combining elements of both parents + """ partition = randint(0, len(self.population[0] - 1)) return parent1[0:partition] + parent2[partition:] def _mutate(self, member): + """ + Randomly mutates a member + + :param member: a member + :return: mutated member + """ if self.mutation_rate >= random(): idx = randint(0, len(member) - 1) member[idx] = 1 if member[idx] == 0 else 1 def genetic_algorithm(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() diff --git a/library/HarmonySearch.py b/library/HarmonySearch.py index 2954ddd..b3bf4d5 100644 --- a/library/HarmonySearch.py +++ b/library/HarmonySearch.py @@ -4,6 +4,9 @@ class HarmonySearch: + """ + Conducts harmony search + """ __metaclass__ = ABCMeta cur_steps = None @@ -21,6 +24,15 @@ class HarmonySearch: max_score = None def __init__(self, hms, hmcr, par, fw, max_steps, max_score=None): + """ + + :param hms: harmony memory size + :param hmcr: harmony memory considering rate + :param par: pitch adjustment rate + :param fw: fret width + :param max_steps: maximum number of steps to run algorithm for + :param max_score: objective function value to stop algorithm once reached + """ if isinstance(hms, int) and hms > 0: self.hms = hms else: @@ -63,28 +75,65 @@ def __repr__(self): return self.__str__() def _clear(self): + """ + Resets the variables that are altered on a per-run basis of the algorithm + + :return: None + """ self.cur_steps = 0 self.memory = list([self._random_harmony() for _ in range(self.hms)]) self.scores = None @abstractmethod def _random_harmony(self): + """ + Generates a list of random harmonies, each represented as a list of floats + + :return: list of harmonies + """ pass @abstractmethod def _score(self, harmony): + """ + Returns score of a harmony + + :param harmony: a harmony + :return: score of harmony + """ pass def _score_all(self): + """ + Finds score of all current harmonies in memory + + :return: None + """ self.scores = list([self._score(x) for x in self.memory]) def _worst_score(self): + """ + Returns index of worst harmony in memory + + :return: index of worst harmony in memory + """ return argmin(self.scores) def _best_score(self): + """ + Returns index of best harmony in memory + + :return: index of best harmony in memory + """ return argmax(self.scores) def harmony_search(self, verbose=True): + """ + Conducts harmony search + + :param verbose: indicates whether or not to print progress regularly + :return: best state and objective function value of best state + """ self._clear() for i in range(self.max_steps): self.cur_steps += 1 diff --git a/library/ParticleSwarm.py b/library/ParticleSwarm.py index ec9ac1b..7a15a90 100644 --- a/library/ParticleSwarm.py +++ b/library/ParticleSwarm.py @@ -1,10 +1,13 @@ from abc import ABCMeta, abstractmethod from random import random -from numpy import apply_along_axis, argmin, array, copy, dot, fill_diagonal, zeros +from numpy import apply_along_axis, argmin, array, copy, diag_indices_from, dot, zeros from numpy.random import uniform class ParticleSwarm: + """ + Conducts particle swarm optimization + """ __metaclass__ = ABCMeta swarm_size = None @@ -28,6 +31,18 @@ class ParticleSwarm: def __init__(self, swarm_size, member_size, lower_bound, upper_bound, c1, c2, c3, max_steps, min_objective=None): + """ + + :param swarm_size: number of members in swarm + :param member_size: number of components per member vector + :param lower_bound: list of lower bounds, where ith element is ith lower bound + :param upper_bound: list of upper bounds, where ith element is ith upper bound + :param c1: constant for 1st term in velocity calculation + :param c2: contsant for 2nd term in velocity calculation + :param c3: constant for 3rd term in velocity calculation + :param max_steps: maximum steps to run algorithm for + :param min_objective: objective function value to stop algorithm once reached + """ if isinstance(swarm_size, int) and swarm_size > 0: self.swarm_size = swarm_size else: @@ -83,6 +98,11 @@ def __repr__(self): return self.__str__() def _clear(self): + """ + Resets the variables that are altered on a per-run basis of the algorithm + + :return: None + """ self.pos = uniform(self.lower_bound, self.upper_bound, size=(self.swarm_size, self.member_size)) self.vel = uniform(self.upper_bound - self.lower_bound, self.lower_bound - self.upper_bound, size=(self.swarm_size, self.member_size)) self.scores = self._score(self.pos) @@ -91,12 +111,32 @@ def _clear(self): @abstractmethod def _objective(self, member): + """ + Returns objective function value for a member of swarm - + operates on 1D numpy array + + :param member: a member + :return: objective function value of member + """ pass def _score(self, pos): + """ + Applies objective function to a member of swarm + + :param pos: + :return: + """ return apply_along_axis(self._objective, 1, pos) def _best(self, old, new): + """ + Finds the best objective function values for each member of swarm + + :param old: old values + :param new: new values + :return: None + """ old_score = self._score(old) new_score = self._score(new) best = [] @@ -108,10 +148,21 @@ def _best(self, old, new): self.best = array(best) def _global_best(self): + """ + Finds the global best across swarm + + :return: None + """ 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): + """ + Conducts particle swarm optimization + + :param verbose: indicates whether or not to print progress regularly + :return: best member of swarm and objective function value of best member of swarm + """ self._clear() for i in range(self.max_steps): self.cur_steps += 1 @@ -119,8 +170,10 @@ def swarm(self, verbose=True): if (i % 100 == 0) and verbose: print self - u1 = fill_diagonal(zeros((self.swarm_size, self.swarm_size)), random()) - u2 = fill_diagonal(zeros((self.swarm_size, self.swarm_size)), random()) + u1 = zeros((self.swarm_size, self.swarm_size)) + u1[diag_indices_from(u1)] = [random() for x in range(self.swarm_size)] + u2 = zeros((self.swarm_size, self.swarm_size)) + u2[diag_indices_from(u2)] = [random() for x in range(self.swarm_size)] vel_new = (self.c1 * self.vel) + \ (self.c2 * dot(u1, (self.best - self.pos))) + \ diff --git a/library/SimulatedAnnealing.py b/library/SimulatedAnnealing.py index fa0f082..3a68656 100644 --- a/library/SimulatedAnnealing.py +++ b/library/SimulatedAnnealing.py @@ -5,6 +5,9 @@ class SimulatedAnnealing: + """ + Conducts simulated annealing algorithm + """ __metaclass__ = ABCMeta initial_state = None @@ -38,6 +41,15 @@ def _get_schedule(self, schedule_str, schedule_constant): def __init__(self, initial_state, max_steps, temp_begin, schedule_constant, min_energy=None, schedule='exponential'): + """ + + :param initial_state: initial state of annealing algorithm + :param max_steps: maximum number of iterations to conduct annealing for + :param temp_begin: beginning temperature + :param schedule_constant: constant value in annealing schedule function + :param min_energy: energy value to stop algorithm once reached + :param schedule: 'exponential' or 'linear' annealing schedule + """ self.initial_state = initial_state if isinstance(max_steps, int) and max_steps > 0: @@ -70,6 +82,11 @@ def __repr__(self): return self.__str__() def _clear(self): + """ + Resets the variables that are altered on a per-run basis of the algorithm + + :return: None + """ self.cur_steps = 0 self.current_state = None self.best_state = None @@ -78,17 +95,40 @@ def _clear(self): @abstractmethod def _neighbor(self): + """ + Returns a random member of the neighbor of the current state + + :return: a random neighbor, given access to self.current_state + """ pass @abstractmethod def _energy(self, state): + """ + Finds the energy of a given state + + :param state: a state + :return: energy of state + """ pass def _accept_neighbor(self, neighbor): + """ + Probabilistically determines whether or not to accept a transition to a neighbor + + :param neighbor: a state + :return: boolean indicating whether or not transition is accepted + """ 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): + """ + Conducts simulated annealing + + :param verbose: indicates whether or not to print progress regularly + :return: best state and best energy + """ self._clear() self.current_state = self.initial_state self.current_temp = self.start_temp diff --git a/library/StochasticHillClimb.py b/library/StochasticHillClimb.py index f5082d5..50878e7 100644 --- a/library/StochasticHillClimb.py +++ b/library/StochasticHillClimb.py @@ -5,6 +5,9 @@ class StochasticHillClimb: + """ + Conducts stochastic hill climb + """ __metaclass__ = ABCMeta initial_state = None @@ -20,6 +23,13 @@ class StochasticHillClimb: temp = None def __init__(self, initial_state, max_steps, temp, max_objective=None): + """ + + :param initial_state: initial state of hill climbing + :param max_steps: maximum steps to run hill climbing for + :param temp: temperature in probabilistic acceptance of transition + :param max_objective: objective function to stop algorithm once reached + """ self.initial_state = initial_state if isinstance(max_steps, int) and max_steps > 0: @@ -49,6 +59,11 @@ def __repr__(self): return self.__str__() def _clear(self): + """ + Resets the variables that are altered on a per-run basis of the algorithm + + :return: None + """ self.cur_steps = 0 self.current_state = None self.best_state = None @@ -56,17 +71,40 @@ def _clear(self): @abstractmethod def _neighbor(self): + """ + Returns a random member of the neighbor of the current state + + :return: a random neighbor, given access to self.current_state + """ pass @abstractmethod def _objective(self, state): + """ + Evaluates a given state + + :param state: a state + :return: objective function value of state + """ pass def _accept_neighbor(self, neighbor): + """ + Probabilistically determines whether or not to accept a transition to a neighbor + + :param neighbor: a state + :return: boolean indicating whether or not transition was accepted + """ 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): + """ + Conducts simulated annealing + + :param verbose: indicates whether or not to print progress regularly + :return: best state and best objective function value + """ self._clear() self.current_state = self.initial_state for i in range(self.max_steps): diff --git a/library/TabuSearch.py b/library/TabuSearch.py index 5fdbe9e..fef449e 100644 --- a/library/TabuSearch.py +++ b/library/TabuSearch.py @@ -5,6 +5,9 @@ from numpy import argmax class TabuSearch: + """ + Conducts tabu search + """ __metaclass__ = ABCMeta cur_steps = None @@ -15,14 +18,26 @@ class TabuSearch: current = None best = None + max_steps = None max_score = None - def __init__(self, tabu_size, max_score=None): + def __init__(self, tabu_size, max_steps, max_score=None): + """ + + :param tabu_size: number of states to keep in tabu list + :param max_steps: maximum number of steps to run algorithm for + :param max_score: score to stop algorithm once reached + """ if isinstance(tabu_size, int) and tabu_size > 0: self.tabu_size = tabu_size else: raise TypeError('Tabu size must be a positive integer') + if isinstance(max_steps, int) and max_steps > 0: + self.max_steps = max_steps + else: + raise TypeError('Maximum steps must be a positive integer') + if max_score is not None: if isinstance(max_score, (int, float)): self.max_score = float(max_score) @@ -40,23 +55,51 @@ def __repr__(self): return self.__str__() def _clear(self): + """ + Resets the variables that are altered on a per-run basis of the algorithm + + :return: None + """ self.cur_steps = 0 self.tabu_list = deque(maxlen=self.tabu_size) self.current = None self.best = None @abstractmethod - def _score(self): + def _score(self, state): + """ + Returns objective function value of a state + + :param state: a state + :return: objective function value of state + """ pass @abstractmethod def _neighborhood(self): + """ + Returns list of all members of neighborhood of current state + + :return: list of members of neighborhood + """ pass def _best(self, neighborhood): + """ + Finds the best member of a neighborhood + + :param neighborhood: a neighborhood + :return: best member of neighborhood + """ return neighborhood[argmax([self._score(x) for x in neighborhood])] def tabu_search(self, verbose=True): + """ + Conducts tabu search + + :param verbose: indicates whether or not to print progress regularly + :return: best state and objective function value of best state + """ self._clear() for i in range(self.max_steps): self.cur_steps += 1