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
This commit is contained in:
Diego Pino 2012-06-13 09:16:06 +02:00
parent 2706a71659
commit bc071a1734
2 changed files with 248 additions and 227 deletions

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <dpino@igalia.com>
*
* 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<Task> tasksInCriticalPath;
private MonteCarloCriticalPathBuilder(List<Task> tasksInCriticalPath) {
this.tasksInCriticalPath = tasksInCriticalPath;
}
public static MonteCarloCriticalPathBuilder create(
List<Task> 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<List<Task>> buildAllPossibleCriticalPaths() {
List<List<Task>> result = new ArrayList<List<Task>>();
if (tasksInCriticalPath.size() == 1) {
result.add(tasksInCriticalPath);
return result;
}
List<List<Task>> allPaths = new ArrayList<List<Task>>();
for (Task each : getStartingTasks(tasksInCriticalPath)) {
allPaths.addAll(allPossiblePaths(each));
}
for (List<Task> path : allPaths) {
if (isCriticalPath(path)) {
result.add(path);
}
}
return result;
}
private Collection<List<Task>> allPossiblePaths(Task task) {
Collection<List<Task>> result = new ArrayList<List<Task>>();
List<Task> path = Collections.singletonList(task);
allPossiblePaths(path, result);
return result;
}
private void allPossiblePaths(List<Task> path,
Collection<List<Task>> allPaths) {
TaskElement lastTask = getLastTask(path);
List<Task> destinations = getDestinations(lastTask);
if (!destinations.isEmpty()) {
for (Task each : destinations) {
allPossiblePaths(newPath(path, each), allPaths);
}
} else {
allPaths.add(path);
}
}
private TaskElement getLastTask(List<Task> path) {
return path.get(path.size() - 1);
}
private List<Task> newPath(List<Task> path, Task task) {
List<Task> result = new ArrayList<Task>();
result.addAll(path);
result.add(task);
return result;
}
private List<Task> getDestinations(TaskElement task) {
Set<Task> result = new HashSet<Task>();
Set<Dependency> 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<Task>(result);
}
private Set<Task> taskGroupChildren(TaskGroup taskGroup) {
Set<Task> result = new HashSet<Task>();
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<Dependency> dependencies = getIncomingDependencies(task);
return dependencies.isEmpty()
&& !parents(origins(dependencies)).contains(task.getParent());
}
private List<TaskGroup> parents(List<TaskElement> taskElements) {
List<TaskGroup> result = new ArrayList<TaskGroup>();
for (TaskElement each: taskElements) {
result.add(each.getParent());
}
return result;
}
private List<TaskElement> origins(Set<Dependency> dependencies) {
List<TaskElement> result = new ArrayList<TaskElement>();
for (Dependency each: dependencies) {
result.add(each.getOrigin());
}
return result;
}
@SuppressWarnings("unchecked")
private Set<Dependency> getIncomingDependencies(TaskElement taskElement) {
return taskElement != null ? taskElement
.getDependenciesWithThisDestination() : Collections.EMPTY_SET;
}
@SuppressWarnings("unchecked")
private Set<Dependency> 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<Task> getStartingTasks(List<Task> tasks) {
List<Task> result = new ArrayList<Task>();
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<Task> path) {
for (Task each : path) {
if (!tasksInCriticalPath.contains(each)) {
return false;
}
}
return true;
}
}

View file

@ -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<List<Task>> buildAllPossibleCriticalPaths(
List<Task> 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<List<Task>> buildAllPossibleCriticalPaths(
List<Task> tasksInCriticalPath) {
List<List<Task>> result = new ArrayList<List<Task>>();
List<List<Task>> allPaths = new ArrayList<List<Task>>();
for (Task each : getStartingTasks(tasksInCriticalPath)) {
buildAllPossiblePaths(each, new ArrayList<Task>(), allPaths);
}
for (List<Task> 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<Task> getStartingTasks(List<Task> tasks) {
List<Task> result = new ArrayList<Task>();
for (Task each : tasks) {
List<Task> origins = getOriginsFrom(each);
if (onlyTasksInCriticalPath(origins).isEmpty()) {
result.add(each);
}
}
return result;
}
private List<Task> getOriginsFrom(Task task) {
List<Task> result = new ArrayList<Task>(), 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<Task> getOriginsFrom(Set<Dependency> dependencies) {
List<Task> result = new ArrayList<Task>();
for (Dependency each : dependencies) {
TaskElement taskElement = each.getOrigin();
if (taskElement instanceof TaskGroup) {
final TaskGroup taskGroup = (TaskGroup) taskElement;
List<TaskElement> children = onlyTasksInCriticalPath(taskGroup
.getChildren());
result.addAll(toTaskIfNecessary(children));
}
if (taskElement instanceof Task) {
result.add((Task) taskElement);
}
}
return result;
}
private List<TaskElement> onlyTasksInCriticalPath(
List<? extends TaskElement> tasks) {
return onlyTasksInGroup(tasks, tasksInCriticalPath);
}
private List<TaskElement> onlyTasksInGroup(
List<? extends TaskElement> tasks, List<? extends TaskElement> group) {
List<TaskElement> result = new ArrayList<TaskElement>();
for (TaskElement each : tasks) {
if (inTaskList(each, group)) {
result.add(each);
}
}
return result;
}
private boolean inTaskList(TaskElement task, List<? extends TaskElement> 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<Task> path) {
for (Task each : path) {
if (!inTaskList(each, tasksInCriticalPath)) {
return false;
}
}
return true;
}
private void buildAllPossiblePaths(Task task, List<Task> path,
List<List<Task>> allPaths) {
List<Task> destinations = getDestinationsFrom(task);
if (destinations.size() == 0) {
path.add(task);
allPaths.add(path);
return;
}
for (Task each : destinations) {
List<Task> oldPath = copyPath(path);
path.add(task);
buildAllPossiblePaths((Task) each, path, allPaths);
path = oldPath;
}
}
private List<Task> copyPath(List<Task> path) {
List<Task> result = new ArrayList<Task>();
for (Task each : path) {
result.add(each);
}
return result;
}
private List<Task> getDestinationsFrom(Task task) {
List<Task> result = new ArrayList<Task>(), 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<Task> getDestinationsFrom(Set<Dependency> dependencies) {
List<Task> result = new ArrayList<Task>();
for (Dependency each : dependencies) {
TaskElement taskElement = each.getDestination();
if (taskElement instanceof TaskGroup) {
final TaskGroup taskGroup = (TaskGroup) taskElement;
List<TaskElement> tasks = onlyTasksWithIncomingDependenciesToOtherTasksNotInThisGroup(taskGroup
.getChildren());
result.addAll(toTaskIfNecessary(tasks));
}
if (taskElement instanceof Task) {
result.add((Task) taskElement);
}
}
return result;
}
private List<TaskElement> onlyTasksWithIncomingDependenciesToOtherTasksNotInThisGroup(
List<TaskElement> tasks) {
List<TaskElement> result = new ArrayList<TaskElement>();
for (TaskElement each : tasks) {
Set<Dependency> 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<? extends Task> toTaskIfNecessary(
List<TaskElement> taskElements) {
List<Task> result = new ArrayList<Task>();
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<MonteCarloTask> toMonteCarloTaskList(List<Task> path) {
List<MonteCarloTask> result = new ArrayList<MonteCarloTask>();
for (Task each : path) {