docs: bilingual comments for apsis-conflict

This commit is contained in:
ApsisRS contributors 2026-10-02 01:04:12 +08:00
parent 175abb3f68
commit bbd3cb0c3f
2 changed files with 132 additions and 0 deletions

View File

@ -5,18 +5,30 @@ use apsis_task::TaskId;
use core::fmt; use core::fmt;
/// The kind of problem a conflict describes. /// The kind of problem a conflict describes.
///
/// 冲突所描述的问题类别。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive] #[non_exhaustive]
pub enum ConflictKind { pub enum ConflictKind {
/// Two placements need more of a resource than it has. /// Two placements need more of a resource than it has.
///
/// 两处放置所需的资源超过其容量。
Capacity, Capacity,
/// A placement falls outside the time window of its task. /// A placement falls outside the time window of its task.
///
/// 某处放置落在其任务的时间窗口之外。
Window, Window,
/// A placement does not agree with its task. /// A placement does not agree with its task.
///
/// 某处放置与其任务不一致。
Requirement, Requirement,
/// A placement falls outside the availability of a resource. /// A placement falls outside the availability of a resource.
///
/// 某处放置落在某资源的可用性之外。
Availability, Availability,
/// A task that must follow another starts too early. /// A task that must follow another starts too early.
///
/// 某任务未待其前驱完成即过早开始。
Precedence, Precedence,
} }
@ -38,28 +50,50 @@ impl fmt::Display for ConflictKind {
/// The conflict names the tasks involved and, where it applies, the resource /// The conflict names the tasks involved and, where it applies, the resource
/// and the instant at which the problem occurs. That is enough for an /// and the instant at which the problem occurs. That is enough for an
/// operator to locate the placement without re-deriving the analysis. /// operator to locate the placement without re-deriving the analysis.
///
/// 计划中发现的单个问题。
///
/// 冲突指明所涉任务,并在适用时指明资源及问题发生的时刻。这足以让操作
/// 者定位放置,而无须重新推导分析。
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Conflict { pub struct Conflict {
/// The kind of problem. /// The kind of problem.
///
/// 问题类别。
pub kind: ConflictKind, pub kind: ConflictKind,
/// The task the conflict is centred on. /// The task the conflict is centred on.
///
/// 该冲突所围绕的任务。
pub task: TaskId, pub task: TaskId,
/// The other task involved, for a conflict between two placements. /// The other task involved, for a conflict between two placements.
///
/// 所涉的另一个任务,用于两处放置之间的冲突。
pub other: Option<TaskId>, pub other: Option<TaskId>,
/// The resource involved, where the conflict is about one. /// The resource involved, where the conflict is about one.
///
/// 所涉资源,当冲突与之相关时。
pub resource: Option<ResourceId>, pub resource: Option<ResourceId>,
/// The time at which the problem occurs, in seconds. /// The time at which the problem occurs, in seconds.
/// ///
/// For a capacity conflict this is the instant at which the demand /// For a capacity conflict this is the instant at which the demand
/// exceeds the capacity. For a window conflict it is the offending edge /// exceeds the capacity. For a window conflict it is the offending edge
/// of the placement. /// of the placement.
///
/// 问题发生的时刻,单位为秒。
///
/// 对容量冲突而言,是需求超出容量的那一瞬;对窗口冲突而言,是该
/// 放置的越界之处。
pub at: f64, pub at: f64,
/// A description of the problem. /// A description of the problem.
///
/// 该问题的说明。
pub detail: String, pub detail: String,
} }
impl Conflict { impl Conflict {
/// Creates a conflict. /// Creates a conflict.
///
/// 创建一个冲突。
pub fn new(kind: ConflictKind, task: TaskId, at: f64, detail: impl Into<String>) -> Self { pub fn new(kind: ConflictKind, task: TaskId, at: f64, detail: impl Into<String>) -> Self {
Self { Self {
kind, kind,
@ -72,12 +106,16 @@ impl Conflict {
} }
/// Returns a copy of this conflict involving another task. /// Returns a copy of this conflict involving another task.
///
/// 返回涉及另一任务的本冲突副本。
pub fn with_other(mut self, other: TaskId) -> Self { pub fn with_other(mut self, other: TaskId) -> Self {
self.other = Some(other); self.other = Some(other);
self self
} }
/// Returns a copy of this conflict involving a resource. /// Returns a copy of this conflict involving a resource.
///
/// 返回涉及某资源的本冲突副本。
pub fn with_resource(mut self, resource: ResourceId) -> Self { pub fn with_resource(mut self, resource: ResourceId) -> Self {
self.resource = Some(resource); self.resource = Some(resource);
self self
@ -89,6 +127,11 @@ impl Conflict {
/// A capacity or availability conflict can often be repaired by moving /// A capacity or availability conflict can often be repaired by moving
/// the placement, while a requirement conflict means the placement does /// the placement, while a requirement conflict means the placement does
/// not belong in the schedule at all. /// not belong in the schedule at all.
///
/// 当该冲突涉及一处应当移动而非丢弃的放置时返回 `true`。
///
/// 容量或可用性冲突常可通过移动放置来修复,而需求冲突意味着该
/// 放置本就不应出现在计划中。
pub const fn is_movable(&self) -> bool { pub const fn is_movable(&self) -> bool {
matches!( matches!(
self.kind, self.kind,
@ -120,6 +163,11 @@ impl fmt::Display for Conflict {
/// follows the plan. The counts by kind are provided because a planner /// follows the plan. The counts by kind are provided because a planner
/// usually wants to know how many of each problem there are before deciding /// usually wants to know how many of each problem there are before deciding
/// what to do. /// what to do.
///
/// 计划中发现的所有冲突。
///
/// 报告按时间、再按类别排序,故阅读它即是在阅读该计划。各类计数一并
/// 提供,因为规划器通常希望先知道每类问题各有若干,再决定对策。
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default)]
pub struct ConflictReport { pub struct ConflictReport {
conflicts: Vec<Conflict>, conflicts: Vec<Conflict>,
@ -132,6 +180,11 @@ impl ConflictReport {
/// ///
/// The conflicts are sorted by time, then by kind, then by task, so that /// The conflicts are sorted by time, then by kind, then by task, so that
/// the order does not depend on the order they were found. /// the order does not depend on the order they were found.
///
/// 由一组冲突与任务计数创建报告。
///
/// 冲突按时间、类别、任务依次排序,故其次序不取决于发现它们的
/// 顺序。
pub fn new(mut conflicts: Vec<Conflict>, offered_tasks: usize, placed_tasks: usize) -> Self { pub fn new(mut conflicts: Vec<Conflict>, offered_tasks: usize, placed_tasks: usize) -> Self {
conflicts.sort_by(|first, second| { conflicts.sort_by(|first, second| {
first first
@ -150,11 +203,15 @@ impl ConflictReport {
} }
/// Returns the conflicts, in time order. /// Returns the conflicts, in time order.
///
/// 返回各冲突,按时间排序。
pub fn conflicts(&self) -> &[Conflict] { pub fn conflicts(&self) -> &[Conflict] {
&self.conflicts &self.conflicts
} }
/// Returns the number of conflicts. /// Returns the number of conflicts.
///
/// 返回冲突数量。
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.conflicts.len() self.conflicts.len()
} }
@ -163,6 +220,10 @@ impl ConflictReport {
/// ///
/// This is the conventional spelling of [`ConflictReport::is_clear`], and /// This is the conventional spelling of [`ConflictReport::is_clear`], and
/// the two always agree. /// the two always agree.
///
/// 当没有冲突时返回 `true`。
///
/// 这是 [`ConflictReport::is_clear`] 的惯用拼写,二者始终一致。
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.conflicts.is_empty() self.conflicts.is_empty()
} }
@ -171,16 +232,25 @@ impl ConflictReport {
/// ///
/// A clear report means the schedule is feasible with respect to every /// A clear report means the schedule is feasible with respect to every
/// check this crate performs, not that it is optimal. /// check this crate performs, not that it is optimal.
///
/// 当没有冲突时返回 `true`。
///
/// 报告无冲突意味着该计划就本 crate 执行的每项检查而言是可行的,
/// 并不意味着它最优。
pub fn is_clear(&self) -> bool { pub fn is_clear(&self) -> bool {
self.conflicts.is_empty() self.conflicts.is_empty()
} }
/// Returns `true` when there is at least one conflict. /// Returns `true` when there is at least one conflict.
///
/// 当至少存在一个冲突时返回 `true`。
pub fn has_conflicts(&self) -> bool { pub fn has_conflicts(&self) -> bool {
!self.conflicts.is_empty() !self.conflicts.is_empty()
} }
/// Returns the number of conflicts of one kind. /// Returns the number of conflicts of one kind.
///
/// 返回某一类别的冲突数量。
pub fn count_of(&self, kind: ConflictKind) -> usize { pub fn count_of(&self, kind: ConflictKind) -> usize {
self.conflicts self.conflicts
.iter() .iter()
@ -189,6 +259,8 @@ impl ConflictReport {
} }
/// Returns the conflicts of one kind, in time order. /// Returns the conflicts of one kind, in time order.
///
/// 返回某一类别的冲突,按时间排序。
pub fn of_kind(&self, kind: ConflictKind) -> Vec<&Conflict> { pub fn of_kind(&self, kind: ConflictKind) -> Vec<&Conflict> {
self.conflicts self.conflicts
.iter() .iter()
@ -200,6 +272,11 @@ impl ConflictReport {
/// ///
/// A task appears once even when it has several conflicts, which is what /// A task appears once even when it has several conflicts, which is what
/// a repair pass wants: the set of tasks to reconsider. /// a repair pass wants: the set of tasks to reconsider.
///
/// 返回至少卷入一个冲突的任务。
///
/// 同一任务即使有多个冲突也只出现一次,这正是一个修复轮所需:待重新
/// 考虑的任务集合。
pub fn conflicting_tasks(&self) -> Vec<TaskId> { pub fn conflicting_tasks(&self) -> Vec<TaskId> {
let mut tasks: Vec<TaskId> = Vec::new(); let mut tasks: Vec<TaskId> = Vec::new();
for conflict in &self.conflicts { for conflict in &self.conflicts {
@ -216,16 +293,22 @@ impl ConflictReport {
} }
/// Returns the number of tasks that were offered to the schedule. /// Returns the number of tasks that were offered to the schedule.
///
/// 返回提交给该计划的任务数量。
pub fn offered_tasks(&self) -> usize { pub fn offered_tasks(&self) -> usize {
self.offered_tasks self.offered_tasks
} }
/// Returns the number of tasks that were placed. /// Returns the number of tasks that were placed.
///
/// 返回已被放置的任务数量。
pub fn placed_tasks(&self) -> usize { pub fn placed_tasks(&self) -> usize {
self.placed_tasks self.placed_tasks
} }
/// Returns a one line summary of the report. /// Returns a one line summary of the report.
///
/// 返回该报告的单行摘要。
pub fn summary(&self) -> String { pub fn summary(&self) -> String {
format!( format!(
"{} conflict(s) among {} placed of {} offered task(s)", "{} conflict(s) among {} placed of {} offered task(s)",
@ -241,6 +324,11 @@ impl ConflictReport {
/// The order runs from the problems that invalidate a plan to the ones that /// The order runs from the problems that invalidate a plan to the ones that
/// merely make it expensive, so that reading the report top to bottom /// merely make it expensive, so that reading the report top to bottom
/// addresses the most serious problems first. /// addresses the most serious problems first.
///
/// 返回使各类别按可读次序分组的排序键。
///
/// 该次序从使计划失效的问题排到仅使计划昂贵的问题,故自顶向下阅读报告
/// 时先处理最严重者。
const fn kind_order(kind: ConflictKind) -> u8 { const fn kind_order(kind: ConflictKind) -> u8 {
match kind { match kind {
ConflictKind::Requirement => 0, ConflictKind::Requirement => 0,

View File

@ -6,22 +6,32 @@ use apsis_schedule::Schedule;
use apsis_task::{Task, TaskId}; use apsis_task::{Task, TaskId};
/// A precedence constraint: one task must finish before another starts. /// A precedence constraint: one task must finish before another starts.
///
/// 一条优先约束:一个任务必须先于另一个开始而完成。
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct Precedence { pub struct Precedence {
/// The task that must finish first. /// The task that must finish first.
///
/// 必须先完成的任务。
pub before: TaskId, pub before: TaskId,
/// The task that must start after. /// The task that must start after.
///
/// 必须后开始的任务。
pub after: TaskId, pub after: TaskId,
} }
impl Precedence { impl Precedence {
/// Creates a precedence constraint. /// Creates a precedence constraint.
///
/// 创建一条优先约束。
pub const fn new(before: TaskId, after: TaskId) -> Self { pub const fn new(before: TaskId, after: TaskId) -> Self {
Self { before, after } Self { before, after }
} }
} }
/// Returns the placements of a task, which is at most one. /// Returns the placements of a task, which is at most one.
///
/// 返回某任务的放置,至多一处。
pub fn placements_of(schedule: &Schedule, task: TaskId) -> Option<&apsis_schedule::Placement> { pub fn placements_of(schedule: &Schedule, task: TaskId) -> Option<&apsis_schedule::Placement> {
schedule.placement_of(task) schedule.placement_of(task)
} }
@ -45,11 +55,28 @@ pub fn placements_of(schedule: &Schedule, task: TaskId) -> Option<&apsis_schedul
/// total demand on a resource is constant between them, so the check is exact /// total demand on a resource is constant between them, so the check is exact
/// and takes time proportional to the number of placements rather than to the /// and takes time proportional to the number of placements rather than to the
/// length of the plan. /// length of the plan.
///
/// 返回计划中的所有冲突。
///
/// 检查是完整执行的:报告包含所有发现的问题,而非只有首个。这是有意
/// 的,因为拿到一份计划的规划器通常希望先知道它有多糟,再决定修复还是
/// 重建。
///
/// 检查项为:
///
/// - 每处放置的任务都在列表中,且时长一致;
/// - 每处放置都落在其任务窗口内;
/// - 任何瞬时没有资源超出其容量;
/// - 每处放置都落在其所要求的每个资源的可用窗口内。
///
/// 资源扫掠检查各放置的边界,因为资源上的总需求在相邻边界之间恒定,
/// 故该检查是精确的,耗时正比于放置数而非计划长度。
pub fn detect(schedule: &Schedule, tasks: &[Task], resources: &ResourceSet) -> ConflictReport { pub fn detect(schedule: &Schedule, tasks: &[Task], resources: &ResourceSet) -> ConflictReport {
let mut conflicts = Vec::new(); let mut conflicts = Vec::new();
// The requirement and window checks need only the placement and its // The requirement and window checks need only the placement and its
// task. // task.
// 需求与窗口检查只需放置及其任务。
for placement in schedule.placements() { for placement in schedule.placements() {
let task = tasks.iter().find(|task| task.id == placement.task); let task = tasks.iter().find(|task| task.id == placement.task);
let Some(task) = task else { let Some(task) = task else {
@ -104,6 +131,7 @@ pub fn detect(schedule: &Schedule, tasks: &[Task], resources: &ResourceSet) -> C
// The availability check: a placement must fall inside a window of every // The availability check: a placement must fall inside a window of every
// resource it requires. // resource it requires.
// 可用性检查:每处放置必须落在其所要求的每个资源的某个窗口内。
for placement in schedule.placements() { for placement in schedule.placements() {
let Some(task) = tasks.iter().find(|task| task.id == placement.task) else { let Some(task) = tasks.iter().find(|task| task.id == placement.task) else {
continue; continue;
@ -123,6 +151,7 @@ pub fn detect(schedule: &Schedule, tasks: &[Task], resources: &ResourceSet) -> C
}; };
// A demand greater than the capacity can never be met, which is a // A demand greater than the capacity can never be met, which is a
// requirement problem rather than a placement problem. // requirement problem rather than a placement problem.
// 超过容量的需求永远无法满足,故属需求问题而非放置问题。
if requirement.demand > resource.capacity() + 1e-9 { if requirement.demand > resource.capacity() + 1e-9 {
conflicts.push( conflicts.push(
Conflict::new( Conflict::new(
@ -158,9 +187,11 @@ pub fn detect(schedule: &Schedule, tasks: &[Task], resources: &ResourceSet) -> C
} }
// The capacity check, one resource at a time. // The capacity check, one resource at a time.
// 容量检查,逐个资源进行。
for resource in resources.resources() { for resource in resources.resources() {
// Each event is a time, a change in demand, and the task responsible, // Each event is a time, a change in demand, and the task responsible,
// so that a conflict can name the pair. // so that a conflict can name the pair.
// 每个事件含时刻、需求变化与责任任务,使冲突得以指明这一对。
let mut events: Vec<(f64, f64, TaskId)> = Vec::new(); let mut events: Vec<(f64, f64, TaskId)> = Vec::new();
for placement in schedule.placements() { for placement in schedule.placements() {
let Some(task) = tasks.iter().find(|task| task.id == placement.task) else { let Some(task) = tasks.iter().find(|task| task.id == placement.task) else {
@ -176,6 +207,7 @@ pub fn detect(schedule: &Schedule, tasks: &[Task], resources: &ResourceSet) -> C
} }
// A release sorts before an acquisition at the same instant, so two // A release sorts before an acquisition at the same instant, so two
// placements that touch are not counted as overlapping. // placements that touch are not counted as overlapping.
// 同一瞬先排释放后排获取,使两个相接的放置不计为重叠。
events.sort_by(|first, second| { events.sort_by(|first, second| {
first first
.0 .0
@ -203,6 +235,8 @@ pub fn detect(schedule: &Schedule, tasks: &[Task], resources: &ResourceSet) -> C
// Report the pair that first pushes the resource over, so // Report the pair that first pushes the resource over, so
// that the conflict names two tasks rather than only the one // that the conflict names two tasks rather than only the one
// that arrived last. // that arrived last.
// 报告首次把该资源推过量的一对,使冲突指明两个任务而
// 不仅是最后到达的那个。
let mut involved = holders.clone(); let mut involved = holders.clone();
involved.sort(); involved.sort();
let first = involved.first().copied(); let first = involved.first().copied();
@ -223,6 +257,8 @@ pub fn detect(schedule: &Schedule, tasks: &[Task], resources: &ResourceSet) -> C
// Report each resource once, at the first instant it is over // Report each resource once, at the first instant it is over
// committed. A second report at a later instant would add // committed. A second report at a later instant would add
// nothing a repair pass can use. // nothing a repair pass can use.
// 每个资源只报告一次,取其首次超订的时刻。较晚时刻
// 的第二次报告对一个修复轮毫无用处。
break; break;
} }
} }
@ -240,6 +276,14 @@ pub fn detect(schedule: &Schedule, tasks: &[Task], resources: &ResourceSet) -> C
/// ///
/// The constraints are checked as given, so they must be acyclic. A cycle is /// The constraints are checked as given, so they must be acyclic. A cycle is
/// a contradiction in the caller's own model and is not detected here. /// a contradiction in the caller's own model and is not detected here.
///
/// 返回一组约束中的优先冲突。
///
/// 当本应居次的任务在应居首者完成之前开始,即为优先冲突。若某约束的两
/// 个任务未被同时放置,则不构成冲突,因为对它们未作任何承诺。
///
/// 约束按给定形式检查,故必须无环。环是调用方自身模型中的矛盾,此处
/// 不予检测。
pub fn detect_precedence(schedule: &Schedule, constraints: &[Precedence]) -> Vec<Conflict> { pub fn detect_precedence(schedule: &Schedule, constraints: &[Precedence]) -> Vec<Conflict> {
let mut conflicts = Vec::new(); let mut conflicts = Vec::new();
for constraint in constraints { for constraint in constraints {