From bc071a1734d845104a2504b7d7e9d5f6b22e053b Mon Sep 17 00:00:00 2001 From: Diego Pino Date: Wed, 13 Jun 2012 09:16:06 +0200 Subject: [PATCH] Bug #1275: Montecarlo combo for selecting critical path is empty * Move code for calculating all critical paths to its own class * The former code broke in many clases so I reimplemented the algorithm FEA: ItEr76S04BugFixing --- .../MonteCarloCriticalPathBuilder.java | 241 ++++++++++++++++++ .../web/montecarlo/MonteCarloModel.java | 234 +---------------- 2 files changed, 248 insertions(+), 227 deletions(-) create mode 100644 libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloCriticalPathBuilder.java diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloCriticalPathBuilder.java b/libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloCriticalPathBuilder.java new file mode 100644 index 000000000..0243f0955 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloCriticalPathBuilder.java @@ -0,0 +1,241 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * Copyright (C) 2010-2012 Igalia, S.L. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.libreplan.web.montecarlo; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.libreplan.business.planner.entities.Dependency; +import org.libreplan.business.planner.entities.Task; +import org.libreplan.business.planner.entities.TaskElement; +import org.libreplan.business.planner.entities.TaskGroup; + +/** + * + * @author Diego Pino García + * + * Constructs all the possible critical paths, departing from a list of + * elements that contain all the tasks which are in the critical path. + * The algorithm determines first all the possible starting tasks and + * navigates them forward until reaching an end. + * + * Navigating from a {@link Task} to a {@link TaskGroup} is a bit + * troublesome. The algorithm considers than when there is a link + * between a task and a taskgroup, the task is connected with all the + * taskgroup's children which have: a) no incoming dependencies b) has + * no incoming dependencies from a task that it's not a children of that + * taskgroup. + * + * Why the list of tasks in the critical path is not the only one + * critical path? It could be the case some of the tasks in that list + * finish at the same time (in parallel for instance). In those cases + * there are many critical paths and it's what this classes solves. + */ +public class MonteCarloCriticalPathBuilder { + + private List tasksInCriticalPath; + + private MonteCarloCriticalPathBuilder(List tasksInCriticalPath) { + this.tasksInCriticalPath = tasksInCriticalPath; + } + + public static MonteCarloCriticalPathBuilder create( + List tasksInCriticalPath) { + return new MonteCarloCriticalPathBuilder(tasksInCriticalPath); + } + + /** + * Constructs all possible paths starting from those tasks in the critical + * path have no incoming dependencies or have incoming dependencies to other + * tasks not in the critical path. + * + * Once all possible path were constructed, filter only those paths which + * all their tasks are in the list of tasks in the critical path + * + * @param tasksInCriticalPath + * @return + */ + public List> buildAllPossibleCriticalPaths() { + List> result = new ArrayList>(); + if (tasksInCriticalPath.size() == 1) { + result.add(tasksInCriticalPath); + return result; + } + List> allPaths = new ArrayList>(); + for (Task each : getStartingTasks(tasksInCriticalPath)) { + allPaths.addAll(allPossiblePaths(each)); + } + for (List path : allPaths) { + if (isCriticalPath(path)) { + result.add(path); + } + } + return result; + } + + private Collection> allPossiblePaths(Task task) { + Collection> result = new ArrayList>(); + List path = Collections.singletonList(task); + allPossiblePaths(path, result); + return result; + } + + private void allPossiblePaths(List path, + Collection> allPaths) { + TaskElement lastTask = getLastTask(path); + List destinations = getDestinations(lastTask); + if (!destinations.isEmpty()) { + for (Task each : destinations) { + allPossiblePaths(newPath(path, each), allPaths); + } + } else { + allPaths.add(path); + } + } + + private TaskElement getLastTask(List path) { + return path.get(path.size() - 1); + } + + private List newPath(List path, Task task) { + List result = new ArrayList(); + result.addAll(path); + result.add(task); + return result; + } + + private List getDestinations(TaskElement task) { + Set result = new HashSet(); + Set dependencies = getOutgoingDependencies(task); + TaskGroup parent = task.getParent(); + if (parent != null) { + if (parent.getEndDate().equals(task.getEndDate())) { + result.addAll(getDestinations((task.getParent()))); + } + } + for (Dependency each : dependencies) { + TaskElement destination = each.getDestination(); + if (isTask(destination)) { + result.add((Task) destination); + } + if (isTaskGroup(destination)) { + result.addAll(taskGroupChildren((TaskGroup) destination)); + } + } + return new ArrayList(result); + } + + private Set taskGroupChildren(TaskGroup taskGroup) { + Set result = new HashSet(); + for (TaskElement child : taskGroup.getChildren()) { + if (isTask(child) && isStartingTask((Task) child)) { + result.add((Task) child); + } + if (isTaskGroup(child)) { + result.addAll(taskGroupChildren((TaskGroup) child)); + } + } + return result; + } + + /** + * A startingTask in a TaskGroup is a task that: + * a) Has no incoming dependencies + * b) All their incoming dependencies are from tasks in a different group + * + * @param task + * @return + */ + private boolean isStartingTask(Task task) { + Set dependencies = getIncomingDependencies(task); + return dependencies.isEmpty() + && !parents(origins(dependencies)).contains(task.getParent()); + } + + private List parents(List taskElements) { + List result = new ArrayList(); + for (TaskElement each: taskElements) { + result.add(each.getParent()); + } + return result; + } + + private List origins(Set dependencies) { + List result = new ArrayList(); + for (Dependency each: dependencies) { + result.add(each.getOrigin()); + } + return result; + } + + @SuppressWarnings("unchecked") + private Set getIncomingDependencies(TaskElement taskElement) { + return taskElement != null ? taskElement + .getDependenciesWithThisDestination() : Collections.EMPTY_SET; + } + + @SuppressWarnings("unchecked") + private Set getOutgoingDependencies(TaskElement taskElement) { + return (taskElement != null) ? taskElement + .getDependenciesWithThisOrigin() : Collections.EMPTY_SET; + } + + private boolean isTaskGroup(TaskElement taskElement) { + return taskElement instanceof TaskGroup; + } + + private boolean isTask(TaskElement taskElement) { + return taskElement instanceof Task; + } + + private List getStartingTasks(List tasks) { + List result = new ArrayList(); + for (Task each : tasks) { + if (isStartingTask(each) && noneParentHasIncomingDependencies(each)) { + result.add(each); + } + } + return result; + } + + private boolean noneParentHasIncomingDependencies(Task each) { + TaskGroup parent = each.getParent(); + while (parent != null && getIncomingDependencies(parent).isEmpty()) { + parent = parent.getParent(); + } + return (parent == null); + } + + private boolean isCriticalPath(List path) { + for (Task each : path) { + if (!tasksInCriticalPath.contains(each)) { + return false; + } + } + return true; + } + +} \ No newline at end of file diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloModel.java index f245ca01a..6fbc6fd56 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/montecarlo/MonteCarloModel.java @@ -26,7 +26,6 @@ import static org.libreplan.web.I18nHelper._; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -46,7 +45,6 @@ import org.libreplan.business.planner.daos.ITaskElementDAO; import org.libreplan.business.planner.entities.Dependency; import org.libreplan.business.planner.entities.Task; import org.libreplan.business.planner.entities.TaskElement; -import org.libreplan.business.planner.entities.TaskGroup; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @@ -61,18 +59,6 @@ import org.zkoss.ganttz.util.LongOperationFeedback.IDesktopUpdatesEmitter; * list of tasks represents a critical path. There could be many * critical paths in scheduling. * - * A big chunk of code goes for determining all the possible critical - * paths, departing from a list of elements that contains all the tasks - * which are in the critical path. The algorithm determines first all - * the possible starting tasks and navigates them forward until reaching - * an end. - * - * Navigating from a task to a taskgroup is a bit cumbersome. The - * algorithm considers than when there is a link between a task a - * taskgroup, the task is connected with all the taskgroup's children - * which have: a) no incoming dependencies b) has no incoming - * dependencies from a task that it's not a children of that taskgroup. - * */ @Component @Scope(BeanDefinition.SCOPE_PROTOTYPE) @@ -123,6 +109,13 @@ public class MonteCarloModel implements IMonteCarloModel { } } + private List> buildAllPossibleCriticalPaths( + List tasksInCriticalPath) { + MonteCarloCriticalPathBuilder criticalPathBuilder = MonteCarloCriticalPathBuilder + .create(tasksInCriticalPath); + return criticalPathBuilder.buildAllPossibleCriticalPaths(); + } + /** * Calculating all the critical paths, may need to explore other tasks that * are not part of the tasks that are on the critical path. So it's @@ -188,219 +181,6 @@ public class MonteCarloModel implements IMonteCarloModel { return result; } - /** - * Constructs all possible paths starting from those tasks in the critical - * path have no incoming dependencies or have incoming dependencies to other - * tasks not in the critical path. - * - * Once all possible path were constructed, filter only those paths which - * all their tasks are in the list of tasks in the critical path - * - * @param tasksInCriticalPath - * @return - */ - private List> buildAllPossibleCriticalPaths( - List tasksInCriticalPath) { - - List> result = new ArrayList>(); - List> allPaths = new ArrayList>(); - - for (Task each : getStartingTasks(tasksInCriticalPath)) { - buildAllPossiblePaths(each, new ArrayList(), allPaths); - } - for (List path : allPaths) { - if (isCriticalPath(path)) { - result.add(path); - } - } - return result; - } - - /** - * Returns the list of starting tasks - * - * A task is a starting task if a) has no incoming dependencies b) the only - * incoming dependencies that it has are to other tasks which are no tasks in - * the critical path - * - * @param tasks - * @return - */ - private List getStartingTasks(List tasks) { - List result = new ArrayList(); - for (Task each : tasks) { - List origins = getOriginsFrom(each); - if (onlyTasksInCriticalPath(origins).isEmpty()) { - result.add(each); - } - } - return result; - } - - private List getOriginsFrom(Task task) { - List result = new ArrayList(), tasks; - - tasks = getOriginsFrom(task.getDependenciesWithThisDestination()); - if (!tasks.isEmpty()) { - result.addAll(tasks); - } else { - tasks = getOriginsFrom(task.getParent() - .getDependenciesWithThisDestination()); - if (!tasks.isEmpty()) { - result.addAll(tasks); - } - } - return result; - } - - private List getOriginsFrom(Set dependencies) { - List result = new ArrayList(); - for (Dependency each : dependencies) { - TaskElement taskElement = each.getOrigin(); - if (taskElement instanceof TaskGroup) { - final TaskGroup taskGroup = (TaskGroup) taskElement; - List children = onlyTasksInCriticalPath(taskGroup - .getChildren()); - result.addAll(toTaskIfNecessary(children)); - } - if (taskElement instanceof Task) { - result.add((Task) taskElement); - } - } - return result; - } - - private List onlyTasksInCriticalPath( - List tasks) { - return onlyTasksInGroup(tasks, tasksInCriticalPath); - } - - private List onlyTasksInGroup( - List tasks, List group) { - List result = new ArrayList(); - for (TaskElement each : tasks) { - if (inTaskList(each, group)) { - result.add(each); - } - } - return result; - } - - private boolean inTaskList(TaskElement task, List tasks) { - for (TaskElement each : tasks) { - if (isSameTask(task, each)) { - return true; - } - } - return false; - } - - private boolean isSameTask(TaskElement task1, TaskElement task2) { - return task1.getOrderElement().getCode() - .equals(task2.getOrderElement().getCode()); - } - - private boolean isCriticalPath(List path) { - for (Task each : path) { - if (!inTaskList(each, tasksInCriticalPath)) { - return false; - } - } - return true; - } - - private void buildAllPossiblePaths(Task task, List path, - List> allPaths) { - List destinations = getDestinationsFrom(task); - if (destinations.size() == 0) { - path.add(task); - allPaths.add(path); - return; - } - for (Task each : destinations) { - List oldPath = copyPath(path); - path.add(task); - buildAllPossiblePaths((Task) each, path, allPaths); - path = oldPath; - } - } - - private List copyPath(List path) { - List result = new ArrayList(); - for (Task each : path) { - result.add(each); - } - return result; - } - - private List getDestinationsFrom(Task task) { - List result = new ArrayList(), tasks; - - tasks = getDestinationsFrom(task.getDependenciesWithThisOrigin()); - if (!tasks.isEmpty()) { - result.addAll(tasks); - } else { - tasks = getDestinationsFrom(task.getParent() - .getDependenciesWithThisOrigin()); - if (!tasks.isEmpty()) { - result.addAll(tasks); - } - } - return result; - } - - private List getDestinationsFrom(Set dependencies) { - List result = new ArrayList(); - for (Dependency each : dependencies) { - TaskElement taskElement = each.getDestination(); - if (taskElement instanceof TaskGroup) { - final TaskGroup taskGroup = (TaskGroup) taskElement; - List tasks = onlyTasksWithIncomingDependenciesToOtherTasksNotInThisGroup(taskGroup - .getChildren()); - result.addAll(toTaskIfNecessary(tasks)); - } - if (taskElement instanceof Task) { - result.add((Task) taskElement); - } - } - return result; - } - - private List onlyTasksWithIncomingDependenciesToOtherTasksNotInThisGroup( - List tasks) { - List result = new ArrayList(); - for (TaskElement each : tasks) { - Set dependencies = each - .getDependenciesWithThisDestination(); - if (dependencies.isEmpty()) { - result.add(each); - continue; - } - for (Dependency dependency : dependencies) { - TaskElement origin = dependency.getOrigin(); - if (origin instanceof Task && !inTaskList(origin, tasks)) { - result.add(origin); - } - } - } - return result; - } - - private Collection toTaskIfNecessary( - List taskElements) { - List result = new ArrayList(); - for (TaskElement each : taskElements) { - if (each instanceof TaskGroup) { - final TaskGroup taskGroup = (TaskGroup) each; - result.addAll(toTaskIfNecessary(taskGroup.getChildren())); - } - if (each instanceof Task) { - result.add((Task) each); - } - } - return result; - } - private List toMonteCarloTaskList(List path) { List result = new ArrayList(); for (Task each : path) {