MOEAD.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. """Multi-Ojective Evolutionary Algorithm with Scalar Decomposition algorithm
  2. """
  3. # main imports
  4. import pkgutil
  5. import logging
  6. import math
  7. import numpy as np
  8. import sys
  9. from ...utils.color import macop_text, macop_line, macop_progress
  10. # module imports
  11. from ..Algorithm import Algorithm
  12. from .MOSubProblem import MOSubProblem
  13. # import all available solutions
  14. for loader, module_name, is_pkg in pkgutil.walk_packages(
  15. path=['macop/solutions'], prefix='macop.solutions.'):
  16. _module = loader.find_module(module_name).load_module(module_name)
  17. globals()[module_name] = _module
  18. def moEvaluator(_solution, _evaluator, _weights):
  19. scores = [ eval(_solution) for eval in _evaluator ]
  20. # associate objectives scores to solution
  21. _solution.scores = scores
  22. return sum([scores[i] for i, w in enumerate(_weights) ])
  23. class MOEAD(Algorithm):
  24. """Multi-Ojective Evolutionary Algorithm with Scalar Decomposition
  25. Attributes:
  26. mu: {int} -- number of sub problems
  27. T: {[float]} -- number of neightbors for each sub problem
  28. initalizer: {function} -- basic function strategy to initialize solution
  29. evaluator: {[function]} -- list of basic function in order to obtained fitness (multiple objectives)
  30. operators: {[Operator]} -- list of operator to use when launching algorithm
  31. policy: {Policy} -- Policy class implementation strategy to select operators
  32. validator: {function} -- basic function to check if solution is valid or not under some constraints
  33. maximise: {bool} -- specify kind of optimization problem
  34. currentSolution: {Solution} -- current solution managed for current evaluation
  35. population: [{Solution}] -- population of solution, one for each sub problem
  36. pfPop: [{Solution}] -- pareto front population
  37. callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initializing algorithm
  38. """
  39. def __init__(self,
  40. _mu,
  41. _T,
  42. _initalizer,
  43. _evaluator,
  44. _operators,
  45. _policy,
  46. _validator,
  47. _maximise=True,
  48. _parent=None):
  49. # redefinition of constructor to well use `initRun` method
  50. self.initializer = _initalizer
  51. self.evaluator = _evaluator
  52. self.operators = _operators
  53. self.policy = _policy
  54. self.validator = _validator
  55. self.callbacks = []
  56. # by default
  57. self.numberOfEvaluations = 0
  58. self.maxEvaluations = 0
  59. # other parameters
  60. self.parent = _parent # parent algorithm if it's sub algorithm
  61. #self.maxEvaluations = 0 # by default
  62. self.maximise = _maximise
  63. # track reference of algo into operator (keep an eye into best solution)
  64. for operator in self.operators:
  65. operator.setAlgo(self)
  66. # also track reference for policy
  67. self.policy.setAlgo(self)
  68. if _mu < _T:
  69. raise ValueError('`mu` cannot be less than `T`')
  70. self.mu = _mu
  71. self.T = _T
  72. # initialize neighbors for each sub problem
  73. self.setNeighbors()
  74. weights = []
  75. for i in range(self.mu):
  76. angle = math.pi / 2 * i / (self.mu - 1)
  77. weights.append([math.cos(angle), math.sin(angle)])
  78. self.subProblems = []
  79. for i in range(self.mu):
  80. # compute weight sum from solution
  81. sub_evaluator = lambda _solution: moEvaluator(_solution, _evaluator, weights[i])
  82. # intialize each sub problem
  83. subProblem = MOSubProblem(i, weights[i], _initalizer,
  84. sub_evaluator, _operators, _policy,
  85. _validator, _maximise, self)
  86. self.subProblems.append(subProblem)
  87. self.population = [None for n in range(self.mu)]
  88. self.pfPop = []
  89. if self.maximise:
  90. self.refPoint = [ 0 for _ in range(len(_evaluator)) ]
  91. else:
  92. self.refPoint = [ sys.float_info.max for _ in range(len(_evaluator)) ]
  93. def initRun(self):
  94. """
  95. Method which initialiazes or re-initializes the whole algorithm context specifically for MOEAD
  96. """
  97. # initialization is done during run method
  98. pass
  99. def run(self, _evaluations):
  100. """
  101. Run the local search algorithm
  102. Args:
  103. _evaluations: {int} -- number of Local search evaluations
  104. Returns:
  105. {Solution} -- best solution found
  106. """
  107. # by default use of mother method to initialize variables
  108. super().run(_evaluations)
  109. # initialize each sub problem
  110. for i in range(self.mu):
  111. self.subProblems[i].run(1)
  112. self.population[i] = self.subProblems[i].bestSolution
  113. self.pfPop.append(self.subProblems[i].bestSolution)
  114. # enable callback resume for MOEAD
  115. self.resume()
  116. # MOEAD algorithm implementation
  117. while not self.stop():
  118. for i in range(self.mu):
  119. # run 1 iteration into sub problem `i`
  120. self.subProblems[i].run(1)
  121. spBestSolution = self.subProblems[i].bestSolution
  122. self.updateRefPoint(spBestSolution)
  123. # for each neighbor of current sub problem update solution if better
  124. improvment = False
  125. for j in self.neighbors[i]:
  126. if spBestSolution.fitness() > self.subProblems[j].bestSolution.fitness():
  127. # create new solution based on current new if better, computes fitness associated to new solution for sub problem
  128. class_name = type(spBestSolution).__name__
  129. newSolution = getattr(globals()['macop.solutions.' + class_name], class_name)(spBestSolution.data, len(spBestSolution.data))
  130. # evaluate solution for new sub problem and update as best solution
  131. self.subProblems[j].evaluate(newSolution)
  132. self.subProblems[j].bestSolution = newSolution
  133. # update population solution for this sub problem
  134. self.population[j] = newSolution
  135. improvment = True
  136. # add new solution if improvment is idenfity
  137. if improvment:
  138. self.pfPop.append(spBestSolution)
  139. # update pareto front
  140. self.pfPop = self.paretoFront(self.pfPop)
  141. # add progress here
  142. self.progress()
  143. # stop algorithm if necessary
  144. if self.stop():
  145. break
  146. logging.info("End of %s, best solution found %s" %
  147. (type(self).__name__, self.population))
  148. self.end()
  149. return self.pfPop
  150. def progress(self):
  151. """
  152. Log progress and apply callbacks if necessary
  153. """
  154. if len(self.callbacks) > 0:
  155. for callback in self.callbacks:
  156. callback.run()
  157. macop_progress(self.getGlobalEvaluation(),
  158. self.getGlobalMaxEvaluation())
  159. logging.info("-- %s evaluation %s of %s (%s%%)" %
  160. (type(self).__name__, self.numberOfEvaluations,
  161. self.maxEvaluations, "{0:.2f}".format(
  162. (self.numberOfEvaluations) / self.maxEvaluations *
  163. 100.)))
  164. def setNeighbors(self):
  165. dmin = dmax = 0
  166. if self.T % 2 == 1:
  167. dmin = -int(self.T / 2)
  168. dmax = int(self.T / 2) + 1
  169. else:
  170. dmin = -int(self.T / 2) + 1
  171. dmax = +self.T / 2
  172. # init neighbord list
  173. self.neighbors = [[] for n in range(self.mu)]
  174. for direction in range(0, -dmin):
  175. for i in range(self.T):
  176. self.neighbors[direction].append(i)
  177. for direction in range(-dmin, self.mu - dmax):
  178. for i in range(direction + dmin, direction + dmax):
  179. self.neighbors[direction].append(i)
  180. for direction in range(self.mu - dmax, self.mu):
  181. for i in range(self.mu - self.T, self.mu):
  182. self.neighbors[direction].append(i)
  183. def updateRefPoint(self, _solution):
  184. if self.maximise:
  185. for i in range(len(self.evaluator)):
  186. if _solution.scores[i] > self.refPoint[i]:
  187. self.refPoint[i] = _solution.scores[i]
  188. else:
  189. for i in range(len(self.evaluator)):
  190. if _solution.scores[i] < self.refPoint[i]:
  191. self.refPoint[i] = _solution.scores[i]
  192. def paretoFront(self, _population):
  193. paFront = []
  194. indexes = []
  195. nObjectives = len(self.evaluator)
  196. nSolutions = len(_population)
  197. # find dominated solution
  198. for i in range(nSolutions):
  199. for j in range(nSolutions):
  200. if j in indexes:
  201. continue
  202. nDominated = 0
  203. # check number of dominated objectives of current solution by the others solution
  204. for k in range(len(self.evaluator)):
  205. if self.maximise:
  206. if _population[i].scores[k] < _population[j].scores[k]:
  207. nDominated += 1
  208. else:
  209. if _population[i].scores[k] > _population[j].scores[k]:
  210. nDominated += 1
  211. if nDominated == nObjectives:
  212. indexes.append(i)
  213. break
  214. # store the non dominated solution into pareto front
  215. for i in range(nSolutions):
  216. if i not in indexes:
  217. paFront.append(_population[i])
  218. return paFront
  219. def end(self):
  220. """Display end message into `run` method
  221. """
  222. print(macop_text('({}) Found after {} evaluations'.format(type(self).__name__, self.numberOfEvaluations)))
  223. for i, solution in enumerate(self.pfPop):
  224. print(' - [{}] {} : {}'.format(i, solution.scores, solution))
  225. print(macop_line())
  226. def information(self):
  227. logging.info("-- Pareto front :")
  228. for i, solution in enumerate(self.pfPop):
  229. logging.info("-- %s] SCORE %s - %s" %
  230. (i, solution.scores, solution))
  231. def __str__(self):
  232. return "%s using %s" % (type(self).__name__, type(
  233. self.population).__name__)