diff --git a/crates/ops/apsis-resource/src/lib.rs b/crates/ops/apsis-resource/src/lib.rs index d8959da..d796ced 100644 --- a/crates/ops/apsis-resource/src/lib.rs +++ b/crates/ops/apsis-resource/src/lib.rs @@ -1,7 +1,5 @@ //! Resource modeling for satellite operations. //! -//! 卫星运行的资源建模。 -//! //! A resource is anything a task can occupy: a satellite, a ground station, a //! transmitter, an onboard recorder. This crate models the two things a //! scheduler needs to know about one: when it is available, and how much of @@ -34,6 +32,23 @@ //! assert!(resource.is_available_over(120.0, 80.0)); //! assert!(!resource.is_available_over(150.0, 100.0)); //! ``` +//! +//! 卫星运行的资源建模。 +//! +//! 资源是任务可以占用的任何东西:一颗卫星、一座地面站、一台发射机、一台星载 +//! 记录器。本 crate 为调度器需要了解的关于它的两件事建模:它何时可用,以及一个 +//! 任务消耗它多少。 +//! +//! 可用性表达为单一时间轴上互不相交的区间集合,称为窗口。在某瞬时有窗口包含该 +//! 瞬时,资源即可用。这些窗口可来自任何地方:`apsis-access` 中的可见性分析为 +//! 地面站产生它们,操作员则手工录入维护时段。 +//! +//! 容量是一个正数,其含义由调用方定义。一次只能运行一项观测的卫星容量为一;记录 +//! 器的容量以兆比特计;功率母线的以瓦特计。两个任务只有在它们的需求合起来超过 +//! 容量时才冲突,这使同一个类型既能表达独占资源也能表达共享资源。 +//! +//! 时间以距调用方所选原点的秒数计,故同一模型可服务于场景时钟、流逝时间与儒略日 +//! 偏移。 #![forbid(unsafe_code)] #![deny(missing_docs)] diff --git a/crates/ops/apsis-schedule/src/lib.rs b/crates/ops/apsis-schedule/src/lib.rs index 5cbf43d..0395e12 100644 --- a/crates/ops/apsis-schedule/src/lib.rs +++ b/crates/ops/apsis-schedule/src/lib.rs @@ -1,7 +1,5 @@ //! Schedule representation and evaluation. //! -//! 调度的表示与评估。 -//! //! A schedule is the answer a scheduler produces: for each task, when it runs //! and on which resources. This crate holds that answer and evaluates it. //! It does not decide the placements, which is the job of `apsis-scheduler`; @@ -32,6 +30,18 @@ //! assert_eq!(schedule.len(), 1); //! assert!(schedule.is_placed(TaskId::new(0))); //! ``` +//! +//! 调度的表示与评估。 +//! +//! 调度是调度器所产生的答案:对每个任务,它何时运行以及在哪些资源上运行。本 +//! crate 保存该答案并评估它。它不决定放置,那是 `apsis-scheduler` 的事;把二者 +//! 分开,使一个调度可以从文件读取、由调度器产生,或手工书写,并以相同方式评估。 +//! +//! 一处放置记录任务、其开始时间,以及它所持有的资源。放置时各资源会针对任务的需求 +//! 检查,故调度无法描述一个运行在它并未请求的资源上的任务。 +//! +//! 评估报告操作员对已完成计划所问的:放置了多少任务、每种资源在其最繁忙时被用去 +//! 多少,以及可用时间实际花去多少。这些数字正是规划器在候选调度之间比较的。 #![forbid(unsafe_code)] #![deny(missing_docs)] diff --git a/crates/ops/apsis-scheduler/src/lib.rs b/crates/ops/apsis-scheduler/src/lib.rs index 3b1632d..c8d3471 100644 --- a/crates/ops/apsis-scheduler/src/lib.rs +++ b/crates/ops/apsis-scheduler/src/lib.rs @@ -1,7 +1,5 @@ //! Scheduling algorithms for satellite task planning. //! -//! 卫星任务规划的调度算法。 -//! //! A scheduler takes a list of tasks and a set of resources and produces a //! schedule. This crate provides a greedy scheduler and the pieces needed to //! build others. @@ -41,6 +39,23 @@ //! assert_eq!(result.schedule.len(), 1); //! assert!(result.is_complete()); //! ``` +//! +//! 卫星任务规划的调度算法。 +//! +//! 调度器接收任务列表与资源集合,产生一个调度。本 crate 提供一个贪心调度器以及 +//! 构建其他调度器所需的构件。 +//! +//! 带资源约束的调度一般是难解的:判定是否每个任务都放得下是一个装箱问题,它是 +//! NP 难的。卫星规划问题规模小,有数百个任务而非数百万个,但对例行的规划周期而言, +//! 精确搜索仍不切实际。故此处的调度器是贪心启发式。 +//! +//! 该算法把任务排序,并把每个放到其全部资源同时空闲的最早时间。任务按加权价值递减 +//! 的次序考虑,这是一个好的启发式:最有价值的工作先被放置,最不有价值的填充剩余 +//! 空隙。强制任务不论其价值都先放置,因为一个必须运行的任务比任何可以运行的任务 +//! 都值更多。 +//! +//! 结果不保证最优,且本 crate 报告它无法放置的内容而非无声丢弃,使操作员能看到该 +//! 计划的代价。 #![forbid(unsafe_code)] #![deny(missing_docs)] diff --git a/crates/ops/apsis-task/src/lib.rs b/crates/ops/apsis-task/src/lib.rs index 7511c08..bf91356 100644 --- a/crates/ops/apsis-task/src/lib.rs +++ b/crates/ops/apsis-task/src/lib.rs @@ -1,7 +1,5 @@ //! Task modeling for satellite operations. //! -//! 卫星运行的任务建模。 -//! //! A task is a unit of work that a scheduler places on a set of resources. It //! carries everything a scheduler needs to decide whether and where to run //! it: how long it takes, when it may run, which resources it needs and in @@ -35,6 +33,19 @@ //! assert_eq!(task.requirements.len(), 2); //! assert!(task.is_valid()); //! ``` +//! +//! 卫星运行的任务建模。 +//! +//! 任务是调度器放置到一组资源上的工作单元。它携带调度器决定是否运行它、在哪里 +//! 运行它所需的一切:耗时多久、何时可以运行、需要哪些资源及多少量,以及它值多少。 +//! +//! 一个任务在其整个时长内同时需要它的全部资源。这是观测所具有的模型:取数期间 +//! 卫星与地面站必须都可用。需要按序使用资源的任务,表达为两个由 +//! [`PrecedenceGraph`] 中先后约束连接的任务,该约束说一个任务必须在另一个开始 +//! 之前完成。 +//! +//! 任务一旦构建便不可变。调度器赋给它的状态分开保存在调度中,使同一个任务可以 +//! 被考虑用于多处放置而不被修改。 #![forbid(unsafe_code)] #![deny(missing_docs)] diff --git a/crates/sim/apsis-event/src/lib.rs b/crates/sim/apsis-event/src/lib.rs index fc868d6..8dca822 100644 --- a/crates/sim/apsis-event/src/lib.rs +++ b/crates/sim/apsis-event/src/lib.rs @@ -1,7 +1,5 @@ //! Simulation event types and event queue. //! -//! 仿真事件类型与事件队列。 -//! //! A discrete-event simulation advances by jumping from one event to the //! next rather than by stepping through every instant. This crate provides //! the queue that makes that possible: a priority queue ordered by time, @@ -31,6 +29,19 @@ //! assert_eq!(event.time, 5.0); //! assert_eq!(event.payload, "first"); //! ``` +//! +//! 仿真事件类型与事件队列。 +//! +//! 离散事件仿真通过从一个事件跳到下一个来推进,而不是逐步走过每个瞬时。本 crate +//! 提供使之成为可能的队列:一个按时间排序的优先队列,并在落在同一瞬时的各事件之间 +//! 有明确定义的次序。 +//! +//! 该并列打破规则比它初看更重要。为同一瞬时调度的两个事件必须以可重复的次序投递, +//! 否则仿真每次运行都会产生不同结果,与另一实现的交叉核对也不可能。故每个事件携带 +//! 一个在调度时赋予的序号,时间相等的事件按该序号排序,故该次序即它们被调度的次序。 +//! 这是每个离散事件仿真器所用的约定。 +//! +//! 时间以距仿真起点的秒数计,故队列对日历与时标一无所知。 #![forbid(unsafe_code)] #![deny(missing_docs)] diff --git a/crates/sim/apsis-output/src/lib.rs b/crates/sim/apsis-output/src/lib.rs index 01a092c..cb893eb 100644 --- a/crates/sim/apsis-output/src/lib.rs +++ b/crates/sim/apsis-output/src/lib.rs @@ -1,7 +1,5 @@ //! Simulation output recording. //! -//! 仿真输出记录。 -//! //! A simulation produces more output than a caller can act on at once: event //! times, the values a model computes as it runs, and the warnings it raises. //! This crate collects those into one trace, so that a run can be examined @@ -34,6 +32,22 @@ //! assert_eq!(recorder.trace().value_of("altitude"), Some(550.0)); //! let _ = RecordKind::Warning; //! ``` +//! +//! 仿真输出记录。 +//! +//! 一次仿真产生的输出多到调用方无法一次全部处理:事件时刻、模型运行时计算的值,以及 +//! 它提出的警告。本 crate 把这些收集进一条轨迹,使一次运行可以在结束后被检视,而不 +//! 只在发生时。 +//! +//! 一条记录有一个时刻、一个类别、一个标签与一个值。时刻是作出该记录时的仿真瞬时, +//! 这正是使轨迹可排序、可与事件日志连接的原因。类别区分了调用方通常想分辨的三件事: +//! 模型作出的观测、它决定的变更,以及它想揭示的警告。标签指明该值所关于的内容,而 +//! 值是一个单数,这覆盖了一次仿真所记录的绝大多数内容,而不强加调用方必须设计的 +//! 模式。 +//! +//! 轨迹保持记录在它们被作出的次序,而非时间次序。该区别要紧:处理器可能先为它刚 +//! 调度的后续事件记录了一个值,再为它正在处理的事件记录一个值,而模型推理所用的 +//! 次序值得保留。想要时间次序的调用方用 [`Trace::in_time_order`] 索取。 #![forbid(unsafe_code)] #![deny(missing_docs)] diff --git a/crates/sim/apsis-scenario/src/lib.rs b/crates/sim/apsis-scenario/src/lib.rs index 82efddd..c72eb26 100644 --- a/crates/sim/apsis-scenario/src/lib.rs +++ b/crates/sim/apsis-scenario/src/lib.rs @@ -1,7 +1,5 @@ //! Scenario definition and loading. //! -//! 场景的定义与加载。 -//! //! A scenario is a simulation's initial conditions: the resources a plan may //! use, the tasks it must place, the precedence constraints between those //! tasks, and the limits the run should respect. It is the input a run is @@ -58,6 +56,42 @@ //! assert_eq!(scenario.tasks().len(), 1); //! assert_eq!(scenario.validate(), Ok(())); //! ``` +//! +//! 场景的定义与加载。 +//! +//! 场景是一次仿真的初始条件:计划可用的资源、必须放置的任务、这些任务之间的先后 +//! 约束,以及运行应遵守的限制。它是一次运行所据以构建的输入,把它保存在一处意味着 +//! 一次运行可以从单一描述复现,而不必从散落的参数重建。 +//! +//! 本 crate 提供两种构建方式。构建器方法是 Rust 代码所用的,它们在每次添加时就检查。 +//! 文本格式是存储的场景所用的,[`Scenario::parse`] 把它读回。二者一致:用 +//! [`Scenario::to_text`] 写出的场景解析回相等的场景,故场景可以被存储、编辑并重新 +//! 加载而不漂移。 +//! +//! 文本格式是面向行的,且不携带第三方依赖。空行与第一个非空格字符为 `#` 的行被忽略。 +//! 其余每一行是一个关键字后接其参数: +//! +//! ```text +//! scenario leo downlink +//! epoch 0 +//! time_limit 3600 +//! event_limit 1000000 +//! +//! resource 0 capacity 1 +//! window 0 0 600 +//! window 0 1200 1800 +//! +//! task 0 duration 60 +//! task_window 0 100 1000 +//! task_priority 0 5 +//! task_mandatory 0 true +//! require 0 0 1 +//! +//! precedence 0 1 +//! ``` +//! +//! `epoch` 是仿真时钟的起始时刻,单位为秒。`time_limit` 与 `event_limit` 是两个运行 +//! 限制;任一个都可省略,那会使该限制不被设置。 #![forbid(unsafe_code)] #![deny(missing_docs)] diff --git a/crates/sim/apsis-sim-engine/src/lib.rs b/crates/sim/apsis-sim-engine/src/lib.rs index 88f4f6f..35bf577 100644 --- a/crates/sim/apsis-sim-engine/src/lib.rs +++ b/crates/sim/apsis-sim-engine/src/lib.rs @@ -1,7 +1,5 @@ //! Discrete-event simulation engine. //! -//! 离散事件仿真引擎。 -//! //! The engine advances a clock by jumping from one event to the next. It //! holds the event queue, the current simulation time, and the statistics a //! caller needs to judge a run. What an event means is the caller's: it @@ -40,6 +38,20 @@ //! assert_eq!(outcome.executed, 2); //! assert_eq!(engine.now(), 20.0); //! ``` +//! +//! 离散事件仿真引擎。 +//! +//! 引擎通过从一个事件跳到下一个来推进时钟。它保存事件队列、当前仿真时刻,以及调用方 +//! 判断一次运行所需的统计。一个事件意味着什么由调用方定义:它提供一个接收该事件并可 +//! 再调度更多事件的处理器,而引擎只负责排序各次调用。 +//! +//! 引擎保持仿真确定。同一瞬时的事件按它们被调度的次序投递,这是事件队列所保证的,故 +//! 相同输入的一次运行产生相同输出。这正是使结果可复现、与另一实现的交叉核对有意义的 +//! 原因。 +//! +//! 引擎因四种原因之一停止,报告为一个 [`StopReason`]:队列排空、仿真达到其时限、 +//! 调用方请求它停止,或达到事件上限。后两者存在,是为了让永远调度事件的模型无法无界 +//! 运行。 #![forbid(unsafe_code)] #![deny(missing_docs)] diff --git a/crates/sim/apsis-sim/src/lib.rs b/crates/sim/apsis-sim/src/lib.rs index ba1a239..50d738b 100644 --- a/crates/sim/apsis-sim/src/lib.rs +++ b/crates/sim/apsis-sim/src/lib.rs @@ -1,7 +1,5 @@ //! Simulation facade that integrates the simulation layer. //! -//! 整合仿真层的仿真门面。 -//! //! This crate is the single entry point to the simulation layer. It ties the //! four pieces together: a [`Scenario`] describes the initial conditions, the //! ops layer plans it, the engine runs the resulting timeline, and a @@ -51,6 +49,26 @@ //! assert_eq!(starts, 1); //! assert_eq!(report.outcome.executed, 2); //! ``` +//! +//! 整合仿真层的仿真门面。 +//! +//! 本 crate 是仿真层的单一入口。它把四部分联系起来:[`Scenario`] 描述初始条件, +//! ops 层为其作计划,引擎运行所得时间线,[`Recorder`] 收集该运行所产生的。 +//! +//! 调用方所遵循的序列很短: +//! +//! 1. [`Simulation::prepare`] 检查场景并为其作计划,产生一个调度。 +//! 2. [`Simulation::start`] 把该调度转为由开始与结束事件构成的时间线,并交回一个 +//! [`Session`]。 +//! 3. [`Session::run`] 驱动引擎,在每个事件处调用一个处理器,并返回一个 +//! [`RunReport`],其中含有引擎结果与轨迹。 +//! +//! 该计划是贪心启发式,在 `apsis-scheduler` 中有述,故一次运行可能留下未放置的任务。 +//! 门面报告这一点而非隐藏它:[`Simulation::plan`] 公开完整规划结果,而 +//! [`Simulation::has_full_plan`] 直接回答那个常见的问题。 +//! +//! 时间线是调度而非物理模型。任务开始时触发一个开始事件,结束时触发一个结束事件, +//! 这正是调用方随时间记录状态所需的;处理器提供该运行所需的任何物理或簿记。 #![forbid(unsafe_code)] #![deny(missing_docs)]