Algorithm.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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. This class enables to manage some common usages of operation research algorithms:
  12. - initialization function of solution
  13. - validator function to check if solution is valid or not (based on some criteria)
  14. - evaluation function to give fitness score to a solution
  15. - operators used in order to update solution during search process
  16. - policy process applied when choosing next operator to apply
  17. - callbacks function in order to do some relative stuff every number of evaluation or reload algorithm state
  18. - parent algorithm associated to this new algorithm instance (hierarchy management)
  19. Attributes:
  20. initalizer: {function} -- basic function strategy to initialize solution
  21. evaluator: {function} -- basic function in order to obtained fitness (mono or multiple objectives)
  22. operators: {[Operator]} -- list of operator to use when launching algorithm
  23. policy: {Policy} -- Policy class implementation strategy to select operators
  24. validator: {function} -- basic function to check if solution is valid or not under some constraints
  25. maximise: {bool} -- specify kind of optimisation problem
  26. currentSolution: {Solution} -- current solution managed for current evaluation comparison
  27. bestSolution: {Solution} -- best solution found so far during running algorithm
  28. callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initializing algorithm
  29. parent: {Algorithm} -- parent algorithm reference in case of inner Algorithm instance (optional)
  30. """
  31. def __init__(self,
  32. initalizer,
  33. evaluator,
  34. operators,
  35. policy,
  36. validator,
  37. maximise=True,
  38. parent=None):
  39. # protected members intialization
  40. self._initializer = initalizer
  41. self._evaluator = evaluator
  42. self._operators = operators
  43. self._policy = policy
  44. self._validator = validator
  45. self._callbacks = []
  46. self._bestSolution = None
  47. self._currentSolution = None
  48. # by default
  49. self._numberOfEvaluations = 0
  50. self._maxEvaluations = 0
  51. # other parameters
  52. self._parent = parent # parent algorithm if it's sub algorithm
  53. #self.maxEvaluations = 0 # by default
  54. self._maximise = maximise
  55. # track reference of algorihtm into operator (keep an eye into best solution)
  56. for operator in self._operators:
  57. if self._parent is not None:
  58. operator.setAlgo(self.getParent())
  59. else:
  60. operator.setAlgo(self)
  61. # also track reference for policy
  62. if self._parent is not None:
  63. self._policy.setAlgo(self.getParent())
  64. else:
  65. self._policy.setAlgo(self)
  66. def addCallback(self, _callback):
  67. """Add new callback to algorithm specifying usefull parameters
  68. Args:
  69. _callback: {Callback} -- specific Callback instance
  70. """
  71. # specify current main algorithm reference for callback
  72. if self._parent is not None:
  73. _callback.setAlgo(self.getParent())
  74. else:
  75. _callback.setAlgo(self)
  76. # set as new
  77. self._callbacks.append(_callback)
  78. def resume(self):
  79. """Resume algorithm using Callback instances
  80. """
  81. # load every callback if many things are necessary to do before running algorithm
  82. for callback in self._callbacks:
  83. callback.load()
  84. def getParent(self):
  85. """Recursively find the main parent algorithm attached of the current algorithm
  86. Returns:
  87. {Algorithm} -- main algorithm set for this algorithm
  88. """
  89. current_algorithm = self
  90. parent_alrogithm = None
  91. # recursively find the main algorithm parent
  92. while self._parent is not None:
  93. parent_alrogithm = current_algorithm._parent
  94. current_algorithm = current_algorithm._parent
  95. return parent_alrogithm
  96. def initRun(self):
  97. """
  98. Initialize the current solution and best solution using the `initialiser` function
  99. """
  100. self._currentSolution = self._initializer()
  101. # evaluate current solution
  102. self._currentSolution.evaluate(self._evaluator)
  103. # keep in memory best known solution (current solution)
  104. if self._bestSolution is None:
  105. self._bestSolution = self._currentSolution
  106. def increaseEvaluation(self):
  107. """
  108. Increase number of evaluation once a solution is evaluated for each dependant algorithm (parents hierarchy)
  109. """
  110. self._numberOfEvaluations += 1
  111. current_algorithm = self
  112. while current_algorithm._parent is not None:
  113. current_algorithm = current_algorithm._parent
  114. current_algorithm._numberOfEvaluations += 1
  115. def getGlobalEvaluation(self):
  116. """Get the global number of evaluation (if inner algorithm)
  117. Returns:
  118. {int} -- current global number of evaluation
  119. """
  120. parent_algorithm = self.getParent()
  121. if parent_algorithm is not None:
  122. return parent_algorithm.getGlobalEvaluation()
  123. return self._numberOfEvaluations
  124. def getGlobalMaxEvaluation(self):
  125. """Get the global max number of evaluation (if inner algorithm)
  126. Returns:
  127. {int} -- current global max number of evaluation
  128. """
  129. parent_algorithm = self.getParent()
  130. if parent_algorithm is not None:
  131. return parent_algorithm.getGlobalMaxEvaluation()
  132. return self._maxEvaluations
  133. def stop(self):
  134. """
  135. Global stopping criteria (check for parents algorithm hierarchy too)
  136. """
  137. parent_algorithm = self.getParent()
  138. # based on global stopping creteria or on its own stopping critera
  139. if parent_algorithm is not None:
  140. return parent_algorithm._numberOfEvaluations >= parent_algorithm._maxEvaluations or self._numberOfEvaluations >= self._maxEvaluations
  141. return self._numberOfEvaluations >= self._maxEvaluations
  142. def evaluate(self, _solution):
  143. """
  144. Evaluate a solution using evaluator passed when intialize algorithm
  145. Args:
  146. solution: {Solution} -- solution to evaluate
  147. Returns:
  148. fitness score of solution which is not already evaluated or changed
  149. Note:
  150. if multi-objective problem this method can be updated using array of `evaluator`
  151. """
  152. return _solution.evaluate(self._evaluator)
  153. def update(self, _solution):
  154. """
  155. Apply update function to solution using specific `policy`
  156. Check if solution is valid after modification and returns it
  157. Args:
  158. solution: {Solution} -- solution to update using current policy
  159. Returns:
  160. {Solution} -- updated solution obtained by the selected operator
  161. """
  162. # two parameters are sent if specific crossover solution are wished
  163. sol = self._policy.apply(_solution)
  164. if (sol.isValid(self._validator)):
  165. return sol
  166. else:
  167. logging.info("-- New solution is not valid %s" % sol)
  168. return _solution
  169. def isBetter(self, _solution):
  170. """
  171. Check if solution is better than best found
  172. Args:
  173. solution: {Solution} -- solution to compare with best one
  174. Returns:
  175. {bool} -- `True` if better
  176. """
  177. # depending of problem to solve (maximizing or minimizing)
  178. if self._maximise:
  179. if _solution.fitness() > self._bestSolution.fitness():
  180. return True
  181. else:
  182. if _solution.fitness() < self._bestSolution.fitness():
  183. return True
  184. # by default
  185. return False
  186. def run(self, _evaluations):
  187. """
  188. Run the specific algorithm following number of evaluations to find optima
  189. """
  190. # append number of max evaluation if multiple run called
  191. self._maxEvaluations += _evaluations
  192. # check if global evaluation is used or not
  193. if self.getParent() is not None and self.getGlobalEvaluation() != 0:
  194. # init number evaluations of inner algorithm depending of globalEvaluation
  195. # allows to restart from `checkpoint` last evaluation into inner algorithm
  196. rest = self.getGlobalEvaluation() % self._maxEvaluations
  197. self._numberOfEvaluations = rest
  198. else:
  199. self._numberOfEvaluations = 0
  200. logging.info("Run %s with %s evaluations" %
  201. (self.__str__(), _evaluations))
  202. def progress(self):
  203. """
  204. Log progress and apply callbacks if necessary
  205. """
  206. if len(self._callbacks) > 0:
  207. for callback in self._callbacks:
  208. callback.run()
  209. macop_progress(self.getGlobalEvaluation(),
  210. self.getGlobalMaxEvaluation())
  211. logging.info(f"-- {type(self).__name__} evaluation {self._numberOfEvaluations} of {self._maxEvaluations} ({self._numberOfEvaluations / self._maxEvaluations * 100:.{2}}%) - BEST SCORE {self._bestSolution.fitness()}")
  212. def end(self):
  213. """Display end message into `run` method
  214. """
  215. print(macop_text(f'({type(self).__name__}) Found after {self._numberOfEvaluations} evaluations \n - {self._bestSolution}'))
  216. print(macop_line())
  217. def information(self):
  218. logging.info(f"-- Best {self._bestSolution} - SCORE {self._bestSolution.fitness()}")
  219. def __str__(self):
  220. return f"{type(self).__name__} using {type(self._bestSolution).__name__}"