docs: bilingual comments for apsis-scheduler
The layer is complete at three hundred and sixty two blocks across the resource, task, schedule, conflict and scheduler crates, including the swept search and the improvement pass.
This commit is contained in:
parent
efddfd6f79
commit
500f9775db
|
|
@ -10,6 +10,11 @@ use apsis_task::{Task, TaskId};
|
|||
/// A task that demands more of a resource than the resource has can never be
|
||||
/// scheduled on it, so this is checked before the search for a time. Returns
|
||||
/// `false` when a required resource does not exist.
|
||||
///
|
||||
/// 当任务的每个资源都有容量容纳其需求时返回 `true`。
|
||||
///
|
||||
/// 需求超过某资源容量的任务永远无法在其上调度,故在搜索时刻之前先检查
|
||||
/// 此点。所需资源不存在时返回 `false`。
|
||||
pub fn resources_have_capacity(task: &Task, resources: &ResourceSet) -> bool {
|
||||
task.requirements.iter().all(|requirement| {
|
||||
resources
|
||||
|
|
@ -23,6 +28,10 @@ pub fn resources_have_capacity(task: &Task, resources: &ResourceSet) -> bool {
|
|||
///
|
||||
/// The result is in the order the resources appear in the set. A resource
|
||||
/// that is not in the set is omitted.
|
||||
///
|
||||
/// 给定一份计划及其任务集合,返回某瞬时各资源上的总需求。
|
||||
///
|
||||
/// 结果按资源在集合中的次序排列。不在集合中的资源被省略。
|
||||
pub fn demand_at(
|
||||
schedule: &Schedule,
|
||||
tasks: &[Task],
|
||||
|
|
@ -55,6 +64,11 @@ pub fn demand_at(
|
|||
/// the demand already placed on it leaves room for one more unit. This is
|
||||
/// the query a watermark scheduler makes when it looks for a satellite with
|
||||
/// spare capacity.
|
||||
///
|
||||
/// 返回在某瞬空闲的资源标识符。
|
||||
///
|
||||
/// 资源空闲当且仅当其可用窗口覆盖该瞬,且其上已放置的需求还留有一个单
|
||||
/// 位的余量。这正是水位调度器寻找有余量卫星时所作的查询。
|
||||
pub fn resources_free_at(
|
||||
schedule: &Schedule,
|
||||
tasks: &[Task],
|
||||
|
|
@ -88,6 +102,11 @@ pub fn resources_free_at(
|
|||
/// The check is that every required resource is available for the whole
|
||||
/// duration and that the demand already placed on it, plus the new demand,
|
||||
/// stays within the capacity at every instant of the placement.
|
||||
///
|
||||
/// 当某任务在某时刻的放置可行时返回 `true`。
|
||||
///
|
||||
/// 检查内容为:每个所需资源在整个时长上可用,且其上已放置的需求加上
|
||||
/// 新需求,在放置的每个瞬时都不超过容量。
|
||||
pub fn feasible_at(
|
||||
task: &Task,
|
||||
tasks: &[Task],
|
||||
|
|
@ -106,6 +125,12 @@ pub fn feasible_at(
|
|||
/// placed task while collecting the boundaries, and the one inside that sums
|
||||
/// the demand over every placement covering a boundary. The boundary
|
||||
/// arithmetic is unchanged, so the answer is the same as the scans give.
|
||||
///
|
||||
/// 使用调用方已构建一次的需求索引,判断某任务在某时刻的放置是否可行。
|
||||
///
|
||||
/// 该索引取代了对任务列表的两次线性扫描:收集边界时查找已放置任务的那
|
||||
/// 次,以及对每个覆盖该边界的放置求和需求的那次。边界运算本身不变,故
|
||||
/// 结果与扫描给出的一致。
|
||||
pub(crate) fn feasible_at_indexed(
|
||||
task: &Task,
|
||||
schedule: &Schedule,
|
||||
|
|
@ -128,6 +153,8 @@ pub(crate) fn feasible_at_indexed(
|
|||
// overlap it. Because the demand of each placement is constant over
|
||||
// its own interval, it is enough to test the boundaries of the
|
||||
// overlapping placements, where the total is greatest.
|
||||
// 新任务的需求须与与其重叠的放置并存。由于每个放置的需求在其
|
||||
// 自身区间内恒定,只需检查重叠放置的边界,即总量最大之处。
|
||||
let mut boundaries: Vec<f64> = vec![start];
|
||||
for placement in schedule.placements() {
|
||||
if crate::sweep::demand_on(index, placement.task, resource.id).is_none() {
|
||||
|
|
@ -159,6 +186,11 @@ pub(crate) fn feasible_at_indexed(
|
|||
/// has, when a resource it requires does not exist, or when its window is
|
||||
/// shorter than its duration. These cannot be repaired by rescheduling and
|
||||
/// are worth reporting separately from the tasks that were merely left out.
|
||||
///
|
||||
/// 返回某组资源完全无法容纳的任务标识符。
|
||||
///
|
||||
/// 任务不可行的情形有:需求超过某资源容量;所需资源不存在;窗口短于其
|
||||
/// 时长。这些无法通过重排修复,值得与那些只是被排除的任务分开报告。
|
||||
pub fn infeasible_tasks(tasks: &[Task], resources: &ResourceSet) -> Vec<TaskId> {
|
||||
tasks
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -32,20 +32,31 @@ use apsis_schedule::Schedule;
|
|||
use apsis_task::{Task, TaskId, TaskState};
|
||||
|
||||
/// The settings of the improvement pass.
|
||||
///
|
||||
/// 改进轮的设置。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ImprovementSettings {
|
||||
/// Whether the pass runs.
|
||||
///
|
||||
/// 该轮是否运行。
|
||||
pub enabled: bool,
|
||||
/// The greatest number of blocking placements to try per task.
|
||||
///
|
||||
/// Each attempt costs two feasibility searches. A small bound keeps the
|
||||
/// pass predictable; a larger one finds more placements on a crowded
|
||||
/// schedule at proportional cost.
|
||||
///
|
||||
/// 每个任务最多尝试的阻塞放置数。
|
||||
///
|
||||
/// 每次尝试耗费两次可行性搜索。较小的界使该轮表现可预期;较大的界
|
||||
/// 能在拥挤的计划上找到更多放置,代价相应增加。
|
||||
pub max_attempts_per_task: usize,
|
||||
}
|
||||
|
||||
impl Default for ImprovementSettings {
|
||||
/// Returns settings that run the pass with four attempts per task.
|
||||
///
|
||||
/// 返回每任务尝试四次的设置。
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
|
|
@ -56,6 +67,8 @@ impl Default for ImprovementSettings {
|
|||
|
||||
impl ImprovementSettings {
|
||||
/// Creates settings with the given bound.
|
||||
///
|
||||
/// 以给定界创建设置。
|
||||
pub const fn new(max_attempts_per_task: usize) -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
|
|
@ -64,6 +77,8 @@ impl ImprovementSettings {
|
|||
}
|
||||
|
||||
/// Returns settings that do not run the pass.
|
||||
///
|
||||
/// 返回不运行该轮的设置。
|
||||
pub const fn disabled() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
|
|
@ -73,21 +88,31 @@ impl ImprovementSettings {
|
|||
}
|
||||
|
||||
/// What the improvement pass changed.
|
||||
///
|
||||
/// 改进轮所作的改变。
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct ImprovementOutcome {
|
||||
/// The identifiers of the tasks the pass placed.
|
||||
///
|
||||
/// 该轮所放置的任务标识符。
|
||||
pub placed: Vec<TaskId>,
|
||||
/// The identifiers of the tasks the pass moved to make room.
|
||||
///
|
||||
/// 该轮为腾出空隙而移动的任务标识符。
|
||||
pub moved: Vec<TaskId>,
|
||||
}
|
||||
|
||||
impl ImprovementOutcome {
|
||||
/// Returns the number of tasks the pass placed.
|
||||
///
|
||||
/// 返回该轮所放置的任务数。
|
||||
pub fn placed_count(&self) -> usize {
|
||||
self.placed.len()
|
||||
}
|
||||
|
||||
/// Returns `true` when the pass changed nothing.
|
||||
///
|
||||
/// 当该轮未作任何改变时返回 `true`。
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.placed.is_empty() && self.moved.is_empty()
|
||||
}
|
||||
|
|
@ -101,6 +126,14 @@ impl ImprovementOutcome {
|
|||
/// assignment. The schedule is modified in place.
|
||||
///
|
||||
/// Returns a record of what the pass placed and moved.
|
||||
///
|
||||
/// 通过搬迁阻塞它们的放置,尝试安排被推迟的任务。
|
||||
///
|
||||
/// `deferred` 是贪心轮无法放置的任务,按考虑顺序排列。`states` 按标识符
|
||||
/// 索引保存每个任务的状态并就地更新,故调用方看到的是最终指派。该计划
|
||||
/// 亦被就地修改。
|
||||
///
|
||||
/// 返回记录该轮所放置与所移动内容的报告。
|
||||
pub fn improve(
|
||||
tasks: &[Task],
|
||||
resources: &ResourceSet,
|
||||
|
|
@ -119,6 +152,7 @@ pub fn improve(
|
|||
continue;
|
||||
};
|
||||
// A task that is not deferred has been placed by an earlier step.
|
||||
// 非被推迟的任务已由先前步骤放置。
|
||||
if schedule.is_placed(*id) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -164,6 +198,11 @@ pub fn improve(
|
|||
///
|
||||
/// Only these can block it: a placement on a resource the task does not need
|
||||
/// consumes capacity that the task was never going to use.
|
||||
///
|
||||
/// 返回与某等待任务共用资源的已放置任务,按时间排序。
|
||||
///
|
||||
/// 只有这些能阻塞它:某个该任务并不需要的资源上的放置,消耗的是它本就
|
||||
/// 不会用到的容量。
|
||||
fn blocking_tasks(waiting: &Task, tasks: &[Task], schedule: &Schedule) -> Vec<TaskId> {
|
||||
let mut blockers: Vec<(f64, TaskId)> = schedule
|
||||
.placements()
|
||||
|
|
@ -196,6 +235,11 @@ fn blocking_tasks(waiting: &Task, tasks: &[Task], schedule: &Schedule) -> Vec<Ta
|
|||
///
|
||||
/// Returns `true` when the pair was rearranged and both tasks are placed. On
|
||||
/// a `false` return the schedule is exactly as it was.
|
||||
///
|
||||
/// 尝试通过移动某个阻塞者来放置等待任务,仅当二者都能容纳时保留二者。
|
||||
///
|
||||
/// 当该对被重排且两个任务均已放置时返回 `true`。返回 `false` 时计划与
|
||||
/// 原状完全一致。
|
||||
fn relocate_pair(
|
||||
waiting: &Task,
|
||||
blocker: &Task,
|
||||
|
|
@ -207,12 +251,14 @@ fn relocate_pair(
|
|||
return false;
|
||||
};
|
||||
// Remove the blocker and look for a home for the waiting task.
|
||||
// 移除阻塞者,为等待任务寻找安身之处。
|
||||
if schedule.unplace(blocker.id).is_err() {
|
||||
return false;
|
||||
}
|
||||
let waiting_start = earliest_feasible_start(waiting, tasks, schedule, resources);
|
||||
if waiting_start.is_none() {
|
||||
// Nothing changed, so restore the blocker.
|
||||
// 没有任何改变,故恢复阻塞者。
|
||||
let _ = schedule.place(blocker, blocker_start);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -223,6 +269,7 @@ fn relocate_pair(
|
|||
}
|
||||
|
||||
// With the waiting task placed, look for a new home for the blocker.
|
||||
// 等待任务已放置后,为阻塞者寻找新的去处。
|
||||
match earliest_feasible_start(blocker, tasks, schedule, resources) {
|
||||
Some(blocker_new_start) => {
|
||||
if schedule.place(blocker, blocker_new_start).is_ok() {
|
||||
|
|
@ -232,6 +279,9 @@ fn relocate_pair(
|
|||
// can only come from the blocker being placed already, which it
|
||||
// is not. Restore the original arrangement and report failure
|
||||
// rather than leaving a half applied move.
|
||||
// 搜索已判定该放置可行,故此处的失败只可能源于阻塞者已被
|
||||
// 放置,而它并未被放置。恢复原安排并报告失败,以免留下
|
||||
// 半完成的移动。
|
||||
let _ = schedule.unplace(waiting.id);
|
||||
let _ = schedule.place(blocker, blocker_start);
|
||||
false
|
||||
|
|
@ -239,6 +289,7 @@ fn relocate_pair(
|
|||
None => {
|
||||
// The blocker has nowhere to go, so the move is abandoned and
|
||||
// the original arrangement restored.
|
||||
// 阻塞者无处可去,故放弃该次移动并恢复原安排。
|
||||
let _ = schedule.unplace(waiting.id);
|
||||
let _ = schedule.place(blocker, blocker_start);
|
||||
false
|
||||
|
|
@ -248,6 +299,8 @@ fn relocate_pair(
|
|||
|
||||
/// Updates the recorded state of a task, ignoring an identifier outside the
|
||||
/// state list.
|
||||
///
|
||||
/// 更新某任务所记录的状态;标识符不在状态列表之内时忽略。
|
||||
fn set_state(states: &mut [TaskState], id: TaskId, state: TaskState) {
|
||||
if let Some(slot) = states.get_mut(id.index()) {
|
||||
*slot = state;
|
||||
|
|
|
|||
|
|
@ -13,53 +13,84 @@ use apsis_task::{PrecedenceGraph, Task, TaskId, TaskState, task_order};
|
|||
/// information an operator needs to see. It is separate from the schedule
|
||||
/// because a task that was not placed has no placement but still has an
|
||||
/// outcome worth reporting.
|
||||
///
|
||||
/// 规划过程中某个任务的结果。
|
||||
///
|
||||
/// 它把任务与规划器所指定的状态配对,这是操作者需要看到的信息。它与
|
||||
/// 计划分开,因为未放置的任务虽无放置,却仍有一份值得报告的结果。
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PlanOutcome {
|
||||
/// The task.
|
||||
///
|
||||
/// 该任务。
|
||||
pub task: TaskId,
|
||||
/// The state the planner assigned.
|
||||
///
|
||||
/// 规划器所指定的状态。
|
||||
pub state: TaskState,
|
||||
}
|
||||
|
||||
impl PlanOutcome {
|
||||
/// Returns `true` when the task was placed.
|
||||
///
|
||||
/// 当该任务已被放置时返回 `true`。
|
||||
pub fn is_scheduled(&self) -> bool {
|
||||
self.state.is_scheduled()
|
||||
}
|
||||
|
||||
/// Returns `true` when the task could not be placed for a structural
|
||||
/// reason, rather than being left out for want of time.
|
||||
///
|
||||
/// 当该任务因结构性原因无法放置、而非因时间不足被排除时返回 `true`。
|
||||
pub fn is_infeasible(&self) -> bool {
|
||||
matches!(self.state, TaskState::Infeasible { .. })
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of planning.
|
||||
///
|
||||
/// 规划的结果。
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SchedulingResult {
|
||||
/// The schedule that was produced.
|
||||
///
|
||||
/// 所产出的计划。
|
||||
pub schedule: Schedule,
|
||||
/// The outcome of every task, in the order the planner considered them.
|
||||
///
|
||||
/// The order is the order of the task list the planner was given, not the
|
||||
/// order in which the planner placed them, so that a caller can index the
|
||||
/// outcomes by the position of the task in its own list.
|
||||
///
|
||||
/// 每个任务的结果,按规划器考虑它们的顺序排列。
|
||||
///
|
||||
/// 该顺序是规划器所获任务列表的顺序,而非其放置的顺序,故调用方
|
||||
/// 可按任务在自身列表中的位置索引结果。
|
||||
pub outcomes: Vec<PlanOutcome>,
|
||||
/// The identifiers of the tasks that the improvement pass placed.
|
||||
///
|
||||
/// These tasks were left out by the greedy pass and placed afterwards by
|
||||
/// moving other placements. They are also recorded as scheduled in the
|
||||
/// outcomes.
|
||||
///
|
||||
/// 改进轮所放置的任务标识符。
|
||||
///
|
||||
/// 这些任务被贪心轮排除,其后通过移动其他放置而被安排。它们在
|
||||
/// 结果中同样被记为已调度。
|
||||
pub improved: Vec<TaskId>,
|
||||
}
|
||||
|
||||
impl SchedulingResult {
|
||||
/// Returns the outcome of a task.
|
||||
///
|
||||
/// 返回某任务的结果。
|
||||
pub fn outcome_of(&self, task: TaskId) -> Option<&PlanOutcome> {
|
||||
self.outcomes.iter().find(|outcome| outcome.task == task)
|
||||
}
|
||||
|
||||
/// Returns the identifiers of the tasks that were placed.
|
||||
///
|
||||
/// 返回已放置任务的标识符。
|
||||
pub fn scheduled(&self) -> Vec<TaskId> {
|
||||
self.outcomes
|
||||
.iter()
|
||||
|
|
@ -69,6 +100,8 @@ impl SchedulingResult {
|
|||
}
|
||||
|
||||
/// Returns the identifiers of the tasks that were left out.
|
||||
///
|
||||
/// 返回被排除任务的标识符。
|
||||
pub fn deferred(&self) -> Vec<TaskId> {
|
||||
self.outcomes
|
||||
.iter()
|
||||
|
|
@ -78,6 +111,8 @@ impl SchedulingResult {
|
|||
}
|
||||
|
||||
/// Returns the identifiers of the tasks that cannot be placed at all.
|
||||
///
|
||||
/// 返回完全无法放置的任务的标识符。
|
||||
pub fn infeasible(&self) -> Vec<TaskId> {
|
||||
self.outcomes
|
||||
.iter()
|
||||
|
|
@ -87,6 +122,8 @@ impl SchedulingResult {
|
|||
}
|
||||
|
||||
/// Returns `true` when every offered task was placed.
|
||||
///
|
||||
/// 当所有提交任务均被放置时返回 `true`。
|
||||
pub fn is_complete(&self) -> bool {
|
||||
self.outcomes.iter().all(PlanOutcome::is_scheduled)
|
||||
}
|
||||
|
|
@ -95,6 +132,11 @@ impl SchedulingResult {
|
|||
///
|
||||
/// A plan that leaves a mandatory task unplaced is not usable, however
|
||||
/// much optional work it found room for.
|
||||
///
|
||||
/// 当所有强制任务均被放置时返回 `true`。
|
||||
///
|
||||
/// 留下强制任务未放置的计划不可用,无论它为多少可选工作腾出了
|
||||
/// 空间。
|
||||
pub fn honours_mandatory(&self, tasks: &[Task]) -> bool {
|
||||
tasks
|
||||
.iter()
|
||||
|
|
@ -107,6 +149,11 @@ impl SchedulingResult {
|
|||
/// A constraint is satisfied when the second task starts at or after the
|
||||
/// end of the first. A constraint whose tasks are not both placed is
|
||||
/// trivially satisfied, because nothing has been promised about them.
|
||||
///
|
||||
/// 当所有优先约束都得到满足时返回 `true`。
|
||||
///
|
||||
/// 约束被满足,指第二个任务的起点不早于第一个的终点。两个任务未
|
||||
/// 同时放置的约束平凡地满足,因为对它们未作任何承诺。
|
||||
pub fn honours_precedence(&self, graph: &PrecedenceGraph) -> bool {
|
||||
self.outcomes
|
||||
.iter()
|
||||
|
|
@ -129,6 +176,11 @@ impl SchedulingResult {
|
|||
/// The planner holds the settings of the search. The settings are separated
|
||||
/// from the schedule so that the same planner can be applied to many task
|
||||
/// sets with the same policy.
|
||||
///
|
||||
/// 贪心规划器。
|
||||
///
|
||||
/// 该规划器持有搜索的设置。设置与计划分开,故同一个规划器可按同一策略
|
||||
/// 应用于许多任务集。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Planner {
|
||||
/// Whether to place mandatory tasks before the others regardless of
|
||||
|
|
@ -138,14 +190,23 @@ pub struct Planner {
|
|||
/// weighted value is low: the optional tasks can use whatever time is
|
||||
/// left, while the reverse order could consume the only window the
|
||||
/// mandatory task had.
|
||||
///
|
||||
/// 是否不论价值高低都先放置强制任务。
|
||||
///
|
||||
/// 强制任务必须执行,故即便其加权价值很低也值得先放置:可选任务
|
||||
/// 可以利用剩下的时间,而相反的顺序可能耗尽强制任务仅有的那个窗口。
|
||||
pub mandatory_first: bool,
|
||||
/// The settings of the improvement pass.
|
||||
///
|
||||
/// 改进轮的设置。
|
||||
pub improvement: ImprovementSettings,
|
||||
}
|
||||
|
||||
impl Default for Planner {
|
||||
/// Returns a planner that places mandatory tasks first and runs the
|
||||
/// improvement pass.
|
||||
///
|
||||
/// 返回一个先放置强制任务并运行改进轮的规划器。
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mandatory_first: true,
|
||||
|
|
@ -159,6 +220,11 @@ impl Planner {
|
|||
///
|
||||
/// The defaults are the ones [`Planner::default`] uses: mandatory tasks
|
||||
/// first and the improvement pass enabled with four attempts per task.
|
||||
///
|
||||
/// 以默认设置创建规划器。
|
||||
///
|
||||
/// 默认值即 [`Planner::default`] 所用者:先放置强制任务,改进轮
|
||||
/// 启用且每任务尝试四次。
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
mandatory_first: true,
|
||||
|
|
@ -167,12 +233,16 @@ impl Planner {
|
|||
}
|
||||
|
||||
/// Returns a copy of this planner with the mandatory placement rule set.
|
||||
///
|
||||
/// 返回设置了强制放置规则的规划器副本。
|
||||
pub const fn with_mandatory_first(mut self, mandatory_first: bool) -> Self {
|
||||
self.mandatory_first = mandatory_first;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns a copy of this planner with the improvement pass configured.
|
||||
///
|
||||
/// 返回配置了改进轮的规划器副本。
|
||||
pub const fn with_improvement(mut self, improvement: ImprovementSettings) -> Self {
|
||||
self.improvement = improvement;
|
||||
self
|
||||
|
|
@ -188,6 +258,15 @@ impl Planner {
|
|||
/// graph, using the value order to break ties. That places a task as
|
||||
/// early as its constraints allow and the most valuable one first among
|
||||
/// the tasks that are free.
|
||||
///
|
||||
/// 返回规划器考虑任务的顺序。
|
||||
///
|
||||
/// 无优先关系图时,顺序是先强制任务(若规划器如此配置),再按加权
|
||||
/// 价值递减,平局按截止时刻与标识符打破。
|
||||
///
|
||||
/// 有优先关系图时,顺序是该图的拓扑序,并以价值次序打破平局。这
|
||||
/// 使任务在其约束允许的最早时刻被放置,而在自由的任务中优先放置
|
||||
/// 价值最高者。
|
||||
pub fn order(&self, tasks: &[Task]) -> Vec<TaskId> {
|
||||
self.order_with_precedence(tasks, None)
|
||||
}
|
||||
|
|
@ -200,6 +279,12 @@ impl Planner {
|
|||
/// interpretation. The caller should validate the graph before
|
||||
/// planning; the order is a fallback so that planning still produces
|
||||
/// something rather than failing outright.
|
||||
///
|
||||
/// 返回规划器考虑任务的顺序,并遵从给定的优先关系图。
|
||||
///
|
||||
/// 约束含环的图会原样产生价值次序,因为这些约束此时没有可满足的
|
||||
/// 解释。调用方应在规划前校验该图;此处的次序是一个回退,使规划
|
||||
/// 仍能产出结果而非直接失败。
|
||||
pub fn order_with_precedence(
|
||||
&self,
|
||||
tasks: &[Task],
|
||||
|
|
@ -239,6 +324,15 @@ impl Planner {
|
|||
/// The schedule that is returned is feasible by construction, because
|
||||
/// each placement is checked against the capacity before it is made. The
|
||||
/// caller may confirm that with `apsis-conflict`.
|
||||
///
|
||||
/// 在一组资源上规划一组任务。
|
||||
///
|
||||
/// 每个任务按规划器所选的顺序被考虑一次,被放置在其全部资源恰好同时
|
||||
/// 空闲的最早时刻。无法放置的任务被报告为被推迟;不存在任何可行
|
||||
/// 放置的则报告为不可行。
|
||||
///
|
||||
/// 返回的计划按构造即可行,因为每处放置在做下之前都对照容量校验
|
||||
/// 过。调用方可用 `apsis-conflict` 复核。
|
||||
pub fn schedule(&self, tasks: &[Task], resources: &ResourceSet) -> SchedulingResult {
|
||||
self.plan(tasks, resources, None)
|
||||
}
|
||||
|
|
@ -256,6 +350,16 @@ impl Planner {
|
|||
/// that the pass does not consider. A caller that needs both should
|
||||
/// improve the plan and then verify it with
|
||||
/// [`SchedulingResult::honours_precedence`].
|
||||
///
|
||||
/// 遵从优先关系图规划一组任务。
|
||||
///
|
||||
/// 任务仅在其所有前驱均已放置之后才被放置,故其起点搜索不早于
|
||||
/// 前驱中最晚的结束时刻。无法放置的前驱会使其后继同样被推迟,
|
||||
/// 因为该约束无法满足。
|
||||
///
|
||||
/// 改进轮不作用于带优先约束的计划,因为移动某处放置可能破坏该轮
|
||||
/// 并不考虑的约束。两者皆需的调用方应先改进计划,再用
|
||||
/// [`SchedulingResult::honours_precedence`] 复核。
|
||||
pub fn schedule_with_precedence(
|
||||
&self,
|
||||
tasks: &[Task],
|
||||
|
|
@ -266,6 +370,8 @@ impl Planner {
|
|||
}
|
||||
|
||||
/// The shared planning routine.
|
||||
///
|
||||
/// 共享的规划例程。
|
||||
fn plan(
|
||||
&self,
|
||||
tasks: &[Task],
|
||||
|
|
@ -275,11 +381,13 @@ impl Planner {
|
|||
let mut schedule = Schedule::new();
|
||||
// The state of every task, indexed by identifier, so that a
|
||||
// predecessor's placement can be looked up by index.
|
||||
// 各任务的状态,按标识符索引,使前驱的放置可按下标查得。
|
||||
let mut states = vec![TaskState::Pending; tasks.len()];
|
||||
let mut outcomes = Vec::with_capacity(tasks.len());
|
||||
let mut order: Vec<TaskId> = self.order_with_precedence(tasks, precedence);
|
||||
// A deferred task is one whose search failed, as opposed to one whose
|
||||
// structural preconditions failed.
|
||||
// 被推迟的任务指搜索失败者,区别于结构性前置条件失败者。
|
||||
let mut deferred: Vec<TaskId> = Vec::new();
|
||||
let infeasible = infeasible_tasks(tasks, resources);
|
||||
|
||||
|
|
@ -311,6 +419,7 @@ impl Planner {
|
|||
|
||||
// A task may not start before the latest end among the tasks that
|
||||
// must precede it.
|
||||
// 任务的起点不得早于其前驱中最晚的结束时刻。
|
||||
let earliest = match self.precedence_floor(id, tasks, precedence, &schedule, &states) {
|
||||
Ok(floor) => floor,
|
||||
Err(reason) => {
|
||||
|
|
@ -327,6 +436,7 @@ impl Planner {
|
|||
|
||||
// The task with the floor applied, so that the search respects
|
||||
// the constraints.
|
||||
// 施加该下界后的任务,使搜索遵从各约束。
|
||||
let constrained = match earliest {
|
||||
Some(floor) if floor > task.earliest_start => {
|
||||
let mut copy = task.clone();
|
||||
|
|
@ -373,6 +483,8 @@ impl Planner {
|
|||
|
||||
// The improvement pass runs only for a plan without precedence
|
||||
// constraints, for the reason given on `schedule_with_precedence`.
|
||||
// 改进轮只作用于无优先约束的计划,原因见
|
||||
// `schedule_with_precedence`。
|
||||
let improved = if precedence.is_some() {
|
||||
Vec::new()
|
||||
} else {
|
||||
|
|
@ -387,6 +499,8 @@ impl Planner {
|
|||
// The outcomes that the pass changed must be updated, and the
|
||||
// order of the outcomes follows the task list so that a caller
|
||||
// can index them by position.
|
||||
// 该轮改动的结果必须更新,且结果次序遵循任务列表,使调用
|
||||
// 方可按位置索引。
|
||||
for id in &outcome.placed {
|
||||
if let Some(slot) = outcomes.iter_mut().find(|entry| entry.task == *id) {
|
||||
let start = schedule
|
||||
|
|
@ -400,6 +514,8 @@ impl Planner {
|
|||
|
||||
// Reorder the outcomes to follow the task list, which is the order a
|
||||
// caller expects when it indexes by its own position.
|
||||
// 将结果重排为遵循任务列表,即调用方按自身位置索引时所期望的
|
||||
// 次序。
|
||||
outcomes.sort_by_key(|outcome| outcome.task.index());
|
||||
|
||||
SchedulingResult {
|
||||
|
|
@ -414,6 +530,11 @@ impl Planner {
|
|||
/// Returns `Ok(None)` when there is no constraint, `Ok(Some(t))` when the
|
||||
/// task may start no earlier than `t`, and `Err` when a predecessor was
|
||||
/// not placed, which makes the constraint unsatisfiable.
|
||||
///
|
||||
/// 给定其前驱,返回某任务可有的最早起点。
|
||||
///
|
||||
/// 无约束时返回 `Ok(None)`;任务起点不早于 `t` 时返回 `Ok(Some(t))`;
|
||||
/// 某前驱未被放置致使约束不可满足时返回 `Err`。
|
||||
fn precedence_floor(
|
||||
&self,
|
||||
id: TaskId,
|
||||
|
|
@ -429,6 +550,7 @@ impl Planner {
|
|||
for predecessor in graph.predecessors_of(id) {
|
||||
// A predecessor that was deferred or is infeasible cannot be
|
||||
// waited for.
|
||||
// 被推迟或不可行的前驱无从等待。
|
||||
let placed = schedule.placement_of(predecessor);
|
||||
let Some(placement) = placed else {
|
||||
let reason = match states.get(predecessor.index()) {
|
||||
|
|
@ -445,6 +567,8 @@ impl Planner {
|
|||
}
|
||||
|
||||
/// Records the state of a task, ignoring an identifier outside the list.
|
||||
///
|
||||
/// 记录某任务的状态;标识符不在列表之内时忽略。
|
||||
fn set_state(states: &mut [TaskState], id: TaskId, state: TaskState) {
|
||||
if let Some(slot) = states.get_mut(id.index()) {
|
||||
*slot = state;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@ use apsis_task::TaskId;
|
|||
///
|
||||
/// It matches the tolerance in [`feasible_at`], so the sweep and the direct
|
||||
/// test draw the same line at a boundary.
|
||||
///
|
||||
/// 比较负载与容量时允许的余量。
|
||||
///
|
||||
/// 它与 [`feasible_at`] 中的容差一致,故扫掠与直接测试在边界处划同一条
|
||||
/// 线。
|
||||
const CAPACITY_SLACK: f64 = 1e-9;
|
||||
|
||||
/// Returns the earliest time at which a task can be placed.
|
||||
|
|
@ -43,6 +48,14 @@ const CAPACITY_SLACK: f64 = 1e-9;
|
|||
/// the availability is constant between them too.
|
||||
///
|
||||
/// Returns `None` when the task cannot be placed anywhere in its window.
|
||||
///
|
||||
/// 返回某任务可被放置的最早时刻。
|
||||
///
|
||||
/// 最早可行起点总是某个小候选集合之一:任务窗口的起点、其某资源可用窗口
|
||||
/// 的起点,或其某资源上已有放置的结束时刻。可行性在其他任何瞬时都不会
|
||||
/// 改变,因为各资源的需求在这些事件之间恒定,可用性亦然。
|
||||
///
|
||||
/// 该任务在其窗口内无处可放时返回 `None`。
|
||||
pub fn earliest_feasible_start(
|
||||
task: &Task,
|
||||
tasks: &[Task],
|
||||
|
|
@ -54,6 +67,7 @@ pub fn earliest_feasible_start(
|
|||
}
|
||||
if task.requirements.is_empty() {
|
||||
// A task with no requirements can start as soon as its window opens.
|
||||
// 无需求的任务可在其窗口开启时即开始。
|
||||
return Some(task.earliest_start);
|
||||
}
|
||||
let latest_start = task.latest_end - task.duration;
|
||||
|
|
@ -63,9 +77,14 @@ pub fn earliest_feasible_start(
|
|||
// demand by scanning the task list at each of those visits would make
|
||||
// deciding one task quadratic in the plan. The map is built once per
|
||||
// task, which is linear, and shared by every visit.
|
||||
// 各任务的需求,按标识符一次性建好索引。扫掠会访问每个所需资源上
|
||||
// 的每处放置,若每次访问都扫描任务列表来读需求,判定一个任务就会
|
||||
// 对整个计划呈二次。该索引每个任务构建一次,是线性的,并由所有
|
||||
// 访问共享。
|
||||
let demands = demand_index(tasks);
|
||||
|
||||
// The sweep, confirmed by the direct test.
|
||||
// 扫掠,其结果由直接测试确认。
|
||||
if let Some(candidate) = swept_earliest_start(task, schedule, resources, latest_start, &demands)
|
||||
{
|
||||
if feasible_at_indexed(task, schedule, resources, candidate, &demands) {
|
||||
|
|
@ -76,12 +95,18 @@ pub fn earliest_feasible_start(
|
|||
// The enumeration, which is exact. It runs when the sweep could not
|
||||
// produce a confirmed answer, which is when the task does not fit at all
|
||||
// or when rounding made the two disagree.
|
||||
// 枚举,它是精确的。当扫掠无法给出经确认的答案时运行,即该任务
|
||||
// 完全放不下,或舍入使二者不一致时。
|
||||
enumerate_earliest_start(task, schedule, resources, latest_start, &demands)
|
||||
}
|
||||
|
||||
/// Returns the earliest start by testing every candidate in turn.
|
||||
///
|
||||
/// This is the original formulation, kept as the fallback for the sweep.
|
||||
///
|
||||
/// 依次测试每个候选,返回最早起点。
|
||||
///
|
||||
/// 这是最初的表述,保留为扫掠的回退。
|
||||
fn enumerate_earliest_start(
|
||||
task: &Task,
|
||||
schedule: &Schedule,
|
||||
|
|
@ -122,6 +147,11 @@ fn enumerate_earliest_start(
|
|||
/// The intervals returned by [`starts_fitting_on`] for each resource are
|
||||
/// intersected, and the first interval that lies inside the task's window
|
||||
/// gives the answer.
|
||||
///
|
||||
/// 由扫掠所得的负载曲线导出最早起点。
|
||||
///
|
||||
/// [`starts_fitting_on`] 为各资源返回的区间被求交,落在该任务窗口内的
|
||||
/// 第一个区间即给出答案。
|
||||
fn swept_earliest_start(
|
||||
task: &Task,
|
||||
schedule: &Schedule,
|
||||
|
|
@ -158,6 +188,11 @@ fn swept_earliest_start(
|
|||
/// availability window and the demand already placed on the resource never
|
||||
/// exceeds the capacity less the new demand. The result is sorted and the
|
||||
/// intervals are disjoint.
|
||||
///
|
||||
/// 返回可使给定时长与需求的放置落在某资源上的起点区间。
|
||||
///
|
||||
/// 当整个放置位于某个可用窗口内、且资源上已放置的需求始终不超过容量减去
|
||||
/// 新需求时,该起点被计入结果。结果有序且各区间互不相交。
|
||||
fn starts_fitting_on(
|
||||
duration: f64,
|
||||
demand: f64,
|
||||
|
|
@ -172,6 +207,7 @@ fn starts_fitting_on(
|
|||
for (from, to) in subtract(&blocked, window.start(), window.end()) {
|
||||
// The placement has to fit inside, so the last start is the end
|
||||
// of the free interval less the duration.
|
||||
// 放置须容纳于其中,故最晚起点为该空闲区间的终点减去时长。
|
||||
let last_start = to - duration;
|
||||
if last_start >= from {
|
||||
result.push((from, last_start));
|
||||
|
|
@ -186,6 +222,11 @@ fn starts_fitting_on(
|
|||
/// The intervals are where the demand already placed on the resource exceeds
|
||||
/// the capacity less `demand`. They are computed with a single sweep over the
|
||||
/// placement boundaries, and are sorted and disjoint.
|
||||
///
|
||||
/// 返回某资源无余量容纳某需求的区间。
|
||||
///
|
||||
/// 这些区间即资源上已放置的需求超过容量减去 `demand` 的时段。它们由
|
||||
/// 对各放置边界的一次扫掠算出,有序且互不相交。
|
||||
fn blocked_intervals(
|
||||
demand: f64,
|
||||
resource_id: ResourceId,
|
||||
|
|
@ -197,6 +238,8 @@ fn blocked_intervals(
|
|||
|
||||
// The load profile changes only where a placement starts or ends, so the
|
||||
// sweep needs only those two instants per placement.
|
||||
// 负载曲线只在某处放置开始或结束时改变,故扫掠每个放置只需那两个
|
||||
// 瞬时。
|
||||
let mut events: Vec<(f64, f64)> = Vec::new();
|
||||
for placement in schedule.placements() {
|
||||
let Some(placed) = demand_on(demands, placement.task, resource_id) else {
|
||||
|
|
@ -229,12 +272,15 @@ fn blocked_intervals(
|
|||
while index < events.len() {
|
||||
let time = events[index].0;
|
||||
// The load is constant on the interval that ends at this instant.
|
||||
// 负载在以该瞬时结束的区间上恒定。
|
||||
if load > limit {
|
||||
opened.get_or_insert(time);
|
||||
}
|
||||
// Apply every change at this instant at once, so the load compared
|
||||
// above belongs to the interval before it and the one after belongs
|
||||
// to the next comparison.
|
||||
// 一次性施加该瞬时的全部变化,故上面比较的负载属于其前的区间,
|
||||
// 其后的负载则用于下一次比较。
|
||||
let mut next = index;
|
||||
while next < events.len() && events[next].0 == time {
|
||||
load += events[next].1;
|
||||
|
|
@ -249,6 +295,8 @@ fn blocked_intervals(
|
|||
}
|
||||
// A run that never closes extends past every placement, so it is
|
||||
// recorded as open ended and swallows the rest of the horizon.
|
||||
// 始终不闭合的区段延伸越过所有放置,故记为开区间,吞没时域的
|
||||
// 其余部分。
|
||||
if let Some(start) = opened {
|
||||
blocked.push((start, f64::INFINITY));
|
||||
}
|
||||
|
|
@ -261,9 +309,16 @@ fn blocked_intervals(
|
|||
/// vector indexed by it rather than in a map. Reading one is then an array
|
||||
/// index rather than a hash, and a hash of a small integer costs more than
|
||||
/// the scan it replaces.
|
||||
///
|
||||
/// 各任务在各资源上的需求,按任务标识符索引。
|
||||
///
|
||||
/// 任务标识符是小的稠密整数,故需求存于以其为下标的向量而非映射中。读取
|
||||
/// 时即数组下标而非哈希,而哈希一个小整数的代价高于它所取代的扫描。
|
||||
pub(crate) type DemandIndex = Vec<Option<std::collections::HashMap<ResourceId, f64>>>;
|
||||
|
||||
/// Builds a [`DemandIndex`] in one pass over the task list.
|
||||
///
|
||||
/// 由任务列表一趟构建 [`DemandIndex`]。
|
||||
pub(crate) fn demand_index(tasks: &[Task]) -> DemandIndex {
|
||||
let highest = tasks.iter().map(|task| task.id.index()).max().unwrap_or(0);
|
||||
let mut index: DemandIndex = (0..=highest).map(|_| None).collect();
|
||||
|
|
@ -282,6 +337,8 @@ pub(crate) fn demand_index(tasks: &[Task]) -> DemandIndex {
|
|||
}
|
||||
|
||||
/// Returns the demand a placed task puts on a resource, if any.
|
||||
///
|
||||
/// 返回某已放置任务在某资源上的需求,若有。
|
||||
pub(crate) fn demand_on(index: &DemandIndex, task: TaskId, resource: ResourceId) -> Option<f64> {
|
||||
index
|
||||
.get(task.index())
|
||||
|
|
@ -293,6 +350,10 @@ pub(crate) fn demand_on(index: &DemandIndex, task: TaskId, resource: ResourceId)
|
|||
/// Returns the pieces of an interval that no blocked interval covers.
|
||||
///
|
||||
/// Both inputs are sorted and disjoint. The result is sorted and disjoint.
|
||||
///
|
||||
/// 返回某区间中未被任何阻塞区间覆盖的部分。
|
||||
///
|
||||
/// 两个输入均有序且互不相交,结果亦然。
|
||||
fn subtract(blocked: &[(f64, f64)], from: f64, to: f64) -> Vec<(f64, f64)> {
|
||||
let mut pieces = Vec::new();
|
||||
let mut cursor = from;
|
||||
|
|
@ -320,6 +381,10 @@ fn subtract(blocked: &[(f64, f64)], from: f64, to: f64) -> Vec<(f64, f64)> {
|
|||
/// Returns the intersection of two sorted, disjoint lists of intervals.
|
||||
///
|
||||
/// The result is sorted and disjoint.
|
||||
///
|
||||
/// 返回两份有序且互不相交的区间列表之交。
|
||||
///
|
||||
/// 结果有序且互不相交。
|
||||
fn intersect(left: &[(f64, f64)], right: &[(f64, f64)]) -> Vec<(f64, f64)> {
|
||||
let mut result = Vec::new();
|
||||
let mut i = 0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue