From 78614d0a97f0a544ad63a8aefd0bc23c3f257be8 Mon Sep 17 00:00:00 2001 From: ApsisRS contributors Date: Fri, 2 Oct 2026 01:41:57 +0800 Subject: [PATCH] docs: bilingual comments for the coverage and link validation scripts --- .../python/constellation/check_coverage.py | 63 +++++++++++++++++-- validation/python/link/check_link.py | 29 ++++++++- validation/python/link/check_s465.py | 31 ++++++++- 3 files changed, 114 insertions(+), 9 deletions(-) diff --git a/validation/python/constellation/check_coverage.py b/validation/python/constellation/check_coverage.py index 1a00aa2..8301867 100644 --- a/validation/python/constellation/check_coverage.py +++ b/validation/python/constellation/check_coverage.py @@ -27,6 +27,31 @@ Usage: Exit status is zero when every quantity agrees within tolerance and one otherwise. + +用球面三角交叉核对 ApsisRS 的覆盖几何。 + +读取由 ``cargo run -p apsis-rs --features network --example dump_coverage`` +写出的量,并只用标准库与 numpy 从球面三角形关系逐个重算。要点在于 Python +侧是依据几何而非依据 Rust 代码写成的,故 Rust 公式中的错误会表现为不一致。 + +由卫星、地面点与地心构成的三角形的三个角满足 + + elevation + nadir + central = 90 degrees + +正弦定理给出 + + sin(nadir) / body_radius = cos(elevation) / satellite_radius + +由此得出天底角、中心角与斜距。覆盖区是中心角从天体上截出的球冠,故其面积 +是带面积,其比例即该面积除以整个表面。 + +本脚本不需要网络访问。 + +用法: + + pixi run python validation/python/constellation/check_coverage.py [path] + +每个量都在容差内一致时退出状态为零,否则为一。 """ from __future__ import annotations @@ -39,6 +64,8 @@ from pathlib import Path # The agreement is at the floating point level because both sides evaluate # the same closed form expressions. The tolerances are set to catch a # transposed radius or a missing conversion, not to allow a loose match. +# 相符程度在浮点水平,因为两侧求的是相同的闭式表达式。这些容差是为捕获 +# 半径写反或漏换算而设,并非为允许宽松匹配。 ANGLE_TOLERANCE_RAD = 1e-12 LENGTH_TOLERANCE_KM = 1e-9 AREA_RELATIVE_TOLERANCE = 1e-12 @@ -48,18 +75,27 @@ DEFAULT_INPUT = Path("validation/data/expected/rust_coverage.csv") def reference_nadir(satellite_radius: float, body_radius: float, elevation: float) -> float: - """Returns the nadir angle from the law of sines.""" + """Returns the nadir angle from the law of sines. + + 由正弦定理返回天底角。 + """ return math.asin(body_radius * math.cos(elevation) / satellite_radius) def reference_central(satellite_radius: float, body_radius: float, elevation: float) -> float: - """Returns the central angle from the angle sum.""" + """Returns the central angle from the angle sum. + + 由角和返回中心角。 + """ nadir = reference_nadir(satellite_radius, body_radius, elevation) return max(0.0, math.pi / 2.0 - elevation - nadir) def reference_range(satellite_radius: float, body_radius: float, elevation: float) -> float: - """Returns the slant range from the law of cosines.""" + """Returns the slant range from the law of cosines. + + 由余弦定理返回斜距。 + """ central = reference_central(satellite_radius, body_radius, elevation) squared = ( satellite_radius**2 @@ -76,6 +112,11 @@ def reference_cap_area(central: float, body_radius: float) -> float: ``2 pi R^2 (1 - cos(central))``. The footprint is not centred on a pole in general, but a spherical cap is rotationally symmetric, so the same expression applies. + + 返回给定中心角的球冠面积。 + + 球冠是极点与某余纬之间的带,故其面积为 ``2 pi R^2 (1 - cos(central))``。 + 覆盖区通常并不以极点为中心,但球冠具有旋转对称性,故同一表达式适用。 """ return 2.0 * math.pi * body_radius**2 * (1.0 - math.cos(central)) @@ -85,6 +126,11 @@ def read_blocks(path: Path): The first block is the case table with a header row; a blank line separates it from the inverse relation block, which has its own header. + + 读取该文件的两个块。 + + 第一块是带表头的用例表;一个空行把它与反解关系块分开,后者有自己的 + 表头。 """ text = path.read_text().splitlines() blocks: list[list[str]] = [[]] @@ -97,7 +143,10 @@ def read_blocks(path: Path): def check_cases(rows: list[dict[str, str]]) -> tuple[bool, list[str]]: - """Checks the case table and returns whether it passed and any failures.""" + """Checks the case table and returns whether it passed and any failures. + + 检查用例表,返回是否通过以及任何失败项。 + """ failures: list[str] = [] compared = 0 worst = { @@ -158,7 +207,10 @@ def check_cases(rows: list[dict[str, str]]) -> tuple[bool, list[str]]: def check_inverse(rows: list[dict[str, str]]) -> tuple[bool, list[str]]: - """Checks that the central angle and elevation relations invert.""" + """Checks that the central angle and elevation relations invert. + + 检查中心角与仰角关系可互相反解。 + """ failures: list[str] = [] compared = 0 worst = 0.0 @@ -172,6 +224,7 @@ def check_inverse(rows: list[dict[str, str]]) -> tuple[bool, list[str]]: # Recompute the round trip independently: from the elevation, the # central angle follows from the angle sum. + # 独立重算往返:由仰角经角和得出中心角。 satellite_radius = 6_378.137 + 550.0 body_radius = 6_378.137 nadir = reference_nadir(satellite_radius, body_radius, elevation) diff --git a/validation/python/link/check_link.py b/validation/python/link/check_link.py index c7ca661..b945ebc 100644 --- a/validation/python/link/check_link.py +++ b/validation/python/link/check_link.py @@ -17,6 +17,22 @@ Usage: Exit status is zero when every figure agrees within tolerance and one otherwise. + +用链路方程交叉核对 ApsisRS 的链路预算。 + +读取由 ``cargo run -p apsis-rs --features rf --example dump_link`` 写出的 +场景,并用纯 Python 从链路方程逐个重算每个数字,然后报告每列的最大差异。 + +此处的参考是链路方程本身而非某个库,因为该方程是闭式的且短到可以无歧义地 +抄录。各常量取自 2019 年 SI 定义,故与 Rust 侧所用的值一致。 + +本脚本不需要网络访问。 + +用法: + + pixi run python validation/python/link/check_link.py [path] + +每个数字都在容差内一致时退出状态为零,否则为一。 """ from __future__ import annotations @@ -27,25 +43,33 @@ import sys from pathlib import Path # Defining constants, exact in the SI. +# 定义常量,在 SI 中精确。 BOLTZMANN = 1.380649e-23 SPEED_OF_LIGHT = 299792458.0 # Tolerance in decibels. The two sides evaluate the same expressions with # double precision, so the only difference should be rounding. +# 容差,单位为分贝。两侧以双精度求相同表达式,故唯一差异应是舍入。 TOLERANCE_DB = 1e-9 DEFAULT_INPUT = Path("validation/data/expected/rust_link.csv") def read_rows(path: Path) -> list[dict[str, str]]: - """Reads the CSV rows.""" + """Reads the CSV rows. + + 读取 CSV 各行。 + """ with path.open(newline="") as handle: reader = csv.DictReader(handle) return [row for row in reader if row.get("transmit_dbw")] def reference(row: dict[str, str]) -> dict[str, float]: - """Recomputes a budget from the link equation.""" + """Recomputes a budget from the link equation. + + 由链路方程重算一份预算。 + """ transmit_dbw = float(row["transmit_dbw"]) transmit_dbi = float(row["transmit_dbi"]) transmit_loss = float(row["transmit_loss_db"]) @@ -65,6 +89,7 @@ def reference(row: dict[str, str]) -> dict[str, float]: cn_db = cn0_db - 10.0 * math.log10(bandwidth) # The example reports the energy per bit ratio at a data rate equal to # the bandwidth. + # 示例在数据速率等于带宽时报告每比特能量比。 ebn0_db = cn0_db - 10.0 * math.log10(bandwidth) return { diff --git a/validation/python/link/check_s465.py b/validation/python/link/check_s465.py index a8f8e37..9df5610 100644 --- a/validation/python/link/check_s465.py +++ b/validation/python/link/check_s465.py @@ -11,6 +11,19 @@ Usage: Exit status is zero when every value agrees within tolerance and one otherwise. + +交叉核对 ApsisRS 的 ITU-R S.465 参考方向图。 + +读取由 ``cargo run -p apsis-rs --features rf --example dump_s465`` 写出的 +值,并从该建议书的各式逐个重算。 + +本脚本不需要网络访问。 + +用法: + + pixi run python validation/python/link/check_s465.py [path] + +每个值都在容差内一致时退出状态为零,否则为一。 """ from __future__ import annotations @@ -21,24 +34,32 @@ import sys from pathlib import Path # The peak gain offset and the branch constants of the recommendation. +# 该建议书的峰值增益偏移与各分支常量。 PEAK_GAIN_OFFSET_DB = 7.7 LARGE_APERTURE_RATIO = 100.0 # Tolerance in decibels. +# 容差,单位为分贝。 TOLERANCE_DB = 1e-6 DEFAULT_INPUT = Path("validation/data/expected/rust_s465.csv") def read_rows(path: Path) -> list[dict[str, str]]: - """Reads the CSV rows.""" + """Reads the CSV rows. + + 读取 CSV 各行。 + """ with path.open(newline="") as handle: reader = csv.DictReader(handle) return [row for row in reader if row.get("ratio")] def break_angle(ratio: float) -> tuple[float, float]: - """Returns the branch constant and the break angle, in degrees.""" + """Returns the branch constant and the break angle, in degrees. + + 返回分支常量与转折角,单位为度。 + """ head, floor_angle = (29.0, 36.0) if ratio >= LARGE_APERTURE_RATIO else (32.0, 48.0) peak = 20.0 * math.log10(ratio) + PEAK_GAIN_OFFSET_DB @@ -66,6 +87,12 @@ def reference(ratio: float, angle_degrees: float) -> float: are converted to radians and back before the branch decision. That reproduces the rounding the implementation performs, which matters at the branch boundaries where the recommendation has a small step. + + 以分贝重算方向图增益。 + + 该 API 以弧度接收角度,故在分支判定之前,文件中以度记录的角度先转成 + 弧度再转回。这复现了实现所执行的舍入,在各分支边界处要紧,因为该建议书 + 在那里有小的台阶。 """ peak = 20.0 * math.log10(ratio) + PEAK_GAIN_OFFSET_DB head, break_at = break_angle(ratio)