use crate::ScheduleError;
use dpm_model::{DependencyId, DependencyKind, Plan, WorkItemId};
use std::collections::{BTreeMap, BTreeSet, VecDeque};
mod calendar;
pub(crate) const EPSILON: f64 = 1e-8;
#[derive(Debug, Clone, Copy)]
pub(crate) struct Edge {
pub(crate) id: DependencyId,
pub(crate) other: usize,
pub(crate) kind: DependencyKind,
pub(crate) lag: f64,
working: bool,
}
#[derive(Debug)]
pub(crate) struct Network {
order: Vec<WorkItemId>,
positions: BTreeMap<WorkItemId, usize>,
incoming: Vec<Vec<Edge>>,
outgoing: Vec<Vec<Edge>>,
}
pub(crate) struct Times {
pub(crate) earliest: Vec<f64>,
pub(crate) latest: Vec<f64>,
pub(crate) earliest_finish: Vec<f64>,
pub(crate) latest_finish: Vec<f64>,
pub(crate) review_wait: Vec<f64>,
pub(crate) finish: f64,
}
pub(crate) fn relation_weight(
kind: DependencyKind,
predecessor: f64,
successor: f64,
lag: f64,
) -> f64 {
match kind {
DependencyKind::FinishStart => predecessor + lag,
DependencyKind::StartStart => lag,
DependencyKind::FinishFinish => predecessor - successor + lag,
DependencyKind::StartFinish => -successor + lag,
}
}
pub(crate) fn finite(work: WorkItemId, value: f64) -> Result<f64, ScheduleError> {
if !value.is_finite() {
return Err(ScheduleError::ArithmeticOverflow(work));
}
Ok(value)
}
fn at(values: &[f64], position: usize, id: WorkItemId) -> Result<f64, ScheduleError> {
values
.get(position)
.copied()
.ok_or(ScheduleError::InvalidDuration(id))
}
fn time_at(values: &[f64], position: usize) -> Result<f64, ScheduleError> {
values
.get(position)
.copied()
.ok_or(ScheduleError::UnknownPosition(position))
}
impl Times {
pub(crate) fn finishes(
&self,
network: &Network,
durations: &[f64],
position: usize,
) -> Result<(f64, f64, f64), ScheduleError> {
if self.earliest_finish.is_empty() {
let id = network
.order
.get(position)
.ok_or(ScheduleError::UnknownPosition(position))?;
let duration = at(durations, position, *id)?;
let earliest = finite(*id, time_at(&self.earliest, position)? + duration)?;
let latest = finite(*id, time_at(&self.latest, position)? + duration)?;
return Ok((earliest, latest, 0.0));
}
Ok((
time_at(&self.earliest_finish, position)?,
time_at(&self.latest_finish, position)?,
time_at(&self.review_wait, position)?,
))
}
}
impl Network {
pub(crate) fn compile(plan: &Plan) -> Result<Self, ScheduleError> {
let order = topological_order(plan)?;
plan.validate()?;
let positions: BTreeMap<_, _> = order.iter().enumerate().map(|(i, id)| (*id, i)).collect();
let mut incoming = vec![Vec::new(); order.len()];
let mut outgoing = vec![Vec::new(); order.len()];
for dep in &plan.dependencies {
let position = |id: &WorkItemId| {
positions
.get(id)
.copied()
.ok_or(ScheduleError::MissingWorkItem(*id))
};
let (from, to) = (position(&dep.predecessor)?, position(&dep.successor)?);
let edge = |other| Edge {
id: dep.id,
other,
kind: dep.kind,
lag: dep.lag_hours,
working: dep.lag_basis == dpm_model::LagBasis::Working,
};
if let Some(edges) = outgoing.get_mut(from) {
edges.push(edge(to));
}
if let Some(edges) = incoming.get_mut(to) {
edges.push(edge(from));
}
}
Ok(Self {
order,
positions,
incoming,
outgoing,
})
}
pub(crate) fn order(&self) -> &[WorkItemId] {
&self.order
}
pub(crate) fn incoming(&self, position: usize) -> &[Edge] {
self.incoming.get(position).map_or(&[], Vec::as_slice)
}
pub(crate) fn position(&self, id: &WorkItemId) -> Option<usize> {
self.positions.get(id).copied()
}
pub(crate) fn times(&self, durations: &[f64]) -> Result<Times, ScheduleError> {
let mut earliest = Vec::with_capacity(self.order.len());
for ((position, id), edges) in self.order.iter().enumerate().zip(&self.incoming) {
let duration = at(durations, position, *id)?;
let mut start: f64 = 0.0;
for edge in edges {
let before = at(durations, edge.other, *id)?;
let weight = relation_weight(edge.kind, before, duration, edge.lag);
start = start.max(finite(*id, time_at(&earliest, edge.other)? + weight)?);
}
earliest.push(start);
}
let mut finish: f64 = 0.0;
for ((id, start), duration) in self.order.iter().zip(&earliest).zip(durations) {
finish = finish.max(finite(*id, start + duration)?);
}
let mut latest: Vec<f64> = durations.iter().map(|d| finish - d).collect();
for ((position, id), edges) in self.order.iter().enumerate().zip(&self.outgoing).rev() {
let duration = at(durations, position, *id)?;
let mut bound = time_at(&latest, position)?;
for edge in edges {
let after = at(durations, edge.other, *id)?;
let weight = relation_weight(edge.kind, duration, after, edge.lag);
bound = bound.min(finite(*id, time_at(&latest, edge.other)? - weight)?);
}
if let Some(slot) = latest.get_mut(position) {
*slot = bound;
}
}
Ok(Times {
earliest_finish: Vec::new(),
latest_finish: Vec::new(),
review_wait: Vec::new(),
earliest,
latest,
finish,
})
}
pub(crate) fn free_float(
&self,
durations: &[f64],
times: &Times,
position: usize,
placement: Option<&crate::placement::Placement>,
) -> Result<f64, ScheduleError> {
if let Some(placement) = placement {
return self.free_float_placed(times, position, placement);
}
let (Some(id), Some(edges)) = (self.order.get(position), self.outgoing.get(position))
else {
return Err(ScheduleError::UnknownPosition(position));
};
let duration = at(durations, position, *id)?;
let start = time_at(×.earliest, position)?;
let mut available = finite(*id, times.finish - start - duration)?;
for edge in edges {
let after = at(durations, edge.other, *id)?;
let weight = relation_weight(edge.kind, duration, after, edge.lag);
let slack = finite(*id, time_at(×.earliest, edge.other)? - start - weight)?;
available = available.min(slack);
}
Ok(available.max(0.0))
}
pub(crate) fn relation_slacks(
&self,
durations: &[f64],
times: &Times,
placement: Option<&crate::placement::Placement>,
) -> Result<Vec<(DependencyId, usize, usize, f64)>, ScheduleError> {
let mut slacks = Vec::new();
for (position, (id, edges)) in self.order.iter().zip(&self.incoming).enumerate() {
if let Some(placement) = placement {
for (dependency, from, slack) in
self.incoming_slacks_placed(durations, times, position, placement)?
{
slacks.push((dependency, from, position, finite(*id, slack)?.max(0.0)));
}
continue;
}
let duration = at(durations, position, *id)?;
let start = time_at(×.earliest, position)?;
for edge in edges {
let before = at(durations, edge.other, *id)?;
let weight = relation_weight(edge.kind, before, duration, edge.lag);
let required = finite(*id, time_at(×.earliest, edge.other)? + weight)?;
slacks.push((edge.id, edge.other, position, (start - required).max(0.0)));
}
}
Ok(slacks)
}
pub(crate) fn total_float(&self, times: &Times, position: usize) -> Result<f64, ScheduleError> {
let Some(id) = self.order.get(position) else {
return Err(ScheduleError::UnknownPosition(position));
};
let slack = time_at(×.latest, position)? - time_at(×.earliest, position)?;
Ok(finite(*id, slack)?.max(0.0))
}
}
fn topological_order(plan: &Plan) -> Result<Vec<WorkItemId>, ScheduleError> {
let mut indegree: BTreeMap<_, usize> = plan.work_items.keys().map(|id| (*id, 0)).collect();
let mut outgoing: BTreeMap<_, Vec<_>> = BTreeMap::new();
let mut seen = BTreeSet::new();
for dep in &plan.dependencies {
if !plan.work_items.contains_key(&dep.predecessor) {
return Err(ScheduleError::MissingWorkItem(dep.predecessor));
}
if seen.insert((dep.predecessor, dep.successor)) {
outgoing
.entry(dep.predecessor)
.or_default()
.push(dep.successor);
let degree = indegree
.get_mut(&dep.successor)
.ok_or(ScheduleError::MissingWorkItem(dep.successor))?;
*degree = degree.wrapping_add(1);
}
}
let mut queue: VecDeque<_> = indegree
.iter()
.filter_map(|(id, n)| (*n == 0).then_some(*id))
.collect();
let mut order = Vec::with_capacity(plan.work_items.len());
while let Some(id) = queue.pop_front() {
order.push(id);
for child in outgoing.get(&id).into_iter().flatten() {
let degree = indegree
.get_mut(child)
.ok_or(ScheduleError::MissingWorkItem(*child))?;
*degree -= 1;
if *degree == 0 {
queue.push_back(*child);
}
}
}
if order.len() != plan.work_items.len() {
return Err(ScheduleError::DependencyCycle);
}
Ok(order)
}