class Serial(AlgorithmBase): """ The serial implementation of NSGA-II """ def __init__(self): """ Initialize the Serial object @return: None """ super(Serial, self).__init__() def initialize(self): """ Randomly initialize a population @return: The initialize population @rtype: list(Individual) """ self._logger.info('Initializing the population') pop = [] # Randomly initialize the population for i in xrange(self._pop_size): ind = Individual() ind.id = i x_set = [None]*self._ndim for j in xrange(self._ndim): x_set[j] = self._rnd.uniform(self._lower_bound[j], self._upper_bound[j]) ind.x = x_set pop.append(ind) return pop def run(self): """ Run the optimization algorithm @return: The history from the run @rtype: History """ # Initialize the population parent_pop = self.initialize() # Evaluate the population self.evaluate_population(parent_pop) # Perform selection to get the crowding distances parent_pop = self.selection(parent_pop, self._pop_size) pareto_front = self.nondominated_sort(parent_pop, len(parent_pop), first_front_only=True) self._converger = Converger(pareto_front[0], self._pop_size, self._true_pareto_front) # Initialize the archive if self._write_history is True: self._history.add_population(parent_pop) gen = 0 converged = 0 convergence_history = [] while (gen < self._generations) and converged < 10: self._logger.info('Starting generation ' + repr(gen+1) + ' of ' + repr(self._generations)) # Apply crossover to generate a child population child_pop = self.tournament_select(parent_pop, self._pop_size)[:] # Apply crossover and mutation for ind1, ind2 in zip(child_pop[::2], child_pop[1::2]): if self._rnd.random() <= self._p_cross: self.mate(ind1, ind2) self.mutate(ind1) self.mutate(ind2) # Evaluate the child population self.evaluate_population(child_pop) # Update ID's for ind in child_pop: ind.id += self._pop_size # Perform NSGA-2 selection to pick the next generation parent_pop = self.selection(parent_pop + child_pop, self._pop_size) pareto_front = self.nondominated_sort(parent_pop, len(parent_pop), first_front_only=True) self._history.add_population(parent_pop) self._logger.info('Pareto Front Size: ' + repr(len(pareto_front[0]))) # Check convergence convergence_metrics = \ self._converger.check_convergence(pareto_front[0], self._pop_size) if self._write_history is True: convergence_history.append(convergence_metrics) self._logger.info('Frontier Goodness: ' + repr(convergence_metrics.fg)) self._logger.info('Expansion Metric: ' + repr(convergence_metrics.expansion)) self._logger.info('Density Metric: ' + repr(convergence_metrics.dm)) self._logger.info('Distance Metric: ' + repr(convergence_metrics.distance)) self._logger.info('Volume Ratio: ' + repr(convergence_metrics.volume_ratio)) if self._conv_tol != 0: if max([convergence_metrics.fg, convergence_metrics.expansion, convergence_metrics.dm]) <= \ self._conv_tol: converged += 1 self._logger.info('Converged Generations: ' + repr(converged)) else: converged = 0 # Update gen += 1 # Final solution solution = self.nondominated_sort(parent_pop, len(parent_pop), first_front_only=True) return self._history, convergence_history, solution[0]
class Islands(AlgorithmBase): """ The Islands optimization algorithm. """ def __init__(self): """ Initialize the Islands algorithm. @return: None """ super(Islands, self).__init__() def initialize(self): """ Randomly initialize the island populations @return: The initialized islands @rtype: list(list(Individual)) """ self._logger.info('Initializing the islands') islands = [] # TODO (JLD): Use _global_popsize where appropriate self._global_popsize = self._islands * self._pop_size # Randomly initialize each sub-population for k in xrange(self._islands): pop = [] for i in xrange(self._pop_size): ind = Individual() ind.id = i x_set = [None]*self._ndim for j in xrange(self._ndim): x_set[j] = self._rnd.uniform(self._lower_bound[j], self._upper_bound[j]) ind.x = x_set pop.append(ind) islands.append(pop) return islands def run(self): """ Run the optimization algorithm @return: The history from the run @rtype: History """ # Initialize the islands islands = self.initialize() # Evaluate the individuals for island in islands: self.evaluate_population(island) # Perform selection to get the crowding distances parent_islands = [] pareto_fronts = [] for island in islands: parent_pop = self.selection(island, self._pop_size) parent_islands.append(parent_pop) # Get the global pareto front pareto_front = self.nondominated_sort(self.coalesce_populations(parent_islands), len(parent_islands)*self._pop_size, first_front_only=True) self._converger = Converger(pareto_front[0], len(pareto_front)) # Initialize the archive self._history.add_population(parent_islands) gen = 0 converged = 0 # TODO (JLD): Make epoch a configuration property epoch = 5 # TODO (JLD): Make number_of_migrants a configuration property number_of_migrants = 3 while (gen < self._generations) and converged < 10: self._logger.info('Starting generation ' + repr(gen+1) + ' of ' + repr(self._generations)) # Migration if (gen % epoch) == 0: self._logger.info('Epoch occurred, migrating...') self.migration(parent_islands, number_of_migrants) # Apply crossover to generate a child population child_islands = [] for parent_pop in parent_islands: child_pop = self.tournament_select(parent_pop, self._pop_size) child_islands.append(child_pop) # Apply crossover and mutation for child_pop in child_islands: for ind1, ind2 in zip(child_pop[::2], child_pop[1::2]): if self._rnd.random() <= self._p_cross: self.mate(ind1, ind2) self.mutate(ind1) self.mutate(ind2) # Evaluate the child population for child_pop in child_islands: self.evaluate_population(child_pop) # Update ID's for child_pop in child_islands: for ind in child_pop: ind.id += self._pop_size # Perform NSGA-2 selection to pick the next generation for parent_pop, child_pop in zip(parent_islands, child_islands): parent_pop = self.selection(parent_pop + child_pop, self._pop_size) pareto_front = self.nondominated_sort(self.coalesce_populations(parent_islands), len(parent_islands)*self._pop_size, first_front_only=True) for parent_pop in parent_islands: self._history.add_population(parent_pop) convergence_metrics = self._converger.check_convergence(pareto_front[0], self._pop_size*self._islands) self._logger.info('Frontier Goodness: ' + repr(convergence_metrics.fg)) self._logger.info('Expansion Metric: ' + repr(convergence_metrics.expansion)) self._logger.info('Density Metric: ' + repr(convergence_metrics.dm)) if max([convergence_metrics.fg, convergence_metrics.expansion, convergence_metrics.dm]) <= self._conv_tol: converged += 1 self._logger.info('Converged Generations: ' + repr(converged)) else: converged = 0 # Update gen += 1 # Final solution solution = [] for parent_pop in parent_islands: for ind in parent_pop: solution.append(ind) # TODO (JLD): Add convergence history, and solution to outputs return self._history, # TODO (JLD): Add in different migration schemes def migration(self, islands, number_of_migrants): """ Perform the migration operation in place on the sub populations @param islands: The sub populations to include in the migration @type islands: list(list(Individual)) @param number_of_migrants: The number of migrants @type number_of_migrants: int @return: None """ # Generate a migration pool, currently just selecting a random individual migrant_pool = [] for island in islands: for i in xrange(number_of_migrants): random_index = self._rnd.randrange(0, len(island)) migrant = island.pop(random_index) migrant_pool.append(migrant) # assign the migrants to an island for island in islands: for i in xrange(number_of_migrants): random_index = self._rnd.randrange(0, len(migrant_pool)) immigrant = migrant_pool.pop(random_index) island.append(immigrant)