private static TimeSpan GetWork(ProjectData project, TaskId taskId) { var hasAssignments = project.GetAssignments(taskId).Any(); if (!hasAssignments) { return(project.Get(TaskFields.Duration, taskId)); } return(project.Get(TaskFields.Work, taskId)); }
private static void ComputeEarlyStartAndFinish(ProjectData project, TaskId taskId, TaskLink predecessorLink, out DateTimeOffset earlyStart, out DateTimeOffset earlyFinish) { var calendar = project.Information.Calendar; var type = predecessorLink.Type; var predecessorEarlyStart = project.Get(TaskFields.EarlyStart, predecessorLink.PredecessorId); var predecessorEarlyFinish = project.Get(TaskFields.EarlyFinish, predecessorLink.PredecessorId); switch (type) { case TaskLinkType.FinishToStart: earlyStart = predecessorEarlyFinish; break; case TaskLinkType.StartToStart: earlyStart = predecessorEarlyStart; break; case TaskLinkType.FinishToFinish: earlyFinish = predecessorEarlyFinish; ComputeStart(project, taskId, out earlyStart, ref earlyFinish); break; case TaskLinkType.StartToFinish: earlyFinish = predecessorEarlyStart; ComputeStart(project, taskId, out earlyStart, ref earlyFinish); break; default: throw new Exception($"Unexpected case label {type}"); } if (predecessorLink.Lag != TimeSpan.Zero) { earlyStart = project.Information.Calendar.AddWork(earlyStart, predecessorLink.Lag); } var mustSnap = type == TaskLinkType.FinishToFinish; if (mustSnap && earlyStart < project.Information.Start) { earlyStart = project.Information.Start; } ComputeFinish(project, taskId, ref earlyStart, out earlyFinish); // In case of Start-to-Finish, the end date is snapped to a start time // unless there is positive lag. if (type == TaskLinkType.StartToFinish && predecessorLink.Lag <= TimeSpan.Zero) { earlyFinish = calendar.FindWorkStart(earlyFinish); } }
private static ProjectData SetAssignmentWork(ProjectData project, AssignmentId id, TimeSpan value) { var taskId = project.Get(AssignmentFields.TaskId, id); var oldTaskWork = project.Get(TaskFields.Work, taskId); var oldAssignmentWork = project.Get(AssignmentFields.Work, id); var newAssignmentWork = value; var newTaskWork = oldTaskWork + newAssignmentWork - oldAssignmentWork; return(project.SetRaw(TaskFields.Work, taskId, newTaskWork) .SetRaw(AssignmentFields.Work, id, newAssignmentWork)); }
private static ProjectData Finalize(this ProjectData project) { var calendar = project.Information.Calendar; foreach (var taskId in project.Tasks) { var earlyStart = project.Get(TaskFields.EarlyStart, taskId); var earlyFinish = project.Get(TaskFields.EarlyFinish, taskId); var lateStart = project.Get(TaskFields.LateStart, taskId); var lateFinish = project.Get(TaskFields.LateFinish, taskId); // Set start, finish, and duration var start = earlyStart; var finish = earlyFinish; var duration = calendar.GetWork(start, finish); project = project.SetRaw(TaskFields.Start, taskId, start) .SetRaw(TaskFields.Finish, taskId, finish) .SetRaw(TaskFields.Duration, taskId, duration); // Set start slack, finish slack, total slack, and criticality var startSlack = calendar.GetWork(earlyStart, lateStart); var finishSlack = calendar.GetWork(earlyFinish, lateFinish); var totalSlack = startSlack <= finishSlack ? startSlack : finishSlack; var isCritical = totalSlack == TimeSpan.Zero; project = project.SetRaw(TaskFields.StartSlack, taskId, startSlack) .SetRaw(TaskFields.FinishSlack, taskId, finishSlack) .SetRaw(TaskFields.TotalSlack, taskId, totalSlack) .SetRaw(TaskFields.IsCritical, taskId, isCritical); // Set free slack var freeSlack = GetFreeSlack(project, taskId); project = project.SetRaw(TaskFields.FreeSlack, taskId, freeSlack); } // Set assignment start and finish foreach (var assignmentId in project.Assignments) { var taskId = project.Get(AssignmentFields.TaskId, assignmentId); var taskStart = project.Get(TaskFields.Start, taskId); var assignmentWork = project.Get(AssignmentFields.Work, assignmentId); var assignmentUnits = project.Get(AssignmentFields.Units, assignmentId); var assignmentStart = taskStart; var duration = TimeSpan.FromHours(assignmentWork.TotalHours / assignmentUnits); ComputeFinish(project.Information.Calendar, ref assignmentStart, out var assignmentFinish, duration); project = project.SetRaw(AssignmentFields.Start, assignmentId, assignmentStart) .SetRaw(AssignmentFields.Finish, assignmentId, assignmentFinish); } return(project); }
private static ProjectData BackwardPass(this ProjectData project) { var computedTasks = new HashSet <TaskId>(); var toBeScheduled = new Queue <TaskId>(project.Tasks); while (toBeScheduled.Count > 0) { var taskId = toBeScheduled.Dequeue(); var successors = project.Get(TaskFields.SuccessorLinks, taskId); var allSuccessorsComputed = successors.All(l => computedTasks.Contains(l.SuccessorId)); if (!allSuccessorsComputed) { toBeScheduled.Enqueue(taskId); } else { ComputeLateStartAndFinish(project, taskId, successors, out var lateStart, out var lateFinish); project = project.SetRaw(TaskFields.LateStart, taskId, lateStart) .SetRaw(TaskFields.LateFinish, taskId, lateFinish); computedTasks.Add(taskId); } } return(project); }
private static DateTimeOffset ComputeProjectFinish(ProjectData project) { return(project.Tasks .Select(t => project.Get(TaskFields.EarlyFinish, t)) .DefaultIfEmpty(project.Information.Start) .Max()); }
private static ProjectData ForwardPass(this ProjectData project) { var computedTasks = new HashSet <TaskId>(); var toBeScheduled = new Queue <TaskId>(project.Tasks); while (toBeScheduled.Count > 0) { var taskId = toBeScheduled.Dequeue(); var predecessors = project.Get(TaskFields.PredecessorLinks, taskId); var allPredecessorsComputed = predecessors.All(l => computedTasks.Contains(l.PredecessorId)); if (!allPredecessorsComputed) { toBeScheduled.Enqueue(taskId); } else { ComputeEarlyStartAndFinish(project, taskId, predecessors, out var earlyStart, out var earlyFinish); project = project.SetRaw(TaskFields.EarlyStart, taskId, earlyStart) .SetRaw(TaskFields.EarlyFinish, taskId, earlyFinish); computedTasks.Add(taskId); } } var projectFinish = ComputeProjectFinish(project); var information = project.Information.WithFinish(projectFinish); return(project.WithInformation(information)); }
private static TimeSpan GetFreeSlack(ProjectData project, TaskId taskId) { var successors = project.Get(TaskFields.SuccessorLinks, taskId); if (successors.Any()) { return(successors.Select(l => GetFreeSlack(project, l)).Min()); } else { var calendar = project.Information.Calendar; var earlyFinish = project.Get(TaskFields.EarlyFinish, taskId); var projectFinish = project.Information.Finish; return(calendar.GetWork(earlyFinish, projectFinish)); } }
private static ProjectData SetAssignmentUnits(ProjectData project, AssignmentId id, double value) { var taskId = project.Get(AssignmentFields.TaskId, id); return(project.SetRaw(AssignmentFields.Units, id, value) .Reset(TaskFields.ResourceNames, taskId) .Reset(TaskFields.ResourceInitials, taskId)); }
private static ProjectData SetTaskWork(ProjectData project, TaskId id, TimeSpan value) { project = project.SetRaw(TaskFields.Work, id, value); if (value == TimeSpan.Zero) { foreach (var assignmentId in project.GetAssignments(id)) { project = project.SetRaw(AssignmentFields.Work, assignmentId, TimeSpan.Zero); } } else { var totalExistingWork = TimeSpan.Zero; foreach (var assignmentId in project.GetAssignments(id)) { totalExistingWork += project.Get(AssignmentFields.Work, assignmentId); } var assignmentCount = project.GetAssignments(id).Count(); foreach (var assignmentId in project.GetAssignments(id)) { var assignmentWork = project.Get(AssignmentFields.Work, assignmentId); double newHours; if (totalExistingWork > TimeSpan.Zero) { newHours = assignmentWork.TotalHours / totalExistingWork.TotalHours * value.TotalHours; } else { newHours = value.TotalHours / assignmentCount; } var newWork = TimeSpan.FromHours(newHours); project = project.SetRaw(AssignmentFields.Work, assignmentId, newWork); } } return(project); }
private static TimeSpan GetFreeSlack(ProjectData project, TaskLink link) { var calendar = project.Information.Calendar; var projectStart = project.Information.Start; var predecessorEarlyStart = project.Get(TaskFields.EarlyStart, link.PredecessorId); var predecessorEarlyFinish = project.Get(TaskFields.EarlyFinish, link.PredecessorId); var successorEarlyStart = project.Get(TaskFields.EarlyStart, link.SuccessorId); var successorEarlyFinish = project.Get(TaskFields.EarlyFinish, link.SuccessorId); switch (link.Type) { case TaskLinkType.FinishToStart: return(calendar.GetWork(predecessorEarlyFinish, successorEarlyStart) - link.Lag); case TaskLinkType.StartToStart: return(calendar.GetWork(predecessorEarlyStart, successorEarlyStart) - link.Lag); case TaskLinkType.FinishToFinish: { // Finish-to-Finish is special in that it won't schedule tasks before the project's // start date. Thus, we'll ignore the lag if the successor was supposed to end before // us (i.e. had a negative slack) but ended up ending after ours (i.e. it couldn't // start any earlier). var successorCannotStartAnyEarlier = successorEarlyStart == projectStart && successorEarlyFinish > predecessorEarlyFinish && link.Lag < TimeSpan.Zero; if (successorCannotStartAnyEarlier) { return(calendar.GetWork(predecessorEarlyFinish, successorEarlyFinish)); } return(calendar.GetWork(predecessorEarlyFinish, successorEarlyFinish) - link.Lag); } case TaskLinkType.StartToFinish: return(calendar.GetWork(predecessorEarlyStart, successorEarlyFinish) - link.Lag); default: throw new Exception($"Unexpected case label {link.Type}"); } }
private static ProjectData SetTaskDuration(ProjectData project, TaskId id, TimeSpan value) { project = project.SetRaw(TaskFields.Duration, id, value); var taskFinish = project.Get(TaskFields.Finish, id); foreach (var assignmentId in project.GetAssignments(id)) { var assignmentFinish = project.Get(AssignmentFields.Finish, assignmentId); var assignmentUnits = project.Get(AssignmentFields.Units, assignmentId); if (assignmentFinish == taskFinish) { var assignmentWork = TimeSpan.FromHours(value.TotalHours * assignmentUnits); project = project.Set(AssignmentFields.Work, assignmentId, assignmentWork); } } return(project); }
private static void ComputeLateStartAndFinish(ProjectData project, TaskId taskId, TaskLink successorLink, out DateTimeOffset lateStart, out DateTimeOffset lateFinish) { var calendar = project.Information.Calendar; switch (successorLink.Type) { case TaskLinkType.FinishToStart: lateFinish = project.Get(TaskFields.LateStart, successorLink.SuccessorId); break; case TaskLinkType.StartToStart: lateStart = project.Get(TaskFields.LateStart, successorLink.SuccessorId); ComputeFinish(project, taskId, ref lateStart, out lateFinish); break; case TaskLinkType.FinishToFinish: lateFinish = project.Get(TaskFields.LateFinish, successorLink.SuccessorId); break; case TaskLinkType.StartToFinish: lateStart = project.Get(TaskFields.LateFinish, successorLink.SuccessorId); ComputeFinish(project, taskId, ref lateStart, out lateFinish); break; default: throw new Exception($"Unexpected case label {successorLink.Type}"); } if (successorLink.Lag != TimeSpan.Zero) { lateFinish = project.Information.Calendar.AddWork(lateFinish, -successorLink.Lag); } // TODO: Is this correct? We probably need to align with with ComputeEarlyStart. if (lateFinish > project.Information.Finish) { lateFinish = project.Information.Finish; } ComputeStart(project, taskId, out lateStart, ref lateFinish); }
private static ProjectData SetTaskOrdinal(ProjectData project, TaskId id, int value) { if (value < 0 || value >= project.TaskMap.Count) { throw new ArgumentOutOfRangeException(nameof(value)); } var oldOrdinal = project.Get(TaskFields.Ordinal, id); var newOrdinal = value; var orderedTaskIds = project.Tasks .OrderBy(t => project.Get(TaskFields.Ordinal, t)) .ToList(); orderedTaskIds.RemoveAt(oldOrdinal); orderedTaskIds.Insert(newOrdinal, id); // First we update all ordinals for (var i = 0; i < orderedTaskIds.Count; i++) { var taskId = orderedTaskIds[i]; var ordinal = i; project = project.SetRaw(TaskFields.Ordinal, taskId, ordinal); } // Then we can update all predecessors/successors foreach (var taskId in orderedTaskIds) { project = project.Reset(TaskFields.Predecessors, taskId) .Reset(TaskFields.Successors, taskId); } return(project); }
private static ProjectData SetTaskPredecessorsOrSuccessors(ProjectData project, TaskId id, string value, bool isSuccessors) { var linkField = isSuccessors ? TaskFields.SuccessorLinks : TaskFields.PredecessorLinks; value = value.Trim(); var predecessorsBuilder = ImmutableArray.CreateBuilder <TaskId>(); var remainingTaskLinks = project.Get(linkField, id).ToList(); if (value.Length > 0) { var parts = value.Split(','); foreach (var part in parts) { var partText = part.Trim(); var taskText = partText; var sign = Math.Max(partText.IndexOf('+'), partText.IndexOf('-')); var lag = TimeSpan.Zero; if (sign >= 0) { var lagText = partText.Substring(sign).Trim(); lag = project.Information.TimeConversion.ParseDuration(lagText); taskText = partText.Substring(0, sign).Trim(); } var taskOrdinalText = taskText; var linkType = (TaskLinkType?)null; if (taskText.EndsWith("FS", StringComparison.OrdinalIgnoreCase)) { linkType = TaskLinkType.FinishToStart; } else if (taskText.EndsWith("SS", StringComparison.OrdinalIgnoreCase)) { linkType = TaskLinkType.StartToStart; } else if (taskText.EndsWith("FF", StringComparison.OrdinalIgnoreCase)) { linkType = TaskLinkType.FinishToFinish; } else if (taskText.EndsWith("SF", StringComparison.OrdinalIgnoreCase)) { linkType = TaskLinkType.StartToFinish; } if (linkType != null) { taskOrdinalText = taskText.Substring(0, taskText.Length - 2).Trim(); } else if (sign < 0) { linkType = TaskLinkType.FinishToStart; } else { throw new FormatException($"'{partText}' isn't a valid value"); } if (!int.TryParse(taskOrdinalText, out var taskOrdinal)) { throw new FormatException($"'{taskOrdinalText}' isn't a valid int"); } var taskId = project.GetTask(taskOrdinal); if (taskId.IsDefault) { throw new FormatException($"'{taskOrdinal}' isn't a valid task"); } var predecessorId = isSuccessors ? id : taskId; var successorId = isSuccessors ? taskId : id; var taskLink = TaskLink.Create(predecessorId, successorId, linkType.Value, lag); var existingLink = project.GetTaskLink(predecessorId, successorId); if (existingLink != null) { project = project.RemoveTaskLink(existingLink); } else if (project.TaskLinkCausesCycle(taskLink)) { var predecessorOrdinal = project.Get(TaskFields.Ordinal, predecessorId); var successorOrdinal = project.Get(TaskFields.Ordinal, successorId); throw new InvalidOperationException($"Cannot add a link from task {predecessorOrdinal} to task {successorOrdinal} as this would cause a cycle."); } project = project.AddTaskLink(taskLink); remainingTaskLinks.Remove(taskLink); } } foreach (var taskLink in remainingTaskLinks) { project = project.RemoveTaskLink(taskLink); } return(project); }