Advertisement
Guest User

Untitled

a guest
Feb 10th, 2016
53
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.42 KB | None | 0 0
  1. # -*- coding=utf-8 -*-
  2. from __future__ import unicode_literals
  3. import re
  4. import subprocess
  5. import tempfile
  6. import datetime
  7.  
  8. __author__ = 'mgallet'
  9.  
  10.  
  11. class Scheduler(object):
  12. def __init__(self, agents, forbidden_weeks, only_p2_weeks=None, weak_weeks=None, fixed_weeks=None,
  13. p1_tolerance=0, p2_tolerance=1, p1_offsets=None, p2_offsets=None,
  14. p1_offloads=None, p2_offloads=None):
  15. """
  16. :param agents: liste des agents
  17. :param forbidden_weeks: forbidden_weeks[agent] = liste des semaines d'absence complète (ni P1, ni P2, ni P3)
  18. :param only_p2_weeks: only_p2_weeks[agent] = liste des semaines d'absence partielle (peut être P2 ou P3 uniquement)
  19. :param weak_weeks: liste des semaines sans P3
  20. :param fixed_weeks: fixed_weeks[week][position] = agent : affectations inviolables (par exemple pour le passé)
  21. :param p1_tolerance: le nombre de P1 doit être le même pour tout le monde à 'p1_tolerance' près
  22. :param p2_tolerance: le nombre de P2 doit être le même pour tout le monde à 'p2_tolerance' près
  23. :param p1_offsets: p1_offsets[agent] = nombre de P1 d'avance (>0) ou de retard (<0) pour l'agent
  24. :param p2_offsets: p2_offsets[agent] = nombre de P2 d'avance (>0) ou de retard (<0) pour l'agent
  25. :param p1_offloads: p1_offloads[agent] = si < 1.0 : l'agent fait moins de P1
  26. :param p2_offloads: p2_offloads[agent] = si < 1.0 : l'agent fait moins de P2
  27. :return:
  28. """
  29. self.agents = agents # [agent1, agent2, agent3, ]
  30. self.forbidden_weeks = forbidden_weeks # forbidden_weeks[agent] = [week1, week2, week3, ..]
  31. self.only_p2_weeks = only_p2_weeks or {} # only_p2_weeks[agent] = [week1, week2, week3, ..]
  32. self.fixed_weeks = fixed_weeks or {} # existing_weeks[week] = {position: agent, }
  33. self.weak_weeks = weak_weeks or {} # set of 2-person-only weeks
  34. self.p1_offloads = p1_offloads or {} # p1_offloads[agent] = float (> 1. => more P1s, < 1. : fewer P1s)
  35. self.p2_offloads = p2_offloads or {} # p2_offloads[agent] = float (> 1. => more P2/3s, < 1. : fewer P2/3s)
  36. self.p1_offsets = p1_offsets or {} # p1_offsets[agent] = int
  37. self.p2_offsets = p2_offsets or {} # p2_offsets[agent] = int
  38. self.p1_tolerance = p1_tolerance
  39. self.p2_tolerance = p2_tolerance
  40. for agent in agents:
  41. self.forbidden_weeks.setdefault(agent, [])
  42. self.only_p2_weeks.setdefault(agent, [])
  43. self.p1_offsets.setdefault(agent, 0)
  44. self.p2_offsets.setdefault(agent, 0)
  45. self.p1_offloads.setdefault(agent, 1.)
  46. self.p2_offloads.setdefault(agent, 1.)
  47.  
  48. @staticmethod
  49. def variable(position=1, agent='mgallet', week=1):
  50. return 'p%d_%s_%d' % (position, agent, week)
  51.  
  52. def constraint_list(self, start_week=1, end_week=52):
  53. weeks = range(start_week, end_week + 1)
  54. positions = range(1, 4)
  55. yield 'min: '
  56. for week in weeks:
  57. for agent in self.agents:
  58. if week not in self.forbidden_weeks[agent]:
  59. for position in positions:
  60. yield "%s >= 0" % self.variable(position, agent, week)
  61. yield "%s <= 1" % self.variable(position, agent, week)
  62. # The same person can only be P1, P2 xor P3
  63. yield '%s <= 1' % ' + '.join([self.variable(position, agent, week) for position in positions])
  64. if week in self.only_p2_weeks[agent]:
  65. yield "%s = 0" % self.variable(1, agent, week)
  66. else:
  67. # Respect the personal constraints of each person
  68. for position in positions:
  69. yield "%s = 0" % self.variable(position, agent, week)
  70. # respect the existing weeks
  71. for week, week_data in self.fixed_weeks.items():
  72. for position, agent in week_data.items():
  73. yield "%s = 1" % self.variable(position, agent, week)
  74. for week in weeks:
  75. # Exactly one person in P1 for each week
  76. yield '%s = 1' % ' + '.join([self.variable(1, agent, week) for agent in self.agents])
  77. # Exactly one person in P2 for each week
  78. yield '%s = 1' % ' + '.join([self.variable(2, agent, week) for agent in self.agents])
  79. if week not in self.weak_weeks:
  80. # Exactly one person in P3 for each week
  81. yield '%s = 1' % ' + '.join([self.variable(3, agent, week) for agent in self.agents])
  82. # P1 cannot be the same for two successive weeks
  83. for week in range(start_week, end_week):
  84. for agent in self.agents:
  85. yield '%s + %s <= 1' % (self.variable(1, agent, week), self.variable(1, agent, week + 1))
  86. # the same person cannot be P1, P2, P3 for four successive weeks
  87. for week in range(start_week, end_week - 3):
  88. for agent in self.agents:
  89. week_1 = ' + '.join([self.variable(position, agent, week) for position in positions])
  90. week_2 = ' + '.join([self.variable(position, agent, week + 1) for position in positions])
  91. week_3 = ' + '.join([self.variable(position, agent, week + 2) for position in positions])
  92. week_4 = ' + '.join([self.variable(position, agent, week + 3) for position in positions])
  93. yield '%s + %s + %s + %s <= 3' % (week_1, week_2, week_3, week_4)
  94.  
  95. for agent in self.agents:
  96. # number of P1s and P2/3s of each agent
  97. yield '%s = p1_sum_%s' % (' + '.join([self.variable(1, agent, week) for week in weeks]), agent)
  98. yield '%s + %s = p23_sum_%s' % (' + '.join([self.variable(2, agent, week) for week in weeks]),
  99. ' + '.join([self.variable(3, agent, week) for week in weeks]), agent)
  100. # (some agents can execute less P1s than others)
  101. # balance of P1s between agents, taking into account previous P1s and external work
  102. offload_terms = []
  103. for agent in self.agents:
  104. offload_terms.append('%g * p1_sum_%s + %g' %
  105. (1. / (self.p1_offloads[agent]), agent,
  106. 1. * self.p1_offsets[agent] / self.p1_offloads[agent]))
  107. yield '%s = p1_sum' % ' + '.join(offload_terms)
  108. for agent in self.agents:
  109. yield '%g + %g * p1_sum_%s <= %g * p1_sum + %g' % (1. * self.p1_offsets[agent] / self.p1_offloads[agent],
  110. 1. / self.p1_offloads[agent],
  111. agent, 1. / len(self.agents), self.p1_tolerance)
  112. yield '%g + %g * p1_sum_%s >= %g * p1_sum - %g' % (1. * self.p1_offsets[agent] / self.p1_offloads[agent],
  113. 1. / self.p1_offloads[agent],
  114. agent, 1. / len(self.agents), self.p1_tolerance)
  115. # balance of P2s between agents, taking into account previous P2/P3s and external work
  116. # (some agents can execute less P2s and P3s than others)
  117. offload_terms = []
  118. for agent in self.agents:
  119. offload_terms.append('%g * p23_sum_%s + %g' %
  120. (1. / (self.p2_offloads[agent]), agent,
  121. 1. * self.p2_offsets[agent] / self.p2_offloads[agent]))
  122. yield '%s = p23_sum' % ' + '.join(offload_terms)
  123. for agent in self.agents:
  124. yield '%g + %g * p23_sum_%s <= %g * p23_sum + %g' % (1. * self.p2_offsets[agent] / self.p2_offloads[agent],
  125. 1. / self.p2_offloads[agent],
  126. agent, 1. / len(self.agents), self.p2_tolerance)
  127. yield '%g + %g * p23_sum_%s >= %g * p23_sum - %g' % (1. * self.p2_offsets[agent] / self.p2_offloads[agent],
  128. 1. / self.p2_offloads[agent],
  129. agent, 1. / len(self.agents), self.p2_tolerance)
  130. # integer constraints
  131. for position in positions:
  132. for week in weeks:
  133. for agent in self.agents:
  134. yield 'int %s' % self.variable(position, agent, week)
  135.  
  136. def solve(self, start_week=1, end_week=52, verbose=False):
  137. with tempfile.NamedTemporaryFile() as fd:
  138. for constraint in self.constraint_list(start_week=start_week, end_week=end_week):
  139. if verbose:
  140. print(constraint)
  141. fd.write('%s;\n' % constraint)
  142. fd.flush()
  143. p = subprocess.Popen(['lp_solve', '-lp', fd.name], stdout=subprocess.PIPE)
  144. stdout, stderr = p.communicate()
  145. results_per_week = {}
  146. regexp = re.compile(r'p(\d+)_([a-z]+)_(\d+)\s+(0|1)')
  147. p1_per_agent = {agent: 0 for agent in self.agents}
  148. p2_p3_per_agent = {agent: 0 for agent in self.agents}
  149. for line in stdout.splitlines():
  150. matcher = regexp.match(line)
  151. if not matcher:
  152. continue
  153. position_str, agent, week_str, value = matcher.groups()
  154. if value != '1':
  155. continue
  156. position = int(position_str)
  157. week = int(week_str)
  158. results_per_week.setdefault(week, {})[position] = agent
  159. if position == 1:
  160. p1_per_agent[agent] += 1
  161. elif position == 2 or position == 3:
  162. p2_p3_per_agent[agent] += 1
  163. weeks = [week for week in results_per_week]
  164. weeks.sort()
  165. for week, week_data in results_per_week.items():
  166. d = datetime.datetime.strptime('2016-%d-1' % week, '%Y-%W-%w')
  167. start = (d - datetime.timedelta(days=3)).strftime('%d/%m/%Y')
  168. end = (d + datetime.timedelta(days=4)).strftime('%d/%m/%Y')
  169. print(' Semaine %s [%s 18h -> %s 18h]: P1 = %s, P2 = %s, P3 = %s' %
  170. (week, start, end, week_data[1], week_data.get(2, '--'), week_data.get(3, '--')))
  171. print('Par agent :')
  172. for agent in self.agents:
  173. print(' %s : %d P1, %d P2 + P3' % (agent, p1_per_agent[agent], p2_p3_per_agent[agent]))
  174. print('Nouvelles contraintes :')
  175. for week, week_data in results_per_week.items():
  176. print('%s: {%s},' % (week, ', '.join(["%d: '%s'" % (x, y) for (x, y) in week_data.items()])))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement