Cours ISC – 2014-2015 PC5 : Méthode PERT PC n°5 : Méthode PERT Question 1 : Créer le graphe pour l'exemple de construction de maison décrit dans la feuille Excel "maison1.csv". On utilise ici l'approche "Activity on Nodes" où les sommets du graphe représentent les tâches et les arcs représentent les dépendances entre les tâches (slide n°32 du cours). On rappelle que l'objectif de la méthode PERT est de déterminer entre autres : La durée minimale du projet Les dates au plus tôt et au plus tard pour chaque tâche afin que le projet ne prenne pas de retard. Les tâches qui sont sur le chemin critique La première étape dans l'élaboration du diagramme PERT consiste à construire le graphique (sommets et arcs) à partir du fichier "maison1.csv". Dans ce fichier est écrit un tableau dont chaque ligne représente une tâche du projet (ie. un sommet du graphe). Chacune de ces tâches est décrite par un code ("A", "B", "C", etc.), une description textuelle, une liste de tâches prérequises et une durée. C'est la liste des tâches prérequises qui permet de déterminer les arcs du diagramme PERT. Par exemple, on peut voir que la tâche "D" (Creusement des fondations) ne peut être réalisée qu'à partir du moment où les tâches "A" (Etude, réalisation et acceptation des plans) et "B" (Préparation du terrain) sont achevées. Il y donc une dépendance entre les tâches "A" et "D" et entre les tâches "B" et "D". Cela se traduit par la présence d'un arc entre les sommets "A" et "D" ainsi qu'entre les sommets "B" et "D" du diagramme PERT. code description D Creusement des fondations predecessors duration A, B 1 Il ne faut pas oublier d'ajouter les tâches fictives de début et de fin de projet (représentées par des cercles). Les tâches qui n'ont pas de prérequis dans "maison1.csv" ont donc néanmoins cette tâche fictive comme prérequis. Dans le diagramme, chacune de ces tâches sera donc reliée par un arc à cette tâche fictive de début de projet. Réciproquement, si une tâche n'est prérequise pour aucune autre, alors elle sera néanmoins prérequise pour la tâche fictive de fin de projet. De même, un arc reliera les tâches concernées à la tâche fictive de fin de projet dans le diagramme PERT. Voir diagramme complet dans le fichier PERT maison 1.pdf 1 Cours ISC – 2014-2015 PC5 : Méthode PERT Question 2 : Calculer les dates au plus tôt et au plus tard pour cet exemple. Les dates au plus tard ne peuvent être calculées qu'une fois que l'on connait la durée minimale du projet. Or le plus simple pour connaître la durée minimale du projet est de calculer les dates au plus tôt de chaque tâche. On commence donc par calculer les dates de début au plus tôt et de fin au plus tôt. Les dates de début au plus tard et de fin au plus tard seront calculées après. La procédure de calcul des dates au plus tôt est la suivante : On part de la tâche fictive de début de projet qui, par définition, commence à la date 0 et a une durée nulle (et se termine donc également à la date 0). On suit le ou les arc(s) du graphe à partir de ce sommet. Ainsi, la ou les tâche(s) qui dépendent de cette tâche fictive auront leur date de début au plus tôt égale à la date de fin au plus tôt celle-ci. En l'occurrence, les tâches "A" et "B" ont donc leur date de début au plus tôt égale à 0. On en déduit les dates de fin au plus tôt pour ces tâches-ci. Pour cela il suffit d'additionner la date de début au plus tôt avec la durée de la tâche. Ici, la tâche "A" s'achève donc au plus tôt à la date 0+4=4 et la tâche "B" à la date 0+2=2. On poursuit ainsi progressivement l'avancement dans le graphe en suivant les dépendances entre les tâches (les arcs du graphe). Si une tâche a plusieurs tâches prérequises alors sa date de début au plus tôt sera égale à la plus grande date de fin au plus tôt de ses prérequis. Par exemple, la tâche "D" ne peut commencer que lorsque "A" et "B" sont achevées. Or "A" termine au plus tôt à la date 4 et "B" à la date 2 donc "D" ne peut commencer au plus tôt qu'à la date 4. =max(2,4) + durée Ainsi, le calcul les dates au plus tôt pour toutes les tâches du projet permet de conclure que la durée la plus courte du projet est de 17 unités de temps. Cette date correspond à la date de fin au plus tôt de la tâche fictive de fin de projet. La procédure de calcul des dates au plus tard est similaire à la procédure de calcul des dates au plus tôt avec quelques différences : On part de la tâche fictive de fin de projet plutôt que de la tâche fictive de début. Les dates au plus tard et au plus tôt de la tâche fictive de fin de projet sont identiques par définition. On parcourt progressivement le graphe en sens inverse. Pour une tâche donnée on calcule d'abord la date de fin au plus tard puis on soustrait la durée de la tâche pour obtenir la date de début au plus tard. La date de fin au plus tard d'une tâche donnée est prise égale à la plus petite date de début au plus tard des tâches dont elle est prérequise. Par exemple, dans le cas de maison1, la date de fin au plus tard de la tâche "A" est égale à la plus petite date de début au plus tard entre les tâches "C", "D" et "E". En l'occurrence la date de fin au plus tard de "A" est donc égale à 4. En 2 Cours ISC – 2014-2015 PC5 : Méthode PERT effet, la tâche "A" ne peut pas se terminer plus tard qu'à la date 4 car la tâche "E" (qui dépend de "A") ne peut commencer au plus tard qu'à la date 4. - durée =min(4,7,9) Question 3 : Donner une caractérisation de la notion de chemin critique et déterminer le chemin critique sur l'exemple. Le chemin critique est caractérisé par l'ensemble des tâches dont les dates au plus tôt sont égales aux dates au plus tard. Ces tâches sont critiques car elles n'ont aucune marge de manœuvre temporelle. Tout retard dans l'exécution d'une de ces tâches entrainera nécessairement un retard sur la fin de projet. Pour maison1 le chemin critique correspond donc aux tâches A, E, H et J. Voir diagramme complet dans le fichier PERT maison 1.pdf Question 4 : Proposer un algorithme de calcul des dates au plus tôt et compléter le programme "mypertcalculator.py" avec cet algorithme (méthode "calculateEarlyDates" de la classe Calculator). Pour le calcul des dates au plus tôt, on propose un algorithme récursif. Pour chaque tâche du projet on compare sa date de début au plus tôt avec la date de fin au plus tôt de tous ses prédécesseurs. On rectifie la date de début au plus tôt si elle est inférieure à la date de fin au plus tôt d'un prédécesseur. 3 Cours ISC – 2014-2015 PC5 : Méthode PERT Voici le détail de l'implémentation : def CalculateEarlyDates(self, project): for task in project.tasks.values(): #boucle d'initialisation sur toutes les tâches du projet task.earlyStart = 0 task.earlyFinish = task.earlyStart + task.duration for task in project.tasks.values(): #Pour chaque tâche on appelle la sous-méthode récursive self._CalculateEarlyDates(task) def _CalculateEarlyDates(self, task): #fonction appelée récursivement for pred in task.predecessors: #On boucle sur tous les prédécesseurs de la tâche en question if pred.code != "source": #On appelle récursivement la méthode jusqu'à ce qu'on arrive à la tâche "source" self._CalculateEarlyDates(pred) if pred.earlyFinish > task.earlyStart: #On rectifie en cas d'incohérence task.earlyStart = pred.earlyFinish task.earlyFinish = task.earlyStart + task.duration Question 5 : Proposer un algorithme de calcul des dates au plus tard et compléter le programme "mypertcalculator.py" avec cet algorithme (méthode "calculateLateDates" de la classe Calculator). L'algorithme de calcul des dates au plus tard fonctionne sur un principe identique que l'algorithme de calcul des dates au plus tôt. La boucle d'initialisation des tâches est adaptée. Le prérequis pour que l'algorithme fonctionne est d'avoir déjà calculé les dates au plus tôt. Voici le détail de l'implémentation : def CalculateLateDates(self, project): self.CalculateEarlyDates(project) #le préalable est de calculer les dates au plus tôt for task in project.tasks.values(): #boucle d'initialisation sur toutes les taches du projet task.lateStart = task.earlyStart task.lateFinish = INFINITY if task.code == "target": task.lateFinish = task.lateStart for task in project.tasks.values(): #Pour chaque tâche du projet on appelle la sous-méthode récursive self._CalculateLateDates(task) def _CalculateLateDates(self, task): for succ in task.successors: #On boucle sur tous les successeurs de la tâche en question if succ.code != "target": #On appelle récursivement la méthode jusqu'à ce qu'on arrive à la tâche "target" self._CalculateLateDates(succ) if succ.lateStart < task.lateFinish: #On rectifie en cas d'incohérence task.lateFinish = succ.lateStart task.lateStart = task.lateFinish - task.duration Question 6 : Proposer un algorithme permettant d'extraire les tâches se trouvant sur un chemin critique et compléter le programme "mypertcalculator.py" avec cet algorithme (méthode "ExtractCriticalTasks" de la classe Calculator). Déterminer si une tâche est critique ou non est relativement aisé. Il suffit de regarder si les dates au plus tôt et les dates au plus tard sont identiques. Si tel est le cas, alors la tâche est sur le chemin critique. 4 Cours ISC – 2014-2015 PC5 : Méthode PERT Pour chaque tâche on calcule la marge de manœuvre (slack). Si elle est égale à zéro alors on ajoute la tâche à la liste des tâches critiques. On donne le détail du code : def DetectCriticalTasks(self, project): CriticalTasks = [] #On initialise une liste vide qui va contenir toutes les tâches critiques for task in project.tasks.values(): #On boucle sur toutes les tâches du projet pour calculer la marge de manœuvre (slack) task.slack = task.lateStart - task.earlyStart if task.slack == 0: #si la marge de manœuvre est nulle alors il s'agit d'une tâche critique CriticalTasks.append(task) #On ajoute la tâche à la fin de la liste print([t.code for t in CriticalTasks]) #affichage de la liste à l'écran Question 7 : Appliquer ces différentes méthodes sur les deux projets décrits dans les feuilles de calcul "maison1.csv" et "maison2.csv". Lorsque l'on applique ces méthodes sur le projet maison1 on obtient les mêmes résultats que ce qui avait été calculé à la main. Cette constatation est rassurante sur le fait que l'algorithme proposé (et son implémentation) est dépourvu de bogue. Il ne s'agit cependant pas d'une preuve. L'algorithme peut alors être testé sur le projet maison2. Le diagramme PERT obtenu peut être visualisé en pièce jointe. Voir PERT maison 2.pdf Une vérification manuelle des résultats rassure à nouveau sur la justesse du programme. La date de fin de projet au plus tôt pour maison2 est 34 unités de temps. Le chemin critique est A, B, C, D, G, H, I, L, N, O, P et Q. Question 8 (optionnelle) : Jusqu'à présent, nous avons supposé que le modèle est correct et en particulier qu’il ne contient pas de boucle. Compléter la méthode « DetectLoops » de la classe « Reader » de façon à détecter la présence éventuelle de boucles dans le modèle. Tester cette méthode en ajoutant une boucle dans l’un des deux projets. Il y a présence d’une boucle lorsque, par exemple, une tâche B a comme prédécesseur (direct ou indirect) une tâche A mais que cette même tâche A a également B comme prédécesseur (direct ou indirect). Une telle situation constitue un blocage car on ne peut pas démarrer B tant que A n’est pas terminé mais on ne peut pas commencer A tant que B n’est pas terminé. L’objectif ici est de vérifier, dès la lecture du fichier d’entrée, que le modèle est correct de par l’absence de boucle. La méthode « DetectLoops » a pour mission de détecter la présence d’une boucle et le cas échéant d’afficher la boucle en question à l’écran. On propose ici aussi un algorithme récursif pour parvenir à ce résultat. Le principe est de parcourir pour toutes les tâches du projet, leurs prédécesseurs. Les prédécesseurs des prédécesseurs sont parcourus de façon récursive. Si à un moment donné on retombe sur la tâche de départ alors cela signifie qu’il y a présence d’une boucle. Pour mettre en place cette stratégie, on utilise des marqueurs (flags) sur chacune des tâches. Pour chaque tâche il y a trois marqueurs possibles : « to-visit » (valeur par défaut) signifie que l’on n’a pas encore cherché à vérifier si cette tâche fait partie ou non d’une boucle. “visited” signifie que l’on est en train de vérifier si la tâche fait partie d’une boucle. 5 Cours ISC – 2014-2015 PC5 : Méthode PERT “OK” signifie que l’on est sûr que la tâche ne fait pas partie d’une boucle. Ainsi, si lors du parcours des prédécesseurs on tombe sur une tâche marquée « visited » alors on a affaire à une boucle. Inversement, si l’on ne trouve que des tâches marquées « OK » alors on est sûr que la tâche en question n’est pas part d’une boucle. On applique la procédure de façon récursive sur les tâches marquées « to-visit ». Le détail de l’implémentation est la suivante : def DetectLoops(self, project): for task in project.tasks.values(): task.flag = "to-visit" for task in project.tasks.values(): self._DetectLoops(task, []) print("No loop detected") for task in project.tasks.values(): task.flag = None # initialising all flags to "to-visit" # the _DetectLoops method is run for all the tasks in the project # if a loop is detected the program will stop here # resetting flags to None def _DetectLoops(self, task, stack): # recursive method if task.flag == "visited": # if the flag has already been visited it means that there is a loop ! stack = stack[stack.index(task.code):] # removing all irrelevant tasks in the stack before it is displayed self.RaiseError("There is a loop: %s ! There might be other loops. Exiting procedure..." %(stack)) elif task.flag == "OK": # if the flag is "OK" it means that this task has already been checked return elif task.flag == "to-visit": # if the task has not been validated then all its predecessors must be checked task.flag = "visited" stack.append(task.code) # this task is potentially part of a loop for predecessor in task.predecessors: self._DetectLoops(predecessor, stack) # if a loop is detected the program will stop here task.flag = "OK" # if all predecessors are "OK" then this task is "OK" too stack.remove(task.code) # thus this task can be removed from the stack as we know it is not in a loop return else: self.RaiseError("Unexpected flag ! Could not check loops.") Cette méthode a été testée sur le modèle maison2 – Loop1.csv avec succès. Le diagramme correspondant à ce modèle est visible dans le fichier PERT maison 2 - Loop1.pdf. There is a loop: ['E', 'Z', 'Y', 'X'] ! There might be other loops. Exiting procedure... L’algorithme a été testé sur d’autres boucles pour tester sa robustesse (absence de faux négatifs). Il a aussi été vérifié par l’exemple qu’il n’y a pas de faux positifs (détection d’une boucle alors qu’il n’y en a pas). 6