docs: bilingual comments for the coverage and link validation scripts
This commit is contained in:
parent
3253397501
commit
8e2a956fa5
|
|
@ -27,6 +27,31 @@ Usage:
|
||||||
|
|
||||||
Exit status is zero when every quantity agrees within tolerance and one
|
Exit status is zero when every quantity agrees within tolerance and one
|
||||||
otherwise.
|
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
|
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 agreement is at the floating point level because both sides evaluate
|
||||||
# the same closed form expressions. The tolerances are set to catch a
|
# the same closed form expressions. The tolerances are set to catch a
|
||||||
# transposed radius or a missing conversion, not to allow a loose match.
|
# transposed radius or a missing conversion, not to allow a loose match.
|
||||||
|
# 相符程度在浮点水平,因为两侧求的是相同的闭式表达式。这些容差是为捕获
|
||||||
|
# 半径写反或漏换算而设,并非为允许宽松匹配。
|
||||||
ANGLE_TOLERANCE_RAD = 1e-12
|
ANGLE_TOLERANCE_RAD = 1e-12
|
||||||
LENGTH_TOLERANCE_KM = 1e-9
|
LENGTH_TOLERANCE_KM = 1e-9
|
||||||
AREA_RELATIVE_TOLERANCE = 1e-12
|
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:
|
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)
|
return math.asin(body_radius * math.cos(elevation) / satellite_radius)
|
||||||
|
|
||||||
|
|
||||||
def reference_central(satellite_radius: float, body_radius: float, elevation: float) -> float:
|
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)
|
nadir = reference_nadir(satellite_radius, body_radius, elevation)
|
||||||
return max(0.0, math.pi / 2.0 - elevation - nadir)
|
return max(0.0, math.pi / 2.0 - elevation - nadir)
|
||||||
|
|
||||||
|
|
||||||
def reference_range(satellite_radius: float, body_radius: float, elevation: float) -> float:
|
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)
|
central = reference_central(satellite_radius, body_radius, elevation)
|
||||||
squared = (
|
squared = (
|
||||||
satellite_radius**2
|
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
|
``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
|
general, but a spherical cap is rotationally symmetric, so the same
|
||||||
expression applies.
|
expression applies.
|
||||||
|
|
||||||
|
返回给定中心角的球冠面积。
|
||||||
|
|
||||||
|
球冠是极点与某余纬之间的带,故其面积为 ``2 pi R^2 (1 - cos(central))``。
|
||||||
|
覆盖区通常并不以极点为中心,但球冠具有旋转对称性,故同一表达式适用。
|
||||||
"""
|
"""
|
||||||
return 2.0 * math.pi * body_radius**2 * (1.0 - math.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
|
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.
|
separates it from the inverse relation block, which has its own header.
|
||||||
|
|
||||||
|
读取该文件的两个块。
|
||||||
|
|
||||||
|
第一块是带表头的用例表;一个空行把它与反解关系块分开,后者有自己的
|
||||||
|
表头。
|
||||||
"""
|
"""
|
||||||
text = path.read_text().splitlines()
|
text = path.read_text().splitlines()
|
||||||
blocks: list[list[str]] = [[]]
|
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]]:
|
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] = []
|
failures: list[str] = []
|
||||||
compared = 0
|
compared = 0
|
||||||
worst = {
|
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]]:
|
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] = []
|
failures: list[str] = []
|
||||||
compared = 0
|
compared = 0
|
||||||
worst = 0.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
|
# Recompute the round trip independently: from the elevation, the
|
||||||
# central angle follows from the angle sum.
|
# central angle follows from the angle sum.
|
||||||
|
# 独立重算往返:由仰角经角和得出中心角。
|
||||||
satellite_radius = 6_378.137 + 550.0
|
satellite_radius = 6_378.137 + 550.0
|
||||||
body_radius = 6_378.137
|
body_radius = 6_378.137
|
||||||
nadir = reference_nadir(satellite_radius, body_radius, elevation)
|
nadir = reference_nadir(satellite_radius, body_radius, elevation)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,22 @@ Usage:
|
||||||
|
|
||||||
Exit status is zero when every figure agrees within tolerance and one
|
Exit status is zero when every figure agrees within tolerance and one
|
||||||
otherwise.
|
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
|
from __future__ import annotations
|
||||||
|
|
@ -27,25 +43,33 @@ import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Defining constants, exact in the SI.
|
# Defining constants, exact in the SI.
|
||||||
|
# 定义常量,在 SI 中精确。
|
||||||
BOLTZMANN = 1.380649e-23
|
BOLTZMANN = 1.380649e-23
|
||||||
SPEED_OF_LIGHT = 299792458.0
|
SPEED_OF_LIGHT = 299792458.0
|
||||||
|
|
||||||
# Tolerance in decibels. The two sides evaluate the same expressions with
|
# Tolerance in decibels. The two sides evaluate the same expressions with
|
||||||
# double precision, so the only difference should be rounding.
|
# double precision, so the only difference should be rounding.
|
||||||
|
# 容差,单位为分贝。两侧以双精度求相同表达式,故唯一差异应是舍入。
|
||||||
TOLERANCE_DB = 1e-9
|
TOLERANCE_DB = 1e-9
|
||||||
|
|
||||||
DEFAULT_INPUT = Path("validation/data/expected/rust_link.csv")
|
DEFAULT_INPUT = Path("validation/data/expected/rust_link.csv")
|
||||||
|
|
||||||
|
|
||||||
def read_rows(path: Path) -> list[dict[str, str]]:
|
def read_rows(path: Path) -> list[dict[str, str]]:
|
||||||
"""Reads the CSV rows."""
|
"""Reads the CSV rows.
|
||||||
|
|
||||||
|
读取 CSV 各行。
|
||||||
|
"""
|
||||||
with path.open(newline="") as handle:
|
with path.open(newline="") as handle:
|
||||||
reader = csv.DictReader(handle)
|
reader = csv.DictReader(handle)
|
||||||
return [row for row in reader if row.get("transmit_dbw")]
|
return [row for row in reader if row.get("transmit_dbw")]
|
||||||
|
|
||||||
|
|
||||||
def reference(row: dict[str, str]) -> dict[str, float]:
|
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_dbw = float(row["transmit_dbw"])
|
||||||
transmit_dbi = float(row["transmit_dbi"])
|
transmit_dbi = float(row["transmit_dbi"])
|
||||||
transmit_loss = float(row["transmit_loss_db"])
|
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)
|
cn_db = cn0_db - 10.0 * math.log10(bandwidth)
|
||||||
# The example reports the energy per bit ratio at a data rate equal to
|
# The example reports the energy per bit ratio at a data rate equal to
|
||||||
# the bandwidth.
|
# the bandwidth.
|
||||||
|
# 示例在数据速率等于带宽时报告每比特能量比。
|
||||||
ebn0_db = cn0_db - 10.0 * math.log10(bandwidth)
|
ebn0_db = cn0_db - 10.0 * math.log10(bandwidth)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ Usage:
|
||||||
|
|
||||||
Exit status is zero when every value agrees within tolerance and one
|
Exit status is zero when every value agrees within tolerance and one
|
||||||
otherwise.
|
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
|
from __future__ import annotations
|
||||||
|
|
@ -21,24 +34,32 @@ import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# The peak gain offset and the branch constants of the recommendation.
|
# The peak gain offset and the branch constants of the recommendation.
|
||||||
|
# 该建议书的峰值增益偏移与各分支常量。
|
||||||
PEAK_GAIN_OFFSET_DB = 7.7
|
PEAK_GAIN_OFFSET_DB = 7.7
|
||||||
LARGE_APERTURE_RATIO = 100.0
|
LARGE_APERTURE_RATIO = 100.0
|
||||||
|
|
||||||
# Tolerance in decibels.
|
# Tolerance in decibels.
|
||||||
|
# 容差,单位为分贝。
|
||||||
TOLERANCE_DB = 1e-6
|
TOLERANCE_DB = 1e-6
|
||||||
|
|
||||||
DEFAULT_INPUT = Path("validation/data/expected/rust_s465.csv")
|
DEFAULT_INPUT = Path("validation/data/expected/rust_s465.csv")
|
||||||
|
|
||||||
|
|
||||||
def read_rows(path: Path) -> list[dict[str, str]]:
|
def read_rows(path: Path) -> list[dict[str, str]]:
|
||||||
"""Reads the CSV rows."""
|
"""Reads the CSV rows.
|
||||||
|
|
||||||
|
读取 CSV 各行。
|
||||||
|
"""
|
||||||
with path.open(newline="") as handle:
|
with path.open(newline="") as handle:
|
||||||
reader = csv.DictReader(handle)
|
reader = csv.DictReader(handle)
|
||||||
return [row for row in reader if row.get("ratio")]
|
return [row for row in reader if row.get("ratio")]
|
||||||
|
|
||||||
|
|
||||||
def break_angle(ratio: float) -> tuple[float, float]:
|
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)
|
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
|
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
|
are converted to radians and back before the branch decision. That
|
||||||
reproduces the rounding the implementation performs, which matters at
|
reproduces the rounding the implementation performs, which matters at
|
||||||
the branch boundaries where the recommendation has a small step.
|
the branch boundaries where the recommendation has a small step.
|
||||||
|
|
||||||
|
以分贝重算方向图增益。
|
||||||
|
|
||||||
|
该 API 以弧度接收角度,故在分支判定之前,文件中以度记录的角度先转成
|
||||||
|
弧度再转回。这复现了实现所执行的舍入,在各分支边界处要紧,因为该建议书
|
||||||
|
在那里有小的台阶。
|
||||||
"""
|
"""
|
||||||
peak = 20.0 * math.log10(ratio) + PEAK_GAIN_OFFSET_DB
|
peak = 20.0 * math.log10(ratio) + PEAK_GAIN_OFFSET_DB
|
||||||
head, break_at = break_angle(ratio)
|
head, break_at = break_angle(ratio)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue