///|
/// Planning utilities that bridge schedules, routes, and resource calendars.
///
/// These functions answer operational questions around a model—what is late,
/// where is capacity tight, and which task should be moved—without changing
/// the underlying solver representation.
pub struct PlanningCapacityPoint {
resource : Int
time : Int
load : Int
capacity : Int
}
///|
/// Create a capacity point.
pub fn planning_capacity_point(
resource : Int,
time : Int,
load : Int,
capacity : Int,
) -> PlanningCapacityPoint {
{ resource, time, load, capacity }
}
///|
/// Return residual capacity.
pub fn PlanningCapacityPoint::residual(self : PlanningCapacityPoint) -> Int {
self.capacity - self.load
}
///|
/// Return whether overloaded.
pub fn PlanningCapacityPoint::overloaded(self : PlanningCapacityPoint) -> Bool {
self.load > self.capacity
}
///|
/// Return all project capacity points.
pub fn project_capacity_points(
project : ProjectPlan,
starts : Array[Int],
) -> Array[PlanningCapacityPoint] {
let result : Array[PlanningCapacityPoint] = []
for resource in 0.. PlanningCapacityPoint? {
let points = project_capacity_points(project, starts)
if points.length() == 0 {
return None
}
let mut result = points[0]
for point in points {
if point.residual() < result.residual() {
result = point
}
}
Some(result)
}
///|
/// Return overloaded capacity points.
pub fn overloaded_capacity_points(
project : ProjectPlan,
starts : Array[Int],
) -> Array[PlanningCapacityPoint] {
let result : Array[PlanningCapacityPoint] = []
for point in project_capacity_points(project, starts) {
if point.overloaded() {
result.push(point)
}
}
result
}
///|
/// Return tasks sorted by slack.
pub fn tasks_by_slack(project : ProjectPlan) -> Array[Int] {
let slacks = project.slacks()
let result : Array[Int] = []
for id in 0.. Array[Int] {
let result : Array[Int] = []
for id, task in project.tasks {
if id < starts.length() &&
task.due != 2147483647 &&
starts[id] + task.duration > task.due {
result.push(id)
}
}
result
}
///|
/// Return total tardiness.
pub fn project_tardiness(project : ProjectPlan, starts : Array[Int]) -> Int {
let mut result = 0
for id in late_project_tasks(project, starts) {
let finish = starts[id] + project.tasks[id].duration
result += finish - project.tasks[id].due
}
result
}
///|
/// Return a project schedule score.
pub fn project_schedule_score(
project : ProjectPlan,
starts : Array[Int],
) -> Int {
project.makespan(starts) +
project_tardiness(project, starts) * 1000 +
project.validate_schedule(starts).length() * 1000000
}
///|
/// Shift a schedule by a constant offset.
pub fn shift_project_schedule(
project : ProjectPlan,
starts : Array[Int],
offset : Int,
) -> Array[Int] {
let result : Array[Int] = []
for index in 0.. Bool {
if left.length() != right.length() || left.length() != project.task_count() {
return false
}
for dependency in project.dependencies {
if (left[dependency.before] < left[dependency.after]) !=
(right[dependency.before] < right[dependency.after]) {
return false
}
}
true
}
///|
/// Return the first task that can be moved later without breaking successors.
pub fn movable_task(project : ProjectPlan, starts : Array[Int]) -> Int? {
if starts.length() != project.task_count() {
return None
}
let slacks = project.slacks()
for id in tasks_by_slack(project) {
if id < slacks.length() && slacks[id] > 0 {
return Some(id)
}
}
None
}
///|
/// Return the number of dependency arcs.
pub fn project_dependency_count(project : ProjectPlan) -> Int {
project.dependencies.length()
}
///|
/// Return the total number of predecessor relationships.
pub fn project_predecessor_count(project : ProjectPlan) -> Int {
let mut result = 0
for id in 0.. String {
"tasks=\{project.task_count()}, makespan=\{project.makespan(starts)}, tardiness=\{project_tardiness(project, starts)}, late=\{late_project_tasks(project, starts).length()}, errors=\{project.validate_schedule(starts).length()}"
}