docs: bilingual comments for access, coverage, isl and routing

This completes the network layer.
This commit is contained in:
ApsisRS contributors 2026-10-02 01:26:58 +08:00
parent 295ba16d0d
commit f3cf18df41
8 changed files with 211 additions and 0 deletions

View File

@ -18,6 +18,10 @@ use apsis_math::parallel::map_index;
/// ///
/// The sequential form, provided for comparison and for small sets where /// The sequential form, provided for comparison and for small sets where
/// starting a thread would cost more than the search. /// starting a thread would cost more than the search.
///
/// 按次序为许多轨迹搜索可见性。
///
/// 顺序形式,供比较以及线程启动代价高于搜索本身的小规模情形使用。
pub fn find_all( pub fn find_all(
finder: &AccessFinder, finder: &AccessFinder,
tracks: &[Vec<OrbitSample>], tracks: &[Vec<OrbitSample>],
@ -30,6 +34,11 @@ pub fn find_all(
/// The result matches [`find_all`] element for element and in the same order. /// The result matches [`find_all`] element for element and in the same order.
/// A track that cannot be searched yields its error in the same position the /// A track that cannot be searched yields its error in the same position the
/// sequential form would put it, so one bad track does not hide the rest. /// sequential form would put it, so one bad track does not hide the rest.
///
/// 跨多个线程为许多轨迹搜索可见性。
///
/// 结果与 [`find_all`] 逐元素、逐次序相同。无法搜索的轨迹在顺序形式会
/// 放置它的同一位置给出其错误,故一条坏轨迹不会掩盖其余。
pub fn find_all_parallel( pub fn find_all_parallel(
finder: &AccessFinder, finder: &AccessFinder,
tracks: &[Vec<OrbitSample>], tracks: &[Vec<OrbitSample>],

View File

@ -10,19 +10,32 @@ use core::f64::consts::PI;
/// is visible above a given elevation mask. On a sphere it is an exact /// is visible above a given elevation mask. On a sphere it is an exact
/// circle, described here by the central angle from the sub-satellite point /// circle, described here by the central angle from the sub-satellite point
/// and the radius of the body. /// and the radius of the body.
///
/// 圆形地面覆盖区。
///
/// 覆盖区是能在地平遮蔽以上看见某卫星的那部分地表。在球面上它是精确
/// 的圆,此处以自星下点的中心角和天体半径描述。
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct Footprint { pub struct Footprint {
/// The central half angle of the footprint, in radians. /// The central half angle of the footprint, in radians.
/// ///
/// This is the angle at the centre of the Earth from the sub-satellite /// This is the angle at the centre of the Earth from the sub-satellite
/// point to the edge of the footprint. /// point to the edge of the footprint.
///
/// 覆盖区的中心半角,单位为弧度。
///
/// 这是在地心处自星下点到覆盖区边缘的角度。
pub central_angle: f64, pub central_angle: f64,
/// The radius of the body, in kilometers. /// The radius of the body, in kilometers.
///
/// 天体的半径,单位为千米。
pub body_radius: f64, pub body_radius: f64,
} }
impl Footprint { impl Footprint {
/// Creates a footprint from its central angle and body radius. /// Creates a footprint from its central angle and body radius.
///
/// 由其中心角与天体半径创建覆盖区。
pub const fn new(central_angle: f64, body_radius: f64) -> Self { pub const fn new(central_angle: f64, body_radius: f64) -> Self {
Self { Self {
central_angle, central_angle,
@ -32,6 +45,8 @@ impl Footprint {
/// Returns the footprint radius measured along the surface, in /// Returns the footprint radius measured along the surface, in
/// kilometers. /// kilometers.
///
/// 返回沿地表量得的覆盖区半径,单位为千米。
pub fn surface_radius(&self) -> f64 { pub fn surface_radius(&self) -> f64 {
self.central_angle * self.body_radius self.central_angle * self.body_radius
} }
@ -45,6 +60,16 @@ impl Footprint {
/// ``` /// ```
/// ///
/// which is exact for a spherical body. /// which is exact for a spherical body.
///
/// 返回覆盖区的面积,单位为平方千米。
///
/// 该面积是被圆锥截去的球面部分,
///
/// ```text
/// A = 2 pi R^2 (1 - cos(central))
/// ```
///
/// 对球形天体这是精确的。
pub fn area(&self) -> f64 { pub fn area(&self) -> f64 {
2.0 * PI * self.body_radius * self.body_radius * (1.0 - self.central_angle.cos()) 2.0 * PI * self.body_radius * self.body_radius * (1.0 - self.central_angle.cos())
} }
@ -53,6 +78,11 @@ impl Footprint {
/// ///
/// The value is between zero and one half, because a footprint from a /// The value is between zero and one half, because a footprint from a
/// point above the surface can never cover more than a hemisphere. /// point above the surface can never cover more than a hemisphere.
///
/// 返回覆盖区占整个球面的比例。
///
/// 该值介于零与二分之一之间,因为来自地表之上某点的覆盖区覆盖范围
/// 绝不会超过一个半球。
pub fn fraction_of_surface(&self) -> f64 { pub fn fraction_of_surface(&self) -> f64 {
self.area() / (4.0 * PI * self.body_radius * self.body_radius) self.area() / (4.0 * PI * self.body_radius * self.body_radius)
} }
@ -62,6 +92,10 @@ impl Footprint {
/// ///
/// The comparison is inclusive, so a point exactly on the edge is /// The comparison is inclusive, so a point exactly on the edge is
/// inside. /// inside.
///
/// 当距星下点给定中心角的地面点位于覆盖区内时返回 `true`。
///
/// 比较是含端点的,故恰在边缘上的点算在内部。
pub fn contains_central_angle(&self, central_angle: f64) -> bool { pub fn contains_central_angle(&self, central_angle: f64) -> bool {
central_angle.is_finite() && central_angle >= 0.0 && central_angle <= self.central_angle central_angle.is_finite() && central_angle >= 0.0 && central_angle <= self.central_angle
} }
@ -136,6 +170,13 @@ pub fn footprint_fraction(
/// ///
/// Returns `None` when either vector has zero length or a non-finite /// Returns `None` when either vector has zero length or a non-finite
/// component. /// component.
///
/// 返回两个地表点之间的角度,单位为弧度。
///
/// 两矢量必须是相对天体中心、在同一坐标系中的位置。该角度是自中心看
/// 它们之间的中心角。
///
/// 任一矢量为零长或含非有限分量时返回 `None`。
pub fn central_angle_between(first: Vec3, second: Vec3) -> Option<f64> { pub fn central_angle_between(first: Vec3, second: Vec3) -> Option<f64> {
if !first.is_finite() || !second.is_finite() { if !first.is_finite() || !second.is_finite() {
return None; return None;
@ -159,6 +200,13 @@ pub fn central_angle_between(first: Vec3, second: Vec3) -> Option<f64> {
/// and the central half angle. /// and the central half angle.
/// ///
/// Returns `None` when either vector is degenerate or not finite. /// Returns `None` when either vector is degenerate or not finite.
///
/// 当某地面点位于某覆盖区内时返回 `true`。
///
/// 该点与星下点都是相对天体中心的位置,单位为千米。覆盖区提供天体半径
/// 与中心半角。
///
/// 任一矢量退化或非有限时返回 `None`。
pub fn point_in_footprint( pub fn point_in_footprint(
footprint: &Footprint, footprint: &Footprint,
point: Vec3, point: Vec3,

View File

@ -37,6 +37,13 @@ use core::f64::consts::FRAC_PI_2;
/// ///
/// Returns `None` when the radii are not usable, when the body radius /// Returns `None` when the radii are not usable, when the body radius
/// exceeds the satellite radius, or when the elevation is not finite. /// exceeds the satellite radius, or when the elevation is not finite.
///
/// 返回卫星处的天底角,单位为弧度。
///
/// 天底角在卫星处量取,介于到地心的方向与到地面点的方向之间。当地面点
/// 恰在卫星正下方时它为零,并随该点移向地平而增大。
///
/// 半径不可用、天体半径超过卫星半径,或仰角非有限时返回 `None`。
pub fn nadir_angle(satellite_radius: f64, body_radius: f64, elevation: f64) -> Option<f64> { pub fn nadir_angle(satellite_radius: f64, body_radius: f64, elevation: f64) -> Option<f64> {
if !satellite_radius.is_finite() || !body_radius.is_finite() || !elevation.is_finite() { if !satellite_radius.is_finite() || !body_radius.is_finite() || !elevation.is_finite() {
return None; return None;
@ -46,6 +53,7 @@ pub fn nadir_angle(satellite_radius: f64, body_radius: f64, elevation: f64) -> O
} }
let sine = body_radius * elevation.cos() / satellite_radius; let sine = body_radius * elevation.cos() / satellite_radius;
// Rounding can push the sine a little above one for a grazing geometry. // Rounding can push the sine a little above one for a grazing geometry.
// 对掠射几何,舍入可能把正弦略微推到一以上。
Some(sine.clamp(-1.0, 1.0).asin()) Some(sine.clamp(-1.0, 1.0).asin())
} }
@ -64,6 +72,8 @@ pub fn earth_central_angle(satellite_radius: f64, body_radius: f64, elevation: f
// Rounding can leave a small negative value at the zenith, where the // Rounding can leave a small negative value at the zenith, where the
// true angle is zero. The negative part is not physical, so it is // true angle is zero. The negative part is not physical, so it is
// removed before the result is returned. // removed before the result is returned.
// 在天顶处(真实角为零)舍入可能留下小的负值。该负值没有物理意义,
// 故在返回结果前将其去掉。
Some((FRAC_PI_2 - elevation - nadir).max(0.0)) Some((FRAC_PI_2 - elevation - nadir).max(0.0))
} }
@ -118,6 +128,20 @@ pub fn slant_range(satellite_radius: f64, body_radius: f64, elevation: f64) -> O
/// ///
/// Returns `None` when the radii are not usable or when the central angle is /// Returns `None` when the radii are not usable or when the central angle is
/// not finite. /// not finite.
///
/// 返回某中心角对应的仰角,单位为弧度。
///
/// 它反解 [`earth_central_angle`]。当已知地面点相对星下点的位置而要求
/// 该点处仰角时使用它。
///
/// 关系为
///
/// ```text
/// tan(elevation) = (cos(central) - body_radius / satellite_radius)
/// / sin(central)
/// ```
///
/// 半径不可用或中心角非有限时返回 `None`。
pub fn elevation_from_central_angle( pub fn elevation_from_central_angle(
satellite_radius: f64, satellite_radius: f64,
body_radius: f64, body_radius: f64,
@ -133,6 +157,7 @@ pub fn elevation_from_central_angle(
let sine = central_angle.sin(); let sine = central_angle.sin();
if sine == 0.0 { if sine == 0.0 {
// Directly below the satellite the elevation is a right angle. // Directly below the satellite the elevation is a right angle.
// 在卫星正下方仰角为直角。
return Some(FRAC_PI_2); return Some(FRAC_PI_2);
} }
Some(((central_angle.cos() - ratio) / sine).atan()) Some(((central_angle.cos() - ratio) / sine).atan())

View File

@ -6,22 +6,39 @@ use apsis_math::Vec3;
use core::f64::consts::FRAC_PI_2; use core::f64::consts::FRAC_PI_2;
/// A ground station, given as a geodetic position on an ellipsoid. /// A ground station, given as a geodetic position on an ellipsoid.
///
/// 一个地面站,以椭球上的大地位置给出。
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct GroundPoint { pub struct GroundPoint {
/// The geodetic position of the station. /// The geodetic position of the station.
///
/// 该站的大地位置。
pub position: Geodetic, pub position: Geodetic,
/// The ellipsoid the position is referred to. /// The ellipsoid the position is referred to.
///
/// 该位置所参照的椭球。
///
/// 该位置所参照的椭球。
///
/// 该位置所参照的椭球。
pub ellipsoid: Ellipsoid, pub ellipsoid: Ellipsoid,
/// The sensor elevation mask, in radians. /// The sensor elevation mask, in radians.
/// ///
/// The minimum elevation above the local horizon at which the station /// The minimum elevation above the local horizon at which the station
/// can see a satellite. A mask of zero means the station sees down to /// can see a satellite. A mask of zero means the station sees down to
/// its own horizon. /// its own horizon.
///
/// 传感器仰角遮蔽,单位为弧度。
///
/// 该站能看到卫星所需高出当地地平的最小仰角。遮蔽为零意味着该站
/// 能看到直至自身地平。
pub elevation_mask: f64, pub elevation_mask: f64,
} }
impl GroundPoint { impl GroundPoint {
/// Creates a ground point on the WGS 84 ellipsoid with a given mask. /// Creates a ground point on the WGS 84 ellipsoid with a given mask.
///
/// 在 WGS 84 椭球上以给定遮蔽创建地面点。
pub fn new(position: Geodetic, elevation_mask: f64) -> Self { pub fn new(position: Geodetic, elevation_mask: f64) -> Self {
Self { Self {
position, position,
@ -35,6 +52,11 @@ impl GroundPoint {
/// ///
/// The geodetic conversion yields meters, so the result is divided by a /// The geodetic conversion yields meters, so the result is divided by a
/// thousand to match the frame the orbit layer uses. /// thousand to match the frame the orbit layer uses.
///
/// 返回该站在地固直角坐标系中的位置,单位为千米。
///
/// 大地转换得到的是米,故结果除以一千,以与 orbit 层所用的坐标系
/// 一致。
pub fn ecef(&self) -> Vec3 { pub fn ecef(&self) -> Vec3 {
self.position.to_cartesian(self.ellipsoid) / 1_000.0 self.position.to_cartesian(self.ellipsoid) / 1_000.0
} }
@ -43,12 +65,18 @@ impl GroundPoint {
/// ///
/// This is the local radius of the ellipsoid at the station's latitude /// This is the local radius of the ellipsoid at the station's latitude
/// plus its height, so it includes the effect of the flattening. /// plus its height, so it includes the effect of the flattening.
///
/// 返回距地心的距离,单位为千米。
///
/// 这是该站纬度处椭球的当地半径加上其高度,故包含了扁率的影响。
pub fn radius(&self) -> f64 { pub fn radius(&self) -> f64 {
self.ecef().norm() self.ecef().norm()
} }
/// Returns `true` when the station and its mask describe a usable /// Returns `true` when the station and its mask describe a usable
/// sensor. /// sensor.
///
/// 当该站及其遮蔽描述一个可用传感器时返回 `true`。
pub fn is_valid(&self) -> bool { pub fn is_valid(&self) -> bool {
self.position.is_valid() self.position.is_valid()
&& self.elevation_mask.is_finite() && self.elevation_mask.is_finite()
@ -63,17 +91,28 @@ impl GroundPoint {
/// because a station that can point anywhere with sufficient gain is /// because a station that can point anywhere with sufficient gain is
/// modelled by its elevation mask alone; the direction matters only when the /// modelled by its elevation mask alone; the direction matters only when the
/// station has a fixed beam. /// station has a fixed beam.
///
/// 位于地面点、具有指向方向的传感器。
///
/// 方向是地固系中的单位矢量。它是可选的,因为能以足够增益指向任何
/// 方向的站仅以其仰角遮蔽建模即可;只有当站具有固定波束时方向才要紧。
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct Sensor { pub struct Sensor {
/// The station. /// The station.
///
/// 该地面站。
pub ground: GroundPoint, pub ground: GroundPoint,
/// The pointing direction in the Earth-fixed frame, if the station is /// The pointing direction in the Earth-fixed frame, if the station is
/// directional. /// directional.
///
/// 地固系中的指向方向,若该站是定向的。
pub pointing: Option<Vec3>, pub pointing: Option<Vec3>,
} }
impl Sensor { impl Sensor {
/// Creates a station that can point anywhere above its mask. /// Creates a station that can point anywhere above its mask.
///
/// 创建能在其遮蔽之上指向任何方向的站。
pub const fn omnidirectional(ground: GroundPoint) -> Self { pub const fn omnidirectional(ground: GroundPoint) -> Self {
Self { Self {
ground, ground,
@ -85,6 +124,10 @@ impl Sensor {
/// ///
/// The direction is normalized when it is used, so the caller may pass /// The direction is normalized when it is used, so the caller may pass
/// an unnormalized vector. /// an unnormalized vector.
///
/// 创建具有固定指向方向的站。
///
/// 该方向在使用时被归一化,故调用方可以传入未归一化的矢量。
pub const fn directional(ground: GroundPoint, pointing: Vec3) -> Self { pub const fn directional(ground: GroundPoint, pointing: Vec3) -> Self {
Self { Self {
ground, ground,
@ -94,11 +137,17 @@ impl Sensor {
} }
/// The point on the Earth's surface directly below a satellite. /// The point on the Earth's surface directly below a satellite.
///
/// 卫星正下方的地表点。
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct SubSatellitePoint { pub struct SubSatellitePoint {
/// The geodetic position of the sub-satellite point. /// The geodetic position of the sub-satellite point.
///
/// 星下点的大地位置。
pub position: Geodetic, pub position: Geodetic,
/// The ellipsoid the position is referred to. /// The ellipsoid the position is referred to.
///
/// 该位置所参照的椭球。
pub ellipsoid: Ellipsoid, pub ellipsoid: Ellipsoid,
} }
@ -116,6 +165,16 @@ pub struct SubSatellitePoint {
/// ///
/// Returns `None` when the position is not finite or lies on the polar axis, /// Returns `None` when the position is not finite or lies on the polar axis,
/// where no longitude is defined. /// where no longitude is defined.
///
/// 返回地固系中某位置的星下点。
///
/// 卫星位置单位为千米。星下点是从地心过卫星的直线与椭球的交点,即把
/// 卫星高度置零而纬经度不变所得的大地位置。
///
/// 这是地心投影,而非过卫星的椭球法线与地表相交的点。对低轨卫星两者
/// 相差至多约二十千米,而覆盖区几何用的是地心形式。
///
/// 位置非有限,或位于未定义经度的极轴上时返回 `None`。
pub fn sub_satellite_point( pub fn sub_satellite_point(
position_ecef_km: Vec3, position_ecef_km: Vec3,
ellipsoid: Ellipsoid, ellipsoid: Ellipsoid,
@ -142,6 +201,15 @@ pub fn sub_satellite_point(
/// below the horizon. /// below the horizon.
/// ///
/// Returns `None` when either position is not usable. /// Returns `None` when either position is not usable.
///
/// 返回自某站看某卫星的仰角,单位为弧度。
///
/// 卫星位置在地固系中,单位为千米。仰角自当地地平量起,后者是垂直于该
/// 站处椭球法线的平面。
///
/// 正值表示卫星在地平之上,直角值表示在天顶,负值表示在地平之下。
///
/// 任一位置不可用时返回 `None`。
pub fn elevation_angle(satellite_ecef_km: Vec3, ground: &GroundPoint) -> Option<f64> { pub fn elevation_angle(satellite_ecef_km: Vec3, ground: &GroundPoint) -> Option<f64> {
if !satellite_ecef_km.is_finite() || !ground.is_valid() { if !satellite_ecef_km.is_finite() || !ground.is_valid() {
return None; return None;
@ -153,6 +221,8 @@ pub fn elevation_angle(satellite_ecef_km: Vec3, ground: &GroundPoint) -> Option<
} }
// The local up direction is the ellipsoid normal at the station, which // The local up direction is the ellipsoid normal at the station, which
// for a geodetic position is the direction the surface normal points. // for a geodetic position is the direction the surface normal points.
// 当地朝上方向是该站处的椭球法线,对大地位置而言即地表法线所指的
// 方向。
let up = local_up(ground); let up = local_up(ground);
let sine = line_of_sight.normalize()?.dot(up.normalize()?); let sine = line_of_sight.normalize()?.dot(up.normalize()?);
Some(sine.clamp(-1.0, 1.0).asin()) Some(sine.clamp(-1.0, 1.0).asin())
@ -164,6 +234,11 @@ pub fn elevation_angle(satellite_ecef_km: Vec3, ground: &GroundPoint) -> Option<
/// geodetic position it follows from the latitude and longitude, because the /// geodetic position it follows from the latitude and longitude, because the
/// normal of a biaxial ellipsoid lies in the meridian plane and makes the /// normal of a biaxial ellipsoid lies in the meridian plane and makes the
/// geodetic latitude with the equatorial plane. /// geodetic latitude with the equatorial plane.
///
/// 返回沿某站处椭球法线的单位矢量。
///
/// 这是当地垂线,即铅垂线所指的方向。对大地位置而言,它由纬度与经度
/// 得出,因为双轴椭球的法线位于子午面内,并与赤道面夹大地纬度。
pub fn local_up(ground: &GroundPoint) -> Vec3 { pub fn local_up(ground: &GroundPoint) -> Vec3 {
let latitude = ground.position.latitude; let latitude = ground.position.latitude;
let longitude = ground.position.longitude; let longitude = ground.position.longitude;
@ -182,6 +257,12 @@ pub fn local_up(ground: &GroundPoint) -> Vec3 {
/// other visibility functions. /// other visibility functions.
/// ///
/// Returns `None` when either position is not usable. /// Returns `None` when either position is not usable.
///
/// 返回自某站看某卫星的仰角,单位为弧度。
///
/// 这与 [`elevation_angle`] 相同,其命名是为与其余可见性函数对称。
///
/// 任一位置不可用时返回 `None`。
pub fn line_of_sight(satellite_ecef_km: Vec3, ground: &GroundPoint) -> Option<f64> { pub fn line_of_sight(satellite_ecef_km: Vec3, ground: &GroundPoint) -> Option<f64> {
elevation_angle(satellite_ecef_km, ground) elevation_angle(satellite_ecef_km, ground)
} }
@ -194,6 +275,14 @@ pub fn line_of_sight(satellite_ecef_km: Vec3, ground: &GroundPoint) -> Option<f6
/// the satellite is in the forward hemisphere of the beam. /// the satellite is in the forward hemisphere of the beam.
/// ///
/// Returns `None` when the satellite position or the sensor is not usable. /// Returns `None` when the satellite position or the sensor is not usable.
///
/// 当从某传感器可见某卫星时返回 `true`。
///
/// 当卫星在该站处的仰角不低于该传感器的遮蔽,且(对定向传感器)到卫星
/// 的方向与指向方向的夹角在一个直角以内,即卫星处于波束的前半球时,
/// 卫星可见。
///
/// 卫星位置或传感器不可用时返回 `None`。
pub fn visible_from(satellite_ecef_km: Vec3, sensor: &Sensor) -> Option<bool> { pub fn visible_from(satellite_ecef_km: Vec3, sensor: &Sensor) -> Option<bool> {
let elevation = elevation_angle(satellite_ecef_km, &sensor.ground)?; let elevation = elevation_angle(satellite_ecef_km, &sensor.ground)?;
if elevation < sensor.ground.elevation_mask { if elevation < sensor.ground.elevation_mask {
@ -216,6 +305,13 @@ pub fn visible_from(satellite_ecef_km: Vec3, sensor: &Sensor) -> Option<bool> {
/// position, without building a [`GroundPoint`]. /// position, without building a [`GroundPoint`].
/// ///
/// Returns `None` when the satellite position or the station is not usable. /// Returns `None` when the satellite position or the station is not usable.
///
/// 返回自某大地站看某卫星的仰角。
///
/// 这是为直接把站作为大地位置给出、而不构建 [`GroundPoint`] 的便利
/// 函数。
///
/// 卫星位置或该站不可用时返回 `None`。
pub fn visible_from_geodetic( pub fn visible_from_geodetic(
satellite_ecef_km: Vec3, satellite_ecef_km: Vec3,
position: Geodetic, position: Geodetic,

View File

@ -61,6 +61,8 @@ pub fn visibility_matrix_parallel(positions: &[Vec3], body_radius: f64) -> Vec<b
// The pairs are independent, so splitting the flat index space across // The pairs are independent, so splitting the flat index space across
// threads needs no coordination. `map_index` owns the output buffer, // threads needs no coordination. `map_index` owns the output buffer,
// which is what lets the threads write disjoint slots of one allocation. // which is what lets the threads write disjoint slots of one allocation.
// 各点对相互独立,故把扁平下标空间跨线程划分无需协调。`map_index`
// 拥有输出缓冲,这正是让各线程写同一分配中互不相交槽位的原因。
map_index(len * len, |index| { map_index(len * len, |index| {
let row = index / len; let row = index / len;
let column = index - row * len; let column = index - row * len;

View File

@ -28,11 +28,13 @@ pub fn closest_approach_to_centre(first: Vec3, second: Vec3) -> Option<f64> {
if length_squared == 0.0 { if length_squared == 0.0 {
// The two points coincide, so the distance is the radius of that // The two points coincide, so the distance is the radius of that
// point. // point.
// 两点重合,故距离即该点的半径。
return Some(first.norm()); return Some(first.norm());
} }
// The parameter of the closest point on the infinite line, clamped to // The parameter of the closest point on the infinite line, clamped to
// the segment. // the segment.
// 无限直线上最接近点的参数,被夹逼到该线段。
let parameter = (-first.dot(segment) / length_squared).clamp(0.0, 1.0); let parameter = (-first.dot(segment) / length_squared).clamp(0.0, 1.0);
let closest = first + segment * parameter; let closest = first + segment * parameter;
Some(closest.norm()) Some(closest.norm())

View File

@ -71,6 +71,7 @@ pub fn edge_connectivity(topology: &Topology, source: usize, destination: usize)
} }
// A residual capacity for each edge, indexed by the edge position. // A residual capacity for each edge, indexed by the edge position.
// 每条边一个残余容量,按边的位置索引。
let edges = topology.edges(); let edges = topology.edges();
let mut residual: Vec<usize> = vec![1; edges.len()]; let mut residual: Vec<usize> = vec![1; edges.len()];
@ -78,6 +79,7 @@ pub fn edge_connectivity(topology: &Topology, source: usize, destination: usize)
loop { loop {
// Breadth first search for an augmenting path in the residual // Breadth first search for an augmenting path in the residual
// graph. // graph.
// 在残余图中广度优先搜索一条增广路径。
let mut parent_edge: Vec<Option<usize>> = vec![None; topology.node_count()]; let mut parent_edge: Vec<Option<usize>> = vec![None; topology.node_count()];
let mut visited = vec![false; topology.node_count()]; let mut visited = vec![false; topology.node_count()];
visited[source] = true; visited[source] = true;
@ -90,6 +92,7 @@ pub fn edge_connectivity(topology: &Topology, source: usize, destination: usize)
} }
for (index, edge) in edges.iter().enumerate() { for (index, edge) in edges.iter().enumerate() {
// Forward edges with residual capacity. // Forward edges with residual capacity.
// 具有残余容量的正向边。
if edge.from == node && residual[index] > 0 && !visited[edge.to] { if edge.from == node && residual[index] > 0 && !visited[edge.to] {
visited[edge.to] = true; visited[edge.to] = true;
parent_edge[edge.to] = Some(index); parent_edge[edge.to] = Some(index);
@ -98,6 +101,7 @@ pub fn edge_connectivity(topology: &Topology, source: usize, destination: usize)
} }
// Reverse edges, which exist when flow has been pushed the // Reverse edges, which exist when flow has been pushed the
// other way and which cancel it. // other way and which cancel it.
// 反向边,当流量已沿另一方向推送时存在,用以抵消它。
if edge.to == node && residual[index] == 0 && !visited[edge.from] { if edge.to == node && residual[index] == 0 && !visited[edge.from] {
visited[edge.from] = true; visited[edge.from] = true;
parent_edge[edge.from] = Some(index); parent_edge[edge.from] = Some(index);
@ -113,6 +117,8 @@ pub fn edge_connectivity(topology: &Topology, source: usize, destination: usize)
// Walk the path back, adjusting the residual capacities. An edge // Walk the path back, adjusting the residual capacities. An edge
// traversed in its own direction consumes one unit; an edge // traversed in its own direction consumes one unit; an edge
// traversed against its direction restores one. // traversed against its direction restores one.
// 沿路径回溯,调整残余容量。沿自身方向穿越的边消耗一个单位;
// 逆自身方向穿越的边恢复一个单位。
let mut node = destination; let mut node = destination;
while node != source { while node != source {
let index = parent_edge[node]?; let index = parent_edge[node]?;
@ -163,6 +169,7 @@ pub fn strongly_connected_components(topology: &Topology) -> usize {
} }
// First pass: the finishing order over the forward edges. // First pass: the finishing order over the forward edges.
// 第一遍:在正向边上求完成次序。
let mut visited = vec![false; node_count]; let mut visited = vec![false; node_count];
let mut order = Vec::with_capacity(node_count); let mut order = Vec::with_capacity(node_count);
for start in 0..node_count { for start in 0..node_count {
@ -171,6 +178,7 @@ pub fn strongly_connected_components(topology: &Topology) -> usize {
} }
// An iterative depth first search, pushing a node again to mark the // An iterative depth first search, pushing a node again to mark the
// point at which its children are done. // point at which its children are done.
// 迭代式深度优先搜索,把某节点再次压栈以标记其子节点完成之处。
let mut stack = vec![(start, false)]; let mut stack = vec![(start, false)];
while let Some((node, processed)) = stack.pop() { while let Some((node, processed)) = stack.pop() {
if processed { if processed {
@ -192,6 +200,7 @@ pub fn strongly_connected_components(topology: &Topology) -> usize {
// Second pass: the reverse edges, taking nodes in reverse finishing // Second pass: the reverse edges, taking nodes in reverse finishing
// order. // order.
// 第二遍:在反向边上按完成次序的逆序取节点。
let mut assigned = vec![false; node_count]; let mut assigned = vec![false; node_count];
let mut components = 0; let mut components = 0;
for node in order.into_iter().rev() { for node in order.into_iter().rev() {

View File

@ -242,6 +242,23 @@ pub fn shortest_path(
/// the one described in Fraire, J. A. et al., *Routing in Delay-Tolerant /// the one described in Fraire, J. A. et al., *Routing in Delay-Tolerant
/// Networks Comprising Heterogeneous Populations of Deterministic Link /// Networks Comprising Heterogeneous Populations of Deterministic Link
/// Dynamics*, IEEE Transactions on Mobile Computing, 2020. /// Dynamics*, IEEE Transactions on Mobile Computing, 2020.
///
/// 返回在拓扑序列某区间上有效的最短路由。
///
/// 该路由在贯穿整个区间都存在的链路上计算,故它在整个区间上保持有效。
/// 该区间取自 `start_seconds` 处生效样本的起点,到 `end_seconds` 处生效
/// 样本的起点,并对该跨度内每个样本的链路求交。
///
/// 所返回的路段记录该路由有效的区间,它以 `end_seconds` 之后的下一处
/// 拓扑变化为界。
///
/// 任一节点越界或两者相同、区间次序不对,或没有路由能存活整个区间时
/// 返回错误。
///
/// 出处:时变图上的存储转发路由模型见 Fraire, J. A. 等,*Routing in
/// Delay-Tolerant Networks Comprising Heterogeneous Populations of
/// Deterministic Link Dynamics*,IEEE Transactions on Mobile Computing,
/// 2020。
pub fn route_over_series( pub fn route_over_series(
series: &TopologySeries, series: &TopologySeries,
source: usize, source: usize,
@ -284,6 +301,8 @@ pub fn route_over_series(
// Intersect the links of every sample spanned by the interval, taking // Intersect the links of every sample spanned by the interval, taking
// the cost from the first sample that has each link. // the cost from the first sample that has each link.
// 对该区间所跨的每个样本的链路求交,代价取自拥有每条链路的第一
// 个样本。
let mut intersection = Topology::new(series.node_count()); let mut intersection = Topology::new(series.node_count());
let reference = &series.samples()[first].topology; let reference = &series.samples()[first].topology;
for edge in reference.edges() { for edge in reference.edges() {
@ -302,6 +321,7 @@ pub fn route_over_series(
let route = shortest_path(&intersection, source, destination)?; let route = shortest_path(&intersection, source, destination)?;
// The leg is valid until the topology changes after the interval ends. // The leg is valid until the topology changes after the interval ends.
// 该路段有效直到区间结束之后的拓扑变化。
let end_of_validity = series let end_of_validity = series
.interval_of(last) .interval_of(last)
.map_or(end_seconds, |(_, next)| next); .map_or(end_seconds, |(_, next)| next);