Algorithm.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. """Abstract Algorithm class used as basic algorithm implementation with some specific initialization
  2. """
  3. # main imports
  4. import logging
  5. import pkgutil
  6. import sys
  7. from ..utils.color import macop_text, macop_line, macop_progress
  8. # Generic algorithm class
  9. class Algorithm():
  10. """Algorithm class used as basic algorithm
  11. Attributes:
  12. initalizer: {function} -- basic function strategy to initialize solution
  13. evaluator: {function} -- basic function in order to obtained fitness (mono or multiple objectives)
  14. operators: {[Operator]} -- list of operator to use when launching algorithm
  15. policy: {Policy} -- Policy class implementation strategy to select operators
  16. validator: {function} -- basic function to check if solution is valid or not under some constraints
  17. maximise: {bool} -- specify kind of optimization problem
  18. currentSolution: {Solution} -- current solution managed for current evaluation
  19. bestSolution: {Solution} -- best solution found so far during running algorithm
  20. callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initializing algorithm
  21. parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
  22. """
  23. def __init__(self,
  24. _initalizer,
  25. _evaluator,
  26. _operators,
  27. _policy,
  28. _validator,
  29. _maximise=True,
  30. _parent=None):
  31. self.initializer = _initalizer
  32. self.evaluator = _evaluator
  33. self.operators = _operators
  34. self.policy = _policy
  35. self.validator = _validator
  36. self.callbacks = []
  37. self.bestSolution = None
  38. # by default
  39. self.numberOfEvaluations = 0
  40. self.maxEvaluations = 0
  41. # other parameters
  42. self.parent = _parent # parent algorithm if it's sub algorithm
  43. #self.maxEvaluations = 0 # by default
  44. self.maximise = _maximise
  45. # track reference of algo into operator (keep an eye into best solution)
  46. for operator in self.operators:
  47. operator.setAlgo(self)
  48. # also track reference for policy
  49. self.policy.setAlgo(self)
  50. self.initRun()
  51. def initContext(self):
  52. """Initialize context for macop solutions (dynamic import)
  53. Must be part of initialization method of Algorithm implementation
  54. """
  55. # dynamically load all available macop solutions
  56. for loader, module_name, _ in pkgutil.walk_packages(
  57. path=[p + '/solutions' for p in sys.modules['macop'].__path__],
  58. prefix='macop.solutions.'):
  59. _module = loader.find_module(module_name).load_module(module_name)
  60. globals()[module_name] = _module
  61. def addCallback(self, _callback):
  62. """Add new callback to algorithm specifying usefull parameters
  63. Args:
  64. _callback: {Callback} -- specific Callback instance
  65. """
  66. # specify current main algorithm reference
  67. _callback.setAlgo(self)
  68. # set as new
  69. self.callbacks.append(_callback)
  70. def resume(self):
  71. """Resume algorithm using Callback instances
  72. """
  73. # load every callback if many things are necessary to do before running algorithm
  74. for callback in self.callbacks:
  75. callback.load()
  76. def initRun(self):
  77. """
  78. Initialize the current solution and best solution
  79. """
  80. self.currentSolution = self.initializer()
  81. # evaluate current solution
  82. self.currentSolution.evaluate(self.evaluator)
  83. # keep in memory best known solution (current solution)
  84. self.bestSolution = self.currentSolution
  85. def increaseEvaluation(self):
  86. """
  87. Increase number of evaluation once a solution is evaluated
  88. """
  89. self.numberOfEvaluations += 1
  90. if self.parent is not None:
  91. self.parent.numberOfEvaluations += 1
  92. def getGlobalEvaluation(self):
  93. """Get the global number of evaluation (if inner algorithm)
  94. Returns:
  95. {int} -- current global number of evaluation
  96. """
  97. if self.parent is not None:
  98. return self.parent.numberOfEvaluations
  99. return self.numberOfEvaluations
  100. def getGlobalMaxEvaluation(self):
  101. """Get the global max number of evaluation (if inner algorithm)
  102. Returns:
  103. {int} -- current global max number of evaluation
  104. """
  105. if self.parent is not None:
  106. return self.parent.maxEvaluations
  107. return self.maxEvaluations
  108. def stop(self):
  109. """
  110. Global stopping criteria (check for inner algorithm too)
  111. """
  112. if self.parent is not None:
  113. return self.parent.numberOfEvaluations >= self.parent.maxEvaluations or self.numberOfEvaluations >= self.maxEvaluations
  114. return self.numberOfEvaluations >= self.maxEvaluations
  115. def evaluate(self, _solution):
  116. """
  117. Evaluate a solution using evaluator passed when intialize algorithm
  118. Args:
  119. solution: {Solution} -- solution to evaluate
  120. Returns:
  121. fitness score of solution which is not already evaluated or changed
  122. Note:
  123. if multi-objective problem this method can be updated using array of `evaluator`
  124. """
  125. return _solution.evaluate(self.evaluator)
  126. def update(self, _solution):
  127. """
  128. Apply update function to solution using specific `policy`
  129. Check if solution is valid after modification and returns it
  130. Args:
  131. solution: {Solution} -- solution to update using current policy
  132. Returns:
  133. {Solution} -- updated solution obtained by the selected operator
  134. """
  135. # two parameters are sent if specific crossover solution are wished
  136. sol = self.policy.apply(_solution)
  137. if (sol.isValid(self.validator)):
  138. return sol
  139. else:
  140. logging.info("-- New solution is not valid %s" % sol)
  141. return _solution
  142. def isBetter(self, _solution):
  143. """
  144. Check if solution is better than best found
  145. Args:
  146. solution: {Solution} -- solution to compare with best one
  147. Returns:
  148. {bool} -- `True` if better
  149. """
  150. # depending of problem to solve (maximizing or minimizing)
  151. if self.maximise:
  152. if _solution.fitness() > self.bestSolution.fitness():
  153. return True
  154. else:
  155. if _solution.fitness() < self.bestSolution.fitness():
  156. return True
  157. # by default
  158. return False
  159. def run(self, _evaluations):
  160. """
  161. Run the specific algorithm following number of evaluations to find optima
  162. """
  163. # append number of max evaluation if multiple run called
  164. self.maxEvaluations += _evaluations
  165. # check if global evaluation is used or not
  166. if self.parent is not None and self.getGlobalEvaluation() != 0:
  167. # init number evaluations of inner algorithm depending of globalEvaluation
  168. # allows to restart from `checkpoint` last evaluation into inner algorithm
  169. rest = self.getGlobalEvaluation() % self.maxEvaluations
  170. self.numberOfEvaluations = rest
  171. else:
  172. self.numberOfEvaluations = 0
  173. logging.info("Run %s with %s evaluations" %
  174. (self.__str__(), _evaluations))
  175. def progress(self):
  176. """
  177. Log progress and apply callbacks if necessary
  178. """
  179. if len(self.callbacks) > 0:
  180. for callback in self.callbacks:
  181. callback.run()
  182. macop_progress(self.getGlobalEvaluation(),
  183. self.getGlobalMaxEvaluation())
  184. logging.info("-- %s evaluation %s of %s (%s%%) - BEST SCORE %s" %
  185. (type(self).__name__, self.numberOfEvaluations,
  186. self.maxEvaluations, "{0:.2f}".format(
  187. (self.numberOfEvaluations) / self.maxEvaluations *
  188. 100.), self.bestSolution.fitness()))
  189. def end(self):
  190. """Display end message into `run` method
  191. """
  192. print(
  193. macop_text('({}) Found after {} evaluations \n - {}'.format(
  194. type(self).__name__, self.numberOfEvaluations,
  195. self.bestSolution)))
  196. print(macop_line())
  197. def information(self):
  198. logging.info("-- Best %s - SCORE %s" %
  199. (self.bestSolution, self.bestSolution.fitness()))
  200. def __str__(self):
  201. return "%s using %s" % (type(self).__name__, type(
  202. self.bestSolution).__name__)