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:
parent
2706a71659
commit
bc071a1734
2 changed files with 248 additions and 227 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue