docs: bilingual comments for the error types, links and mesh

The error modules across the remaining crates share a shape, so one table
covers them all. Also the inter-satellite link geometry and the mesh
connectivity analysis, including the Menger and Kosaraju notes.
This commit is contained in:
ApsisRS contributors 2026-10-02 01:14:59 +08:00
parent e41e2c77bb
commit 99807cb5e6
9 changed files with 163 additions and 0 deletions

View File

@ -3,20 +3,34 @@
use core::fmt; use core::fmt;
/// The category of an access error. /// The category of an access error.
///
/// 接入错误的类别。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive] #[non_exhaustive]
pub enum AccessErrorKind { pub enum AccessErrorKind {
/// The station or its mask is not usable. /// The station or its mask is not usable.
///
/// 地面站或其遮蔽角不可用。
InvalidStation, InvalidStation,
/// The sampling step is not positive and finite. /// The sampling step is not positive and finite.
///
/// 采样步长非正或非有限。
InvalidSampleStep, InvalidSampleStep,
/// The crossing tolerance is not positive and finite. /// The crossing tolerance is not positive and finite.
///
/// 交叉容差非正或非有限。
InvalidTolerance, InvalidTolerance,
/// The search interval is empty or not ordered. /// The search interval is empty or not ordered.
///
/// 搜索区间为空或次序不对。
InvalidInterval, InvalidInterval,
/// The sample sequence is not in increasing time order. /// The sample sequence is not in increasing time order.
///
/// 样本序列未按时间递增排列。
UnorderedSamples, UnorderedSamples,
/// A supplied satellite position is not usable. /// A supplied satellite position is not usable.
///
/// 所提供的卫星位置不可用。
InvalidPosition, InvalidPosition,
} }
@ -35,6 +49,8 @@ impl fmt::Display for AccessErrorKind {
} }
/// A failure while finding access windows. /// A failure while finding access windows.
///
/// 查找可见性窗口时发生的失败。
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccessError { pub struct AccessError {
kind: AccessErrorKind, kind: AccessErrorKind,

View File

@ -3,16 +3,26 @@
use core::fmt; use core::fmt;
/// The category of a handover error. /// The category of a handover error.
///
/// 切换错误的类别。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive] #[non_exhaustive]
pub enum HandoverErrorKind { pub enum HandoverErrorKind {
/// The candidate list is empty. /// The candidate list is empty.
///
/// 候选列表为空。
NoCandidates, NoCandidates,
/// The same satellite appears more than once in the candidate list. /// The same satellite appears more than once in the candidate list.
///
/// 同一卫星在候选列表中出现多次。
DuplicateSatellite, DuplicateSatellite,
/// A quality value is outside its range or not finite. /// A quality value is outside its range or not finite.
///
/// 某质量值超出取值范围或非有限。
InvalidQuality, InvalidQuality,
/// A policy parameter is not usable. /// A policy parameter is not usable.
///
/// 某策略参数不可用。
InvalidPolicy, InvalidPolicy,
} }
@ -29,6 +39,8 @@ impl fmt::Display for HandoverErrorKind {
} }
/// A failure while making a handover decision. /// A failure while making a handover decision.
///
/// 作出切换决策时发生的失败。
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct HandoverError { pub struct HandoverError {
kind: HandoverErrorKind, kind: HandoverErrorKind,

View File

@ -21,6 +21,14 @@ use apsis_math::{Vec3, parallel::map_index};
/// ///
/// A pair whose test is undefined, because a position is not finite or the /// A pair whose test is undefined, because a position is not finite or the
/// body radius is not positive, yields `false`. /// body radius is not positive, yields `false`.
///
/// 按顺序返回各位置有序对的可见性。
///
/// 结果按行主序:位于 `row * positions.len() + column` 的元素是
/// `positions[row]` 对 `positions[column]` 的可见性。卫星对自身总可见,
/// 这提示调用方跳过对角线。
///
/// 测试未定义的点对(位置非有限或天体半径非正)给出 `false`。
pub fn visibility_matrix(positions: &[Vec3], body_radius: f64) -> Vec<bool> { pub fn visibility_matrix(positions: &[Vec3], body_radius: f64) -> Vec<bool> {
let len = positions.len(); let len = positions.len();
let mut matrix = vec![false; len * len]; let mut matrix = vec![false; len * len];
@ -34,6 +42,7 @@ pub fn visibility_matrix(positions: &[Vec3], body_radius: f64) -> Vec<bool> {
} }
/// Returns the visibility of every ordered pair of positions, across several /// Returns the visibility of every ordered pair of positions, across several
/// 跨多个线程返回各位置有序对的可见性。
/// threads. /// threads.
/// ///
/// The result matches [`visibility_matrix`] element for element and in the /// The result matches [`visibility_matrix`] element for element and in the

View File

@ -12,6 +12,13 @@ use apsis_math::Vec3;
/// blocking body lies between the terminals. /// blocking body lies between the terminals.
/// ///
/// Returns `None` when either point is not finite. /// Returns `None` when either point is not finite.
///
/// 返回原点到两点连线的最短距离。
///
/// 最近点落在两端之间时它是垂距,否则是到较近一端的距离。对星间链路
/// 而言前一种情形是要紧的,因为遮挡天体位于两端之间。
///
/// 任一点非有限时返回 `None`。
pub fn closest_approach_to_centre(first: Vec3, second: Vec3) -> Option<f64> { pub fn closest_approach_to_centre(first: Vec3, second: Vec3) -> Option<f64> {
if !first.is_finite() || !second.is_finite() { if !first.is_finite() || !second.is_finite() {
return None; return None;
@ -39,6 +46,13 @@ pub fn closest_approach_to_centre(first: Vec3, second: Vec3) -> Option<f64> {
/// ///
/// Returns `None` when either position is not finite or the body radius is /// Returns `None` when either position is not finite or the body radius is
/// not positive. /// not positive.
///
/// 当两颗卫星能越过该天体互相可见时返回 `true`。
///
/// 判据是两位置之间的线段是否保持在给定半径的球体之外。恰好擦过表面
/// 的链路被视为通畅,因为该极限即几何地平线。
///
/// 任一位置非有限或天体半径非正时返回 `None`。
pub fn mutual_visibility(first: Vec3, second: Vec3, body_radius: f64) -> Option<bool> { pub fn mutual_visibility(first: Vec3, second: Vec3, body_radius: f64) -> Option<bool> {
if !body_radius.is_finite() || body_radius <= 0.0 { if !body_radius.is_finite() || body_radius <= 0.0 {
return None; return None;
@ -57,6 +71,14 @@ pub fn mutual_visibility(first: Vec3, second: Vec3, body_radius: f64) -> Option<
/// or when the body radius is not usable. A cell that reports its own /// or when the body radius is not usable. A cell that reports its own
/// position for both ends has no link, so that case is rejected rather than /// position for both ends has no link, so that case is rejected rather than
/// returned with a zero range. /// returned with a zero range.
///
/// 返回两颗卫星之间链路的完整几何。
///
/// 各方向是自每颗卫星指向对方的单位矢量。其中含对中心的最接近距离,使
/// 调用方能判断该链路有多少余量。
///
/// 任一位置非有限、两者重合或天体半径不可用时返回 `None`。两端回报
/// 自身位置的单元没有链路,故该情形被拒绝而非以零距离返回。
pub fn link_geometry(first: Vec3, second: Vec3, body_radius: f64) -> Option<LinkGeometry> { pub fn link_geometry(first: Vec3, second: Vec3, body_radius: f64) -> Option<LinkGeometry> {
if !body_radius.is_finite() || body_radius < 0.0 { if !body_radius.is_finite() || body_radius < 0.0 {
return None; return None;
@ -80,6 +102,8 @@ pub fn link_geometry(first: Vec3, second: Vec3, body_radius: f64) -> Option<Link
/// Returns `true` when the Sun is within the field of view of an optical /// Returns `true` when the Sun is within the field of view of an optical
/// link. /// link.
/// ///
/// 当太阳位于某光链路的视场内时返回 `true`。
///
/// The test is the angle between the link direction and the direction to the /// The test is the angle between the link direction and the direction to the
/// Sun. If that angle is within the field of view at either terminal, the /// Sun. If that angle is within the field of view at either terminal, the
/// Sun is in the beam and the link cannot be used. /// Sun is in the beam and the link cannot be used.

View File

@ -7,13 +7,24 @@ use apsis_math::Vec3;
/// The range is the straight line distance, which a link budget uses for the /// The range is the straight line distance, which a link budget uses for the
/// free space path loss. The two pointing vectors are the directions each /// free space path loss. The two pointing vectors are the directions each
/// terminal must aim, expressed in the frame the positions were given in. /// terminal must aim, expressed in the frame the positions were given in.
///
/// 两颗卫星之间链路的几何。
///
/// 距离是直线距离,链路预算以之计算自由空间路径损耗。两个指向矢量是各
/// 终端须瞄准的方向,表达在位置所给的坐标系中。
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct LinkGeometry { pub struct LinkGeometry {
/// The distance between the two satellites, in kilometers. /// The distance between the two satellites, in kilometers.
///
/// 两颗卫星之间的距离,单位为千米。
pub range_km: f64, pub range_km: f64,
/// The unit vector from the first satellite toward the second. /// The unit vector from the first satellite toward the second.
///
/// 自第一颗卫星指向第二颗的单位矢量。
pub direction_first_to_second: Vec3, pub direction_first_to_second: Vec3,
/// The unit vector from the second satellite toward the first. /// The unit vector from the second satellite toward the first.
///
/// 自第二颗卫星指向第一颗的单位矢量。
pub direction_second_to_first: Vec3, pub direction_second_to_first: Vec3,
/// The closest the line between the two passes to the centre of the /// The closest the line between the two passes to the centre of the
/// body, in kilometers. /// body, in kilometers.
@ -22,6 +33,11 @@ pub struct LinkGeometry {
/// reported because a link that clears the limb by a small margin is /// reported because a link that clears the limb by a small margin is
/// fragile: atmospheric refraction and the oblateness of the body both /// fragile: atmospheric refraction and the oblateness of the body both
/// eat into the margin. /// eat into the margin.
///
/// 两点连线与天体中心的最短距离,单位为千米。
///
/// 它决定该链路是否被遮挡。之所以报告,是因为仅以很小余量擦过临边的
/// 链路是脆弱的:大气折射与天体扁率都会侵蚀该余量。
pub centre_distance_km: f64, pub centre_distance_km: f64,
} }
@ -31,11 +47,17 @@ impl LinkGeometry {
/// This is how far the line between the two satellites clears the /// This is how far the line between the two satellites clears the
/// surface, measured at the closest point. A negative value means the /// surface, measured at the closest point. A negative value means the
/// link is blocked. /// link is blocked.
///
/// 返回该链路的擦地余量,单位为千米。
///
/// 它是两点连线在最接近处越过地表的量度。负值表示该链路被遮挡。
pub fn surface_clearance(&self, body_radius: f64) -> f64 { pub fn surface_clearance(&self, body_radius: f64) -> f64 {
self.centre_distance_km - body_radius self.centre_distance_km - body_radius
} }
/// Returns `true` when the link clears the body. /// Returns `true` when the link clears the body.
///
/// 当该链路越过天体时返回 `true`。
pub fn is_clear(&self, body_radius: f64) -> bool { pub fn is_clear(&self, body_radius: f64) -> bool {
self.centre_distance_km >= body_radius self.centre_distance_km >= body_radius
} }
@ -47,27 +69,44 @@ impl LinkGeometry {
/// unavailable are kept separate, because routing across a constellation has /// unavailable are kept separate, because routing across a constellation has
/// to distinguish a link that is geometrically blocked from one that is /// to distinguish a link that is geometrically blocked from one that is
/// merely not established yet. /// merely not established yet.
///
/// 某一瞬时链路的状态。
///
/// 链路或承载业务或否。链路不可用的各种原因分开保留,因为在星座上路由
/// 须区分被几何遮挡的链路与尚未建立的链路。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LinkState { pub enum LinkState {
/// The link is geometrically clear and may be used. /// The link is geometrically clear and may be used.
///
/// 该链路几何上通畅,可以使用。
Available, Available,
/// The Earth blocks the line between the two satellites. /// The Earth blocks the line between the two satellites.
///
/// 地球遮挡两颗卫星之间的连线。
Blocked, Blocked,
/// The Sun is within the field of view of one of the terminals, so an /// The Sun is within the field of view of one of the terminals, so an
/// optical link cannot be used. /// optical link cannot be used.
///
/// 太阳位于某一终端的视场内,故该光链路不可用。
SunInField, SunInField,
/// The link is clear but is not established, for example because it has /// The link is clear but is not established, for example because it has
/// not yet been acquired or its capacity is exhausted. /// not yet been acquired or its capacity is exhausted.
///
/// 该链路通畅但尚未建立,例如尚未捕获或其容量已耗尽。
NotEstablished, NotEstablished,
} }
impl LinkState { impl LinkState {
/// Returns `true` when the link may carry traffic. /// Returns `true` when the link may carry traffic.
///
/// 当该链路可承载业务时返回 `true`。
pub const fn is_usable(self) -> bool { pub const fn is_usable(self) -> bool {
matches!(self, Self::Available) matches!(self, Self::Available)
} }
/// Returns a short name for the state. /// Returns a short name for the state.
///
/// 返回该状态的简称。
pub const fn name(self) -> &'static str { pub const fn name(self) -> &'static str {
match self { match self {
Self::Available => "available", Self::Available => "available",

View File

@ -9,6 +9,13 @@ use apsis_topology::Topology;
/// hops. The result is in node order. /// hops. The result is in node order.
/// ///
/// Returns an empty vector when the node is out of range. /// Returns an empty vector when the node is out of range.
///
/// 沿边的方向返回自某节点可达的节点集合。
///
/// 起点节点被包含在内,因为节点经零跳路径可达自身。结果按节点次序
/// 排列。
///
/// 节点越界时返回空向量。
pub fn reachable_from(topology: &Topology, source: usize) -> Vec<usize> { pub fn reachable_from(topology: &Topology, source: usize) -> Vec<usize> {
if source >= topology.node_count() { if source >= topology.node_count() {
return Vec::new(); return Vec::new();
@ -43,6 +50,18 @@ pub fn reachable_from(topology: &Topology, source: usize) -> Vec<usize> {
/// Returns `Some(0)` when no path exists, and `None` when either node is out /// Returns `Some(0)` when no path exists, and `None` when either node is out
/// of range or the two nodes are the same, for which the question is not /// of range or the two nodes are the same, for which the question is not
/// defined. /// defined.
///
/// 返回两节点之间相互独立的路径条数。
///
/// 两条路径在不含公共边时相互独立。依 Menger 定理,该条数即使这两节点
/// 断连所需移除的最少边数,故即该连接可承受的链路失效次数。
///
/// 该值以两节点之间单位容量的最大流计算,采用 Ford-Fulkerson 方法的
/// Edmonds-Karp 变体,每步沿最短增广。对卫星网这样的小图它既快又
/// 精确。
///
/// 不存在路径时返回 `Some(0)`;任一节点越界或两节点相同(此时该问题
/// 无定义)时返回 `None`。
pub fn edge_connectivity(topology: &Topology, source: usize, destination: usize) -> Option<usize> { pub fn edge_connectivity(topology: &Topology, source: usize, destination: usize) -> Option<usize> {
if source >= topology.node_count() || destination >= topology.node_count() { if source >= topology.node_count() || destination >= topology.node_count() {
return None; return None;
@ -125,6 +144,18 @@ pub fn edge_connectivity(topology: &Topology, source: usize, destination: usize)
/// ///
/// Source: Kosaraju's algorithm as described in Cormen et al., /// Source: Kosaraju's algorithm as described in Cormen et al.,
/// *Introduction to Algorithms*, 3rd edition, MIT Press, 2009, chapter 22. /// *Introduction to Algorithms*, 3rd edition, MIT Press, 2009, chapter 22.
///
/// 返回强连通分量的数量。
///
/// 一个分量内每个节点都能沿边的方向到达其余每个节点时,它即强连通。
/// 该计数是各自内部可路由的独立网络数,对含定向星间链路的卫星网而
/// 言,它正是衡量该网由几部分构成的恰当度量。
///
/// 算法为 Kosaraju:一次深度优先搜索记录各节点的完成次序,再在反向
/// 边上按完成次序的逆序做第二次搜索,输出各分量。
///
/// 出处:Kosaraju 算法,见 Cormen 等,*Introduction to Algorithms*,
/// 第 3 版,MIT Press,2009,第 22 章。
pub fn strongly_connected_components(topology: &Topology) -> usize { pub fn strongly_connected_components(topology: &Topology) -> usize {
let node_count = topology.node_count(); let node_count = topology.node_count();
if node_count == 0 { if node_count == 0 {

View File

@ -3,22 +3,38 @@
use core::fmt; use core::fmt;
/// The category of a topology error. /// The category of a topology error.
///
/// 拓扑错误的类别。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive] #[non_exhaustive]
pub enum TopologyErrorKind { pub enum TopologyErrorKind {
/// A node index is outside the range of the graph. /// A node index is outside the range of the graph.
///
/// 节点下标超出该图的取值范围。
NoSuchNode, NoSuchNode,
/// An edge already exists between the two nodes. /// An edge already exists between the two nodes.
///
/// 两节点之间已存在该边。
DuplicateEdge, DuplicateEdge,
/// No edge exists between the two nodes. /// No edge exists between the two nodes.
///
/// 两节点之间不存在该边。
NoSuchEdge, NoSuchEdge,
/// A cost is not finite or not positive. /// A cost is not finite or not positive.
///
/// 代价非有限或非正。
InvalidCost, InvalidCost,
/// The series is empty. /// The series is empty.
///
/// 该序列为空。
EmptySeries, EmptySeries,
/// The times in a series are not strictly increasing. /// The times in a series are not strictly increasing.
///
/// 序列中的时刻非严格递增。
UnorderedTimes, UnorderedTimes,
/// A time is not finite. /// A time is not finite.
///
/// 某时刻非有限。
InvalidTime, InvalidTime,
} }
@ -38,6 +54,8 @@ impl fmt::Display for TopologyErrorKind {
} }
/// A failure while building or querying a topology. /// A failure while building or querying a topology.
///
/// 构建或查询拓扑时发生的失败。
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct TopologyError { pub struct TopologyError {
kind: TopologyErrorKind, kind: TopologyErrorKind,

View File

@ -3,6 +3,8 @@
use core::fmt; use core::fmt;
/// The category of a scenario error. /// The category of a scenario error.
///
/// 场景错误的类别。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive] #[non_exhaustive]
pub enum ScenarioErrorKind { pub enum ScenarioErrorKind {

View File

@ -3,14 +3,22 @@
use core::fmt; use core::fmt;
/// The category of a simulation error. /// The category of a simulation error.
///
/// 仿真错误的类别。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive] #[non_exhaustive]
pub enum SimulationErrorKind { pub enum SimulationErrorKind {
/// The scenario is not usable. /// The scenario is not usable.
///
/// 该场景不可用。
InvalidScenario, InvalidScenario,
/// The scheduled resources do not form a usable set. /// The scheduled resources do not form a usable set.
///
/// 所调度的资源未构成可用的集合。
InvalidResources, InvalidResources,
/// A scheduled placement does not match the task it names. /// A scheduled placement does not match the task it names.
///
/// 某处放置与其所指名的任务不匹配。
InconsistentPlan, InconsistentPlan,
} }
@ -26,6 +34,8 @@ impl fmt::Display for SimulationErrorKind {
} }
/// A failure while preparing or starting a simulation. /// A failure while preparing or starting a simulation.
///
/// 准备或启动仿真时发生的失败。
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct SimulationError { pub struct SimulationError {
kind: SimulationErrorKind, kind: SimulationErrorKind,
@ -41,6 +51,8 @@ impl SimulationError {
} }
/// Attaches a description of what caused the failure. /// Attaches a description of what caused the failure.
///
/// 附加对导致该失败之事的说明。
pub fn with_detail(mut self, detail: impl Into<String>) -> Self { pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
self.detail = Some(detail.into()); self.detail = Some(detail.into());
self self