docs: bilingual comments for the coverage and link validation scripts

This commit is contained in:
ApsisRS contributors 2026-10-02 01:41:57 +08:00
parent 3253397501
commit 8e2a956fa5
3 changed files with 114 additions and 9 deletions

View File

@ -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)

View File

@ -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 {

View File

@ -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)