apsis-rs/crates/ops/apsis-task/src/precedence.rs

317 lines
13 KiB
Rust

//! Precedence constraints between tasks.
//!
//! A precedence constraint says that one task must finish before another
//! starts. A set of such constraints forms a directed acyclic graph, and a
//! scheduler must respect it: a task cannot be placed until every task that
//! must precede it has been placed and has finished.
//!
//! The graph is stored as adjacency lists. It rejects a cycle when an edge is
//! added, because a cycle is not a set of constraints that can be satisfied
//! and would otherwise produce an empty topological order that a caller
//! might mistake for a scheduling failure rather than a modelling error.
//!
//! The precedence graph is a separate type from [`Task`](crate::Task)
//! because the constraints are a property of the set of tasks rather than of
//! any one task, and because a caller may want to plan the same tasks under
//! different constraint graphs.
//!
//! 任务之间的先后约束。
//!
//! 一条先后约束说一个任务必须在另一个开始之前完成。一组这样的约束构成有向无环图,
//! 而调度器必须遵守它:一个任务在其必须先行的每个任务都被放置并完成之前无法被
//! 放置。
//!
//! 该图以邻接表存储。它在添加边时拒绝环,因为环不是一组可满足的约束,且否则会产生
//! 一个空的拓扑序,调用方可能把它误认为调度失败而非建模错误。
//!
//! 先后关系图是与 [`Task`](crate::Task) 不同的类型,因为约束是任务集合的性质而非
//! 任一单个任务的性质,也因为调用方可能想在不同约束图下规划同一批任务。
use crate::error::{TaskError, TaskErrorKind};
use crate::task::TaskId;
use std::collections::BTreeMap;
/// A directed acyclic graph of precedence constraints.
///
/// An edge from one task to another means the first must finish before the
/// second starts. The order of the tasks themselves is the caller's: the
/// graph only records which pairs are related.
///
/// 优先约束构成的有向无环图。
///
/// 从一个任务指向另一个任务的边表示前者必须先于后者完成。任务本身的次序
/// 由调用方决定:该图只记录哪些任务对之间存在关系。
#[derive(Debug, Clone, PartialEq, Default)]
pub struct PrecedenceGraph {
/// The successors of each task, in the order the edges were added.
///
/// 各任务的后继,按边添加顺序排列。
successors: BTreeMap<usize, Vec<usize>>,
/// The predecessors of each task, in the order the edges were added.
///
/// 各任务的前驱,按边添加顺序排列。
predecessors: BTreeMap<usize, Vec<usize>>,
}
impl PrecedenceGraph {
/// Creates an empty graph.
///
/// 创建一个空图。
pub fn new() -> Self {
Self {
successors: BTreeMap::new(),
predecessors: BTreeMap::new(),
}
}
/// Returns the number of edges.
///
/// 返回边的数量。
pub fn edge_count(&self) -> usize {
self.successors.values().map(Vec::len).sum()
}
/// Returns `true` when there are no edges.
///
/// 当没有边时返回 `true`。
pub fn is_empty(&self) -> bool {
self.successors.values().all(Vec::is_empty)
}
/// Returns `true` when the constraint from `before` to `after` exists.
///
/// 当存在从 `before` 到 `after` 的约束时返回 `true`。
pub fn has_constraint(&self, before: TaskId, after: TaskId) -> bool {
self.successors
.get(&before.index())
.is_some_and(|successors| successors.contains(&after.index()))
}
/// Adds a constraint that `before` must finish before `after` starts.
///
/// Returns an error when the two identifiers are the same, because a task
/// cannot precede itself, or when the constraint would close a cycle.
///
/// Adding a constraint that already exists is accepted and has no effect,
/// so a caller may assemble a graph from overlapping sources without
/// having to deduplicate.
///
/// 添加一条约束:`before` 必须在 `after` 开始之前完成。
///
/// 两个标识符相同时返回错误,因为任务不能先于自身;该约束会闭成环时
/// 亦返回错误。
///
/// 添加已存在的约束被接受且不起作用,故调用方可以从多个有重叠的数据源
/// 汇集一个图,无须先去重。
pub fn add(&mut self, before: TaskId, after: TaskId) -> Result<(), TaskError> {
if before == after {
return Err(TaskError::new(TaskErrorKind::PrecedenceCycle)
.with_detail(format!("{before} cannot precede itself")));
}
if self.has_constraint(before, after) {
return Ok(());
}
// A new edge from `before` to `after` closes a cycle exactly when
// `before` is already reachable from `after`. That is checked before
// the edge is inserted, so a rejected edge leaves the graph
// unchanged.
// 从 `before` 到 `after` 的新边恰在 `before` 已可自 `after` 到达时
// 闭成环。该检查先于插入,故被拒的边不会改变图。
if self.is_reachable(after, before) {
return Err(
TaskError::new(TaskErrorKind::PrecedenceCycle).with_detail(format!(
"{before} must precede {after}, but {after} already precedes {before}"
)),
);
}
self.successors
.entry(before.index())
.or_default()
.push(after.index());
self.predecessors
.entry(after.index())
.or_default()
.push(before.index());
Ok(())
}
/// Returns `true` when `to` is reachable from `from` following the
/// constraints.
///
/// 当沿约束可自 `from` 到达 `to` 时返回 `true`。
pub fn is_reachable(&self, from: TaskId, to: TaskId) -> bool {
if from == to {
return true;
}
let mut visited: Vec<usize> = Vec::new();
let mut stack = vec![from.index()];
while let Some(node) = stack.pop() {
if node == to.index() {
return true;
}
if visited.contains(&node) {
continue;
}
visited.push(node);
if let Some(successors) = self.successors.get(&node) {
stack.extend(successors.iter().copied());
}
}
false
}
/// Returns the tasks that must finish before a task starts.
///
/// The result is in the order the constraints were added. It is the
/// direct predecessors only, not the transitive closure.
///
/// 返回必须在某任务开始前完成的任务。
///
/// 结果按约束添加顺序排列。它只含直接前驱,不含传递闭包。
pub fn predecessors_of(&self, task: TaskId) -> Vec<TaskId> {
self.predecessors
.get(&task.index())
.map(|list| list.iter().map(|index| TaskId::new(*index)).collect())
.unwrap_or_default()
}
/// Returns the tasks that must wait for a task to finish.
///
/// The result is in the order the constraints were added. It is the
/// direct successors only, not the transitive closure.
///
/// 返回必须等待某任务完成的任务。
///
/// 结果按约束添加顺序排列。它只含直接后继,不含传递闭包。
pub fn successors_of(&self, task: TaskId) -> Vec<TaskId> {
self.successors
.get(&task.index())
.map(|list| list.iter().map(|index| TaskId::new(*index)).collect())
.unwrap_or_default()
}
/// Returns the tasks with no predecessor.
///
/// 返回没有前驱的任务。
pub fn roots(&self) -> Vec<TaskId> {
// Every task that appears as a successor has a predecessor, so the
// roots are the tasks that appear only as a predecessor.
// 凡出现为后继的任务都有前驱,故根是那些只作为前驱出现的任务。
let mut roots: Vec<usize> = Vec::new();
for task in self.successors.keys() {
if !self.predecessors.contains_key(task) && !roots.contains(task) {
roots.push(*task);
}
}
roots.sort_unstable();
roots.into_iter().map(TaskId::new).collect()
}
/// Returns the tasks with no successor.
///
/// 返回没有后继的任务。
pub fn leaves(&self) -> Vec<TaskId> {
let mut leaves: Vec<usize> = Vec::new();
for task in self.predecessors.keys() {
if !self.successors.contains_key(task) && !leaves.contains(task) {
leaves.push(*task);
}
}
leaves.sort_unstable();
leaves.into_iter().map(TaskId::new).collect()
}
/// Returns a topological order of the given tasks.
///
/// The order respects every constraint: a task appears after all of its
/// predecessors. Ties are broken by the position of the task in the
/// supplied list, so that a caller can impose its own preference among
/// tasks that are otherwise unordered. That is what lets a planner place
/// the more valuable tasks first within the freedom the constraints
/// allow.
///
/// Tasks in the list that have no constraints still appear.
///
/// Returns an error when the graph contains a cycle among the given
/// tasks. The graph rejects cycles as they are added, so this can only
/// happen when a caller builds the graph from one task set and orders
/// another, or when the tasks are not all in the graph.
///
/// 返回所给任务的拓扑序。
///
/// 该次序满足每一条约束:任务出现在其所有前驱之后。平局按该任务在所给
/// 列表中的位置打破,使调用方可在本来无序的任务间表达自己的偏好。这
/// 正是让规划器在约束允许的自由度内先放置更有价值的任务。
///
/// 列表中无任何约束的任务同样出现。
///
/// 当图在所给任务之间含环时返回错误。该图在添加时就拒绝成环,故这只
/// 可能发生在调用方以一组任务建图却对另一组排序,或任务未全在图中时。
pub fn topological_order(&self, tasks: &[TaskId]) -> Result<Vec<TaskId>, TaskError> {
// The position of each task in the supplied list, which breaks ties.
// 各任务在所给列表中的位置,用于打破平局。
let mut preference: BTreeMap<usize, usize> = BTreeMap::new();
for (position, task) in tasks.iter().enumerate() {
preference.insert(task.index(), position);
}
// The number of predecessors of each task that are themselves in the
// list. A predecessor outside the list is ignored, because it cannot
// be scheduled and so cannot be waited for.
// 各任务在列表内的前驱个数。列表外的前驱被忽略,因为它无法被
// 调度,也就无从等待。
let mut pending: BTreeMap<usize, usize> = BTreeMap::new();
for task in tasks {
let count = self
.predecessors_of(*task)
.iter()
.filter(|predecessor| preference.contains_key(&predecessor.index()))
.count();
pending.insert(task.index(), count);
}
let mut order: Vec<usize> = Vec::with_capacity(tasks.len());
let mut placed: Vec<usize> = Vec::with_capacity(tasks.len());
while order.len() < tasks.len() {
// Among the tasks whose predecessors are all placed, take the
// one the caller prefers.
// 在前驱均已放置的任务中,取调用方偏好者。
let next = tasks
.iter()
.map(|task| task.index())
.filter(|index| !placed.contains(index))
.filter(|index| pending.get(index).copied().unwrap_or(0) == 0)
.min_by_key(|index| preference.get(index).copied().unwrap_or(usize::MAX));
let Some(next) = next else {
return Err(TaskError::new(TaskErrorKind::PrecedenceCycle)
.with_detail("no task is free of unplaced predecessors"));
};
placed.push(next);
order.push(next);
for successor in self.successors_of(TaskId::new(next)) {
if let Some(count) = pending.get_mut(&successor.index()) {
*count = count.saturating_sub(1);
}
}
}
Ok(order.into_iter().map(TaskId::new).collect())
}
/// Returns the number of tasks in the graph.
///
/// 返回图中的任务数。
pub fn task_count(&self) -> usize {
let mut all: Vec<usize> = self.successors.keys().copied().collect();
for task in self.predecessors.keys() {
if !all.contains(task) {
all.push(*task);
}
}
all.len()
}
}