Source code for desdeo_emo.population.Population

from abc import ABC, abstractmethod
from typing import Dict, List, Union

import numpy as np

from desdeo_emo.population.CreateIndividuals import create_new_individuals
from desdeo_emo.recombination.BoundedPolynomialMutation import BP_mutation
from desdeo_emo.recombination.SimulatedBinaryCrossover import SBX_xover
from desdeo_problem import MOProblem

from desdeo_tools.utilities import non_dominated


[docs]class BasePopulation(ABC): def __init__(self, problem: MOProblem, pop_size: int, pop_params: Dict = None): self.pop_size: int = pop_size self.problem = problem self.individuals: Union[List, np.ndarray] = None self.objectives: np.ndarray = None self.uncertainity: np.ndarray = None self.fitness: np.ndarray = None if not problem.n_of_constraints == 0: self.constraint = None self.nadir_objective_vector = problem.nadir self.nadir_fitness_val = None if problem.ideal is not None: self.nadir_fitness_val = problem.nadir * problem._max_multiplier self.xover = None self.mutation = None self.recombination = None @property def ideal_objective_vector(self) -> np.ndarray: return self.problem.ideal @property def ideal_fitness_val(self) -> np.ndarray: return self.problem.ideal_fitness @abstractmethod
[docs] def add(self, offsprings: Union[List, np.ndarray]) -> List: """Evaluate and add offspring to the population. Parameters ---------- offsprings : Union[List, np.ndarray] List or array of individuals to be evaluated and added to the population. Returns ------- List Indices of the evaluated individuals """ pass
@abstractmethod
[docs] def keep(self, indices: List): """Save the population members given by the list of indices for the next generation. Delete the rest. Parameters ---------- indices : List List of indices of the population members to be kept for the next generation. """ pass
@abstractmethod
[docs] def delete(self, indices: List): """Delete the population members given by the list of indices for the next generation. Keep the rest. Parameters ---------- indices : List List of indices of the population members to be deleted. """ pass
@abstractmethod
[docs] def mate( self, mating_individuals: List = None, params: Dict = None ) -> Union[List, np.ndarray]: """Perform crossover and mutation over the population members. Parameters ---------- mating_individuals : List, optional List of individuals taking part in recombination. By default None, which recombinated all individuals in random order. params : Dict, optional Parameters for the mutation or crossover operator, by default None. Returns ------- Union[List, np.ndarray] The offspring population """ pass
[docs]class Population(BasePopulation): def __init__( self, problem: MOProblem, pop_size: int, pop_params: Dict = None, use_surrogates: bool = False, ): super().__init__(problem, pop_size) self.lower_limits = self.problem.get_variable_lower_bounds() self.upper_limits = self.problem.get_variable_upper_bounds() if pop_params is None: design = "LHSDesign" if pop_params is not None: if "design" in pop_params.keys(): design = pop_params["design"] else: design = "LHSDesign" individuals = create_new_individuals(design, problem, pop_size) self.add(individuals, use_surrogates) self.xover = SBX_xover() self.mutation = BP_mutation(self.lower_limits, self.upper_limits)
[docs] def add(self, offsprings: Union[List, np.ndarray], use_surrogates: bool = False): """Evaluate and add offspring to the population. Parameters ---------- offsprings : Union[List, np.ndarray] List or array of individuals to be evaluated and added to the population. use_surrogates: bool If true, use surrogate models rather than true function evaluations. use_surrogates: bool If true, use surrogate models rather than true function evaluations. Returns ------- Results Results of evaluation. """ results = self.problem.evaluate(offsprings, use_surrogates) objectives = results.objectives fitness = results.fitness constraints = results.constraints uncertainity = results.uncertainity if self.individuals is None: self.individuals = offsprings self.objectives = objectives self.fitness = fitness self.constraint = constraints self.uncertainity = uncertainity first_offspring_index = 0 else: first_offspring_index = self.individuals.shape[0] if self.individuals.ndim - offsprings.ndim == 1: self.individuals = np.vstack((self.individuals, [offsprings])) elif self.individuals.ndim == offsprings.ndim: self.individuals = np.vstack((self.individuals, offsprings)) else: pass # TODO raise error self.objectives = np.vstack((self.objectives, objectives)) self.fitness = np.vstack((self.fitness, fitness)) if self.problem.n_of_constraints != 0: self.constraint = np.vstack((self.constraint, constraints)) if uncertainity is None: self.uncertainity = None else: self.uncertainity = np.vstack((self.uncertainity, uncertainity)) last_offspring_index = self.individuals.shape[0] self.update_ideal() return results
[docs] def keep(self, indices: List): """Save the population members given by the list of indices for the next generation. Delete the rest. Parameters ---------- indices : List List of indices of the population members to be kept for the next generation. """ mask = np.zeros(self.individuals.shape[0], dtype=bool) mask[indices] = True self.individuals = self.individuals[mask] self.objectives = self.objectives[mask] self.fitness = self.fitness[mask] if self.uncertainity is not None: self.uncertainity = self.uncertainity[mask] if self.problem.n_of_constraints > 0: self.constraint = self.constraint[mask]
[docs] def delete(self, indices: List): """Delete the population members given by the list of indices for the next generation. Keep the rest. Parameters ---------- indices : List List of indices of the population members to be deleted. """ mask = np.ones(self.individuals.shape[0], dtype=bool) mask[indices] = False self.individuals = self.individuals[mask] if len(self.individuals) == 0: self.individuals = None self.objectives = self.objectives[mask] self.fitness = self.fitness[mask] if self.uncertainity is not None: self.uncertainity = self.uncertainity[mask] if self.problem.n_of_constraints > 0: self.constraint = self.constraint[mask]
[docs] def mate(self, mating_individuals: List = None) -> Union[List, np.ndarray]: """Perform crossover and mutation over the population members. Parameters ---------- mating_individuals : List, optional List of individuals taking part in recombination. By default None, which recombinated all individuals in random order. params : Dict, optional Parameters for the mutation or crossover operator, by default None. Returns ------- Union[List, np.ndarray] The offspring population """ if self.recombination is not None: offspring = self.recombination.do(self.individuals, mating_individuals) else: offspring = self.xover.do(self.individuals, mating_individuals) offspring = self.mutation.do(offspring) return offspring
[docs] def update_ideal(self): pass """if self.ideal_fitness_val is None: self.ideal_fitness_val = np.amin(self.fitness, axis=0) else: self.ideal_fitness_val = np.amin( np.vstack((self.ideal_fitness_val, self.fitness)), axis=0 ) self.ideal_objective_vector = ( self.ideal_fitness_val * self.problem._max_multiplier )"""
[docs] def replace(self, indices: List, individual: np.ndarray, evaluation: tuple): """Replace the population members given by the list of indices by the given individual and its evaluation. Keep the rest of the population unchanged. Parameters ---------- indices : List List of indices of the population members to be replaced. individual: np.ndarray Decision variables of the individual that will replace the positions given in the list. evaluation: tuple Result of the evaluation of the objective function, constraints, etc. obtained using the evaluate method. """ self.individuals[indices, :] = individual self.objectives[indices, :] = evaluation.objectives self.fitness[indices, :] = evaluation.fitness if self.constraint is not None: self.constraint[indices, :] = evaluation.constraints if self.uncertainity is not None: self.uncertainity[indices, :] = evaluation.uncertainity
[docs] def repair(self, individual): """Repair the variables of an individual which are not in the boundary defined by the problem Parameters ---------- individual : Decision variables of the individual. Return ---------- The new decision vector with the variables in the boundary defined by the problem """ upper_bounds = self.problem.get_variable_upper_bounds() lower_bounds = self.problem.get_variable_lower_bounds() upper_bounds_check = np.where(individual > upper_bounds) lower_bounds_check = np.where(individual < lower_bounds) individual[upper_bounds_check] = upper_bounds[upper_bounds_check] individual[lower_bounds_check] = lower_bounds[lower_bounds_check] return individual
[docs] def reevaluate_fitness(self): self.fitness = self.problem.reevaluate_fitness(self.objectives)
[docs] def non_dominated_fitness(self): return non_dominated(self.fitness)
[docs] def non_dominated_objectives(self): return non_dominated(self.objectives * self.problem._max_multiplier)