import time
from multiprocessing import Pool, cpu_count
from optimeed.core.tools import printIfShown, SHOW_INFO, indentParagraph
from optimeed.optimize.optiVariable import Binary_OptimizationVariable, Integer_OptimizationVariable, Real_OptimizationVariable
from optimeed.core import Option_class
from .algorithmInterface import AlgorithmInterface
from .convergence import InterfaceConvergence, EvolutionaryConvergence
# Platypus imports
from .platypus import FixedLengthArray, Generator, Solution
from .platypus.algorithms import NSGAIII, SPEA2, SMPSO, NSGAII, OMOPSO
from .platypus.core import Problem, TerminationCondition, nondominated, unique, Archive, EpsilonDominance
from .platypus.evaluator import Evaluator, run_job
from .platypus.operators import CompoundOperator, SBX, HUX, PM, BitFlip
from .platypus.types import Real, Integer, Binary
[docs]class My_OMOPSO(OMOPSO):
def __init__(self, problem, epsilons, **kwargs):
super().__init__(problem, epsilons, **kwargs)
self.archive = MyConvergence(EpsilonDominance(epsilons))
[docs]class MyConvergence(InterfaceConvergence, Archive):
[docs] conv: EvolutionaryConvergence
def __init__(self, *args, **kwargs):
super().__init__(*args, *kwargs)
self.log = []
self.conv = EvolutionaryConvergence()
self.curr_step = 1
[docs] def extend(self, solutions):
super().extend(solutions)
self.log.append(solutions)
theObjectives = [list(solution.objectives) for solution in solutions]
theConstraints = [list(solution.constraints) for solution in solutions]
self.conv.set_points_at_step(self.curr_step, theObjectives, theConstraints)
self.curr_step += 1
[docs] def get_graphs(self):
return self.conv.get_graphs()
[docs]class MyProblem(Problem):
"""Automatically sets the optimization problem"""
def __init__(self, theOptimizationVariables, nbr_objectives, nbr_constraints, evaluationFunction):
super(MyProblem, self).__init__(len(theOptimizationVariables), nbr_objectives, nbr_constraints)
# Convert types of optimization variables
for i in range(len(self.types)):
optimizationVariable = theOptimizationVariables[i]
if type(optimizationVariable) is Real_OptimizationVariable:
self.types[i] = Real(optimizationVariable.get_min_value(), optimizationVariable.get_max_value())
elif type(optimizationVariable) is Integer_OptimizationVariable:
self.types[i] = Integer(optimizationVariable.get_min_value(), optimizationVariable.get_max_value())
elif type(optimizationVariable) is Binary_OptimizationVariable:
self.types[i] = Binary(1)
else:
raise ValueError("Optimization variable not managed with this algorithm")
self.evaluationFunction = evaluationFunction
self.constraints[:] = "<=0"
self.directions = FixedLengthArray(nbr_objectives, self.MINIMIZE)
[docs] def evaluate(self, solution):
x = solution.variables[:]
returnedValues = self.evaluationFunction(x)
for i in range(len(returnedValues["objectives"])):
solution.objectives[i] = returnedValues["objectives"][i]
for i in range(len(returnedValues["constraints"])):
solution.constraints[i] = returnedValues["constraints"][i]
solution.junk = returnedValues # NEW VARIABLE ADDED
[docs]class MyGenerator(Generator):
"""Population generator to insert initial individual"""
def __init__(self, initialVectorGuess):
super(MyGenerator, self).__init__()
self.initialVectorGuess = initialVectorGuess
self.inserted_initialVector = False
[docs] def generate(self, problem):
solution = Solution(problem)
if not self.inserted_initialVector:
solution.variables = [x.encode(self.initialVectorGuess[i]) for i, x in enumerate(problem.types)]
self.inserted_initialVector = True
else:
solution.variables = [x.rand() for x in problem.types]
return solution
# class MyPeriodicAction(PeriodicAction):
# """Periodic action to save the convergence at each new generation.
# This is not entirely correct because it truncates the population before..."""
# def __init__(self, theOptimizationAlgo, theConvergenceHistoric):
# super().__init__(theOptimizationAlgo, by_nfe=True, frequency=1)
# self.theConvergence = theConvergenceHistoric
#
# def do_action(self):
# theObjectives = [list(solution.objectives) for solution in self.algorithm.result]
# theConstraints = [list(solution.constraints) for solution in self.algorithm.result]
# self.theConvergence.set_points_at_step(self.iteration, theObjectives, theConstraints)
[docs]class MyTerminationCondition(TerminationCondition):
def __init__(self, maxTime):
super(MyTerminationCondition, self).__init__()
self.maxTime = maxTime
self.startingTime = None
[docs] def initialize(self, algorithm):
self.startingTime = time.time()
[docs] def shouldTerminate(self, algorithm):
return time.time() - self.startingTime > self.maxTime
[docs]class MyMapEvaluator(Evaluator):
def __init__(self, callback_on_evaluation):
super().__init__()
self.callback_on_evaluation = callback_on_evaluation
[docs] def evaluate_all(self, jobs, **kwargs):
outputs = [None] * len(jobs)
for k, job in enumerate(jobs):
outputs[k] = run_job(job)
self.callback_on_evaluation(outputs[k].solution.junk)
del outputs[k].solution.junk
return outputs
[docs]class MyMultiprocessEvaluator(Evaluator):
def __init__(self, callback_on_evaluation, numberOfCores):
super().__init__()
self.callback_on_evaluation = callback_on_evaluation
self.pool = Pool(numberOfCores)
[docs] def evaluate_all(self, jobs, **kwargs):
outputs = [None] * len(jobs)
for k, output in enumerate(self.pool.imap_unordered(run_job, jobs)):
outputs[k] = output
self.callback_on_evaluation(outputs[k].solution.junk)
del outputs[k].solution.junk
return outputs
[docs] def close(self):
printIfShown("Closing Pool", SHOW_INFO)
self.pool.close()
printIfShown("Waiting for all processes to complete", SHOW_INFO)
self.pool.join()
printIfShown("Pool closed", SHOW_INFO)
[docs]class MultiObjective_GA(AlgorithmInterface, Option_class):
"""Based on `Platypus Library <https://platypus.readthedocs.io/en/docs/index.html>`_.
Workflow:
Define what to optimize and which function to call with a :class:`Problem`
Define the initial population with a :class:`Generator`
Define the algorithm. As options, define how to evaluate the elements with a :class:`Evaluator`, i.e., for multiprocessing.
Define what is the termination condition of the algorithm with :class:`TerminationCondition`. Here, termination condition is a maximum time.
"""
def __init__(self):
super().__init__()
self.maxTime = None # set by set_maxtime
self.evaluationFunction = None # set by set_max_objective
self.callback_on_evaluation = None # set by set_max_objective
self.initialPopulation = None # set by set_initial_population OR randomly generated (default)
self.currentPopulation = None
self.numberOfObjective = None
self.numberOfConstraints = None
self.theDimensionalConstraints = None
self.logArchive = MyConvergence()
self.Options.add_option(self.OPTI_ALGORITHM, "Optimization algorithm", 'NSGAIII')
self.Options.add_option(self.NUMBER_OF_CORES, "Number of cores", 1)
# self.Options.add_option(self.DIVISION_OUTER, "NSGAIII : Number of outer divisions", 30)
[docs] def compute(self, initialVectorGuess, listOfOptimizationVariables):
theProblem = MyProblem(listOfOptimizationVariables, self.numberOfObjective, self.numberOfConstraints, self.evaluationFunction)
# theTerminationCondition = MyTerminationCondition(self.maxTime)
kwargs = {"generator": MyGenerator(initialVectorGuess), "archive": self.logArchive}
# Update evaluator for multiprocessing
if self.get_optionValue(self.NUMBER_OF_CORES) > 1:
kwargs.update({"evaluator": MyMultiprocessEvaluator(callback_on_evaluation=self.callback_on_evaluation,
numberOfCores=min(cpu_count(), self.get_optionValue(self.NUMBER_OF_CORES))
)})
else:
kwargs.update({"evaluator": MyMapEvaluator(callback_on_evaluation=self.callback_on_evaluation)})
# Update variator if different types to optimize
base_type = theProblem.types[0].__class__
if not all([isinstance(t, base_type) for t in theProblem.types]):
kwargs.update({"variator": CompoundOperator(SBX(), HUX(), PM(), BitFlip())})
# Set optimization algorithm
divisions_outer = int(30 / self.numberOfObjective)
if self.Options.get_value(self.OPTI_ALGORITHM) == 'SPEA2':
pop_size = self.numberOfObjective + divisions_outer
algorithm = SPEA2(theProblem, population_size=pop_size, **kwargs)
elif self.Options.get_value(self.OPTI_ALGORITHM) == 'SMPSO':
algorithm = SMPSO(theProblem, **kwargs)
elif self.Options.get_value(self.OPTI_ALGORITHM) == 'OMOPSO':
algorithm = My_OMOPSO(theProblem, epsilons=[0.05], **kwargs)
elif self.Options.get_value(self.OPTI_ALGORITHM) == 'NSGAII':
algorithm = NSGAII(theProblem, **kwargs) # self.Options.get_value(self.DIVISION_OUTER))
else:
algorithm = NSGAIII(theProblem, divisions_outer, **kwargs) # self.Options.get_value(self.DIVISION_OUTER))
algorithm.run(MyTerminationCondition(self.maxTime))
try:
kwargs.get("evaluator").close()
except KeyError:
print("here")
def decode_solution_platypus(var_solutions, var_optimisations):
my_solutions = [None] * len(var_solutions)
for k, type_var in enumerate(var_optimisations):
my_solutions[k] = type_var.decode(var_solutions[k])
return my_solutions
return [decode_solution_platypus(solution.variables, theProblem.types) for solution in unique(nondominated(algorithm.result)) if solution.feasible]
[docs] def set_evaluationFunction(self, evaluationFunction, callback_on_evaluation, numberOfObjectives, numberOfConstraints):
self.evaluationFunction = evaluationFunction
self.callback_on_evaluation = callback_on_evaluation
self.numberOfObjective = numberOfObjectives
self.numberOfConstraints = numberOfConstraints
[docs] def set_maxtime(self, maxTime):
self.maxTime = maxTime
[docs] def __str__(self):
theStr = ''
theStr += "Platypus multiobjective library\n"
theStr += indentParagraph(str(self.Options), indent_level=1)
return theStr
[docs] def get_convergence(self):
return self.logArchive