Source code for fevovaq.BigBangBigCrunch

from .problem import Problem
from .tools.support import compute_statistics, print_info, Logbook, set_progress_bar, BestIndividualTracker, \
    FinalResult




[docs] class BBBC(object): """ The Big Bang - Big Crunch (BBBC) method as developed by Erol and Eks in 2006 [1] consists of two alternating steps: 1) a Big Bang phase, where the candidate solutions are randomly distributed over the search space; 2) a Big Crunch phase, where a contraction operation estimates a weighted average, denoted as Centre of Mass, of the randomly distributed candidate solutions. During the Big Bang phases, new candidate solutions are generated considering the Center of Mass and the best global solution, as introduced in [2]. References: [1] O. K. Erol and I. Eksin, “A new optimization method: big bang–big crunch”, Advances in Engineering Software, vol. 37, no. 2, pp. 106– 111, 2006. [2] C. V. Camp, “Design of space trusses using big bang–big crunch optimization,” Journal of Structural Engineering, vol. 133, no. 7, pp. 999–1008, 2007. Args: elitism: If True, the best solution of current population is transferred directly into the next generation. alpha: Hyperparameter limiting the size of the search space. beta: Hyperparameter defined in range (0,1) controlling the influence of the best individual on the location of new candidate solutions. """ def __init__( self, elitism: bool = True, alpha: float = 10.0, beta: float = 0.25 ): self.elitism = elitism self.alpha = alpha self.beta = beta @staticmethod def _compute_centre_of_mass(population, fitness, xp): """ Compute the centre of mass. """ return xp.sum(population / fitness[:, None], axis=0) / xp.sum(1 / fitness)
[docs] def evolve_population(self, problem, population, fitness, gen): """ Evolve the population by means of genetic operators. Args: problem : :class:`~.Problem` to be solved. population: A population of individuals as array of real parameters with (`pop_size`, `n_params`) shape. fitness: A set of fitness values associated to the population as array of real values with (`pop_size`, ) shape. gen: Current generation number. Returns: The offspring and fitness values obtained after evolution, and number of fitness evaluations completed during the evolution. """ xp = problem.xp pop_size, dim = population.shape best_idx = xp.argmin(fitness) best = population[best_idx] com = self._compute_centre_of_mass(population, fitness, xp) diff = problem.bounds_max - problem.bounds_min r = xp.random.normal(0, 1, size=(pop_size, dim)) offspring = self.beta * com + (1 - self.beta) * best + diff * r * self.alpha / gen problem.check_bounds(offspring) fit_offspring = problem.evaluate_fitness(offspring) return offspring, fit_offspring, pop_size
[docs] def optimize(self, problem: Problem, pop_size: int, initial_pop=None, max_nfev=None, max_gen=1000, num_run=1, seed=None, verbose=True) -> FinalResult: """ Optimize the parameters of the problem to be solved. Args: problem: :class:`~.Problem` to be solved. pop_size: Population size. initial_pop: Initial population of possible solutions as array of real parameters with (`pop_size`, `n_params`) shape. If None, the initial population is randomly generated from `param_bounds`. max_nfev: Maximum number of fitness evaluations. If not None, the maximum number this is considered as stopping criterion. max_gen: Maximum number of generations. If `max_nfev` is None, this is considered as stopping criterion. num_run: Independent execution number of the algorithm. seed: Initialize the random number generator. If None, the current time is used. verbose: If True, the statistics of fitness values is printed during the evolution. Returns: A :class:`~.FinalResult` containing the optimization result. """ xp = problem.xp if seed is not None: xp.random.seed(seed) gen = 0 population = xp.array(initial_pop) if initial_pop is not None else problem.generate_random_pop(pop_size) fitness = problem.evaluate_fitness(population) tot_nfev = population.shape[0] best_tracker = BestIndividualTracker(xp) best_tracker.update(population, fitness) pbar = set_progress_bar(max_gen, max_nfev) if max_nfev is not None: pbar.update(tot_nfev) stats = compute_statistics(fitness, xp) lg = Logbook() lg.record(gen=0, nfev=tot_nfev, **stats) if verbose: print_info(n_run=num_run, gen=0, nfev=tot_nfev, **stats, header=True) for gen in range(1, max_gen + 1): if max_nfev is not None and tot_nfev >= max_nfev: break if self.elitism: idx_best = xp.argmin(fitness) elite_x = population[idx_best].copy() elite_f = fitness[idx_best].copy() offspring, fit_offspring, nfev = self.evolve_population(problem, population, fitness, gen) tot_nfev += nfev if self.elitism: idx_worst_offspring = xp.argmax(fit_offspring) offspring[idx_worst_offspring] = elite_x fit_offspring[idx_worst_offspring] = elite_f population[:] = offspring fitness[:] = fit_offspring best_tracker.update(population, fitness) stats = compute_statistics(fitness, xp) lg.record(gen=gen, nfev=nfev, **stats) if verbose: print_info(n_run=num_run, gen=gen, nfev=nfev, **stats) pbar.update(nfev if max_nfev else 1) pbar.close() return FinalResult(x=best_tracker.get_best(), fun=best_tracker.get_best_fit(), nfev=tot_nfev, gen=gen, log=lg.get_log())