docs: bilingual comments for the atmosphere and coordinate validation scripts

This commit is contained in:
ApsisRS contributors 2026-10-02 01:41:29 +08:00
parent fd829e797f
commit 3253397501
8 changed files with 180 additions and 10 deletions

View File

@ -17,6 +17,23 @@ Usage:
Exit status is zero when every value agrees within tolerance and one
otherwise.
用 ITU-R P.676-12 交叉核对 ApsisRS 的气体衰减。
读取由 ``cargo run -p apsis-rs --features rf --example dump_gas`` 写出的
值,并用 ``itur`` 包逐个重算,后者直接实现 ITU-R 各建议书,然后报告每列
的最大差异。
``itur`` 包是作为 ITU-R 研究计划一部分维护的建议书参考实现,故它是对线表
与各公式的独立核对,而非对它们的复述。
本脚本不需要网络访问。
用法:
pixi run python validation/python/atmosphere/check_gas.py [path]
每个值都在容差内一致时退出状态为零,否则为一。
"""
from __future__ import annotations
@ -31,6 +48,7 @@ import astropy.units as u
from itur.models.itu676 import _ITU676_12_
# The reference atmosphere of the recommendation, matching the Rust side.
# 该建议书的参考大气,与 Rust 侧一致。
PRESSURE_HPA = 1013.25
WATER_VAPOUR_DENSITY = 7.5
TEMPERATURE_K = 288.15
@ -42,6 +60,11 @@ TEMPERATURE_K = 288.15
# contributions, so a pure relative test is too strict there. The check
# passes when either the relative difference is small or the absolute
# difference is negligible.
#
# 容差。
#
# 比衰减跨越数个数量级,且低频处的水汽项是较大贡献之间的小差值,故纯粹
# 的相对判据在那里过严。当相对差异很小或绝对差异可忽略时,检查即通过。
RELATIVE_TOLERANCE = 1e-8
ABSOLUTE_TOLERANCE = 1e-10
@ -49,7 +72,10 @@ DEFAULT_INPUT = Path("validation/data/expected/rust_gas.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("frequency_ghz")]
@ -103,6 +129,8 @@ def main(argv: list[str]) -> int:
# The column passes on either measure, so the reported figure is
# the relative one and the acceptance test below also allows the
# absolute difference to be negligible.
# 该列以两种度量之一通过即可,故所报告的数字是相对值,而下面的
# 接受判据也允许绝对差异可忽略。
if relative > worst[column]:
worst[column] = relative
worst_at[column] = frequency

View File

@ -14,6 +14,21 @@ Usage:
Exit status is zero when every value agrees within tolerance and one
otherwise.
用 ITU-R P.676-12 交叉核对 ApsisRS 的精确斜路径。
读取由 ``cargo run -p apsis-rs --features rf --example dump_gas_exact``
写出的值,并用 ``itur`` 包逐个重算,后者直接实现该建议书的精确方法。精确
方法在分层标准大气中积分比衰减并追踪折射后的射线,故它是低仰角下宜用的
形式。
本脚本不需要网络访问。
用法:
pixi run python validation/python/atmosphere/check_gas_exact.py [path]
每个值都在容差内一致时退出状态为零,否则为一。
"""
from __future__ import annotations
@ -25,19 +40,24 @@ from pathlib import Path
from itur.models.itu676 import _ITU676_12_
# The reference atmosphere, matching the Rust side.
# 参考大气,与 Rust 侧一致。
PRESSURE_HPA = 1013.25
WATER_VAPOUR_DENSITY = 7.5
TEMPERATURE_K = 288.15
# The implementation reproduces the reference to about nine significant
# digits, so this allows for the rounding of the recorded values.
# 该实现把参考值复现到约九位有效数字,故此值容许所记录值的舍入。
RELATIVE_TOLERANCE = 1e-7
DEFAULT_INPUT = Path("validation/data/expected/rust_gas_exact.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("frequency_ghz")]

View File

@ -18,6 +18,23 @@ Usage:
Exit status is zero when every compared value agrees within tolerance and one
otherwise.
用 ITU-R P.839-4 交叉核对 ApsisRS 的雨顶高度。
读取由 ``cargo run -p apsis-rs --features rf --example dump_p839`` 写出的
值,并用 ``itur`` 包逐个重算,后者直接实现该建议书。
参考实现在其双线性插值器所覆盖区域之外返回非数(NaN),这发生在两极附近
与接缝处。那些点被分开报告而不计为失败,因为所列网格确实覆盖它们,而
Rust 模型是由该网格插值的。
本脚本不需要网络访问。
用法:
pixi run python validation/python/atmosphere/check_p839.py [path]
每个被比较的值都在容差内一致时退出状态为零,否则为一。
"""
from __future__ import annotations
@ -33,13 +50,17 @@ from itur.models.itu839 import _ITU839_4_
# The table is stored in metres, so the interpolation reproduces the
# tabulated values to that resolution.
# 该表以米存储,故插值把所列值复现到该分辨率。
TOLERANCE_KM = 1e-9
DEFAULT_INPUT = Path("validation/data/expected/rust_p839.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("latitude_deg")]
@ -79,6 +100,7 @@ def main(argv: list[str]) -> int:
)
if math.isnan(reference):
# The reference interpolator does not cover this point.
# 参考插值器不覆盖该点。
outside += 1
continue

View File

@ -12,6 +12,19 @@ Usage:
Exit status is zero when every value agrees within tolerance and one
otherwise.
用 ITU-R P.838-3 交叉核对 ApsisRS 的降雨系数。
读取由 ``cargo run -p apsis-rs --features rf --example dump_rain`` 写出的
值,并用 ``itur`` 包逐个重算,后者直接实现该建议书。
本脚本不需要网络访问。
用法:
pixi run python validation/python/atmosphere/check_rain.py [path]
每个值都在容差内一致时退出状态为零,否则为一。
"""
from __future__ import annotations
@ -26,13 +39,18 @@ from itur.models.itu838 import _ITU838_3_
# digits. The Rust output is written with twelve decimal places, which limits
# the comparison for the smallest coefficients, so the tolerance is set to
# one part in ten to the eighth.
# 该实现把参考值复现到约十二位有效数字。Rust 输出以十二位小数写出,这限制
# 了最小系数的比较,故容差设为十的八次方分之一。
RELATIVE_TOLERANCE = 1e-8
DEFAULT_INPUT = Path("validation/data/expected/rust_rain.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("frequency_ghz")]

View File

@ -14,6 +14,20 @@ Usage:
Exit status is zero when every value agrees within tolerance and one
otherwise.
用 ITU-R P.618-13 交叉核对 ApsisRS 的降雨路径。
读取由 ``cargo run -p apsis-rs --features rf --example dump_rain_path``
写出的值,并从该建议书的各式逐个重算。比衰减系数来自 ITU-R P.838-3 的
``itur`` 实现,路径几何与两个缩减因子则抄自建议书 P.618-13。
本脚本不需要网络访问。
用法:
pixi run python validation/python/atmosphere/check_rain_path.py [path]
每个值都在容差内一致时退出状态为零,否则为一。
"""
from __future__ import annotations
@ -26,28 +40,37 @@ from pathlib import Path
from itur.models.itu838 import _ITU838_3_
# Tolerance on the path length and the attenuation, in their own units.
# 路径长度与衰减的容差,各以其自身单位计。
LENGTH_TOLERANCE_KM = 1e-9
ATTENUATION_TOLERANCE_DB = 1e-9
# The effective radius of the Earth used by the path geometry, in
# kilometers, from Recommendation P.618-13.
# 路径几何所用的地球有效半径,单位为千米,取自建议书 P.618-13。
EFFECTIVE_EARTH_RADIUS_KM = 8500.0
# The rain layer height used by the Rust example, in kilometers.
# Rust 示例所用的雨层高度,单位为千米。
RAIN_HEIGHT_KM = 3.0
DEFAULT_INPUT = Path("validation/data/expected/rust_rain_path.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("rain_rate_mm_per_h")]
def reference(row: dict[str, str], coefficients) -> tuple[float, float]:
"""Recomputes the path length and the attenuation."""
"""Recomputes the path length and the attenuation.
重算路径长度与衰减。
"""
rain_rate = float(row["rain_rate_mm_per_h"])
frequency = float(row["frequency_ghz"])
elevation = float(row["elevation_deg"])
@ -60,6 +83,7 @@ def reference(row: dict[str, str], coefficients) -> tuple[float, float]:
specific = float(k) * rain_rate ** float(alpha)
# Equation 2: the slant path length.
# 式 2:斜路径长度。
height_difference = RAIN_HEIGHT_KM - station_height
sine = math.sin(math.radians(elevation))
slant_length = 2.0 * height_difference / (
@ -68,9 +92,11 @@ def reference(row: dict[str, str], coefficients) -> tuple[float, float]:
)
# Equation 3: the horizontal projection.
# 式 3:水平投影。
horizontal_length = abs(slant_length * math.cos(math.radians(elevation)))
# Equation 6: the horizontal reduction factor.
# 式 6:水平缩减因子。
horizontal_reduction = 1.0 / (
1.0
+ 0.78 * math.sqrt(horizontal_length * specific / frequency)
@ -78,11 +104,13 @@ def reference(row: dict[str, str], coefficients) -> tuple[float, float]:
)
# Equation 7: the angle at which the path meets the rain layer.
# 式 7:路径与雨层相交的角度。
eta = math.degrees(
math.atan2(height_difference, horizontal_length * horizontal_reduction)
)
# Equation 8: the longer of the two path estimates.
# 式 8:两个路径估计中较长者。
if eta > elevation:
path_length = (
horizontal_length * horizontal_reduction / math.cos(math.radians(elevation))
@ -92,6 +120,7 @@ def reference(row: dict[str, str], coefficients) -> tuple[float, float]:
# Equation 9: the vertical adjustment factor. The exponential takes the
# elevation in degrees.
# 式 9:垂直调整因子。指数以度接收仰角。
xi = 36.0 - abs(latitude) if abs(latitude) < 36.0 else 0.0
vertical_adjustment = 1.0 / (
1.0

View File

@ -14,6 +14,20 @@ Usage:
Exit status is zero when every position agrees within the tolerance and one
otherwise.
交叉核对 ApsisRS 的大地坐标到地固坐标转换。
读取由 ``cargo run -p apsis-rs --features physics --example dump_geodesy``
写出的网格,并用 astropy 逐个重算位置,然后报告最大差异。
本脚本不需要网络访问。它从命令行给出的文件路径读取输入,缺省为标准位置,
并把报告写到标准输出。
用法:
pixi run python validation/python/coord/check_geodetic.py [path]
每个位置都在容差内一致时退出状态为零,否则为一。
"""
from __future__ import annotations
@ -27,13 +41,17 @@ from astropy.coordinates import EarthLocation
# Position tolerance in meters. Astropy evaluates the same closed form, so
# the only difference should be floating point rounding.
# 位置容差,单位为米。Astropy 求的是同一个闭式,故唯一差异应是浮点舍入。
TOLERANCE_M = 1e-6
DEFAULT_INPUT = Path("validation/data/expected/rust_geodetic.csv")
def rust_positions(path: Path) -> list[tuple[float, float, float, tuple[float, float, float]]]:
"""Reads the Rust output rows."""
"""Reads the Rust output rows.
读取 Rust 输出各行。
"""
rows = []
with path.open(newline="") as handle:
reader = csv.DictReader(handle)
@ -50,7 +68,10 @@ def rust_positions(path: Path) -> list[tuple[float, float, float, tuple[float, f
def astropy_position(latitude_deg: float, longitude_deg: float, height_m: float):
"""Recomputes a position with astropy."""
"""Recomputes a position with astropy.
用 astropy 重算一个位置。
"""
location = EarthLocation.from_geodetic(
lon=longitude_deg * u.deg,
lat=latitude_deg * u.deg,

View File

@ -20,6 +20,25 @@ Usage:
Exit status is zero when every position agrees within tolerance and one
otherwise.
交叉核对 ApsisRS 的 TEME 到地固坐标变换。
读取由 ``cargo run -p apsis-rs --features physics --example dump_teme``
写出的位置,并用 ERFA 逐个重算,施加相同的格林尼治平恒星时与相同的零极移,
使该检查比较的是实现而非输入的选择。
参考值是 1982 年的恒星时与 IAU SOFA 库的极移合成,由 ERFA 实现。注意
astropy 与该变换只相符到数百米,因为 astropy 施加实测极移与实测
UT1 - UTC,而本检查对两者都用零。该相符情况为提供信息而报告,但并非接受
判据。
本脚本不需要网络访问。
用法:
pixi run python validation/python/coord/check_teme.py [path]
每个位置都在容差内一致时退出状态为零,否则为一。
"""
from __future__ import annotations
@ -33,33 +52,45 @@ import erfa
# Tolerance in kilometers. The transformation is a product of two rotations
# evaluated in the same order, so the agreement is at the level of the last
# bits of a double.
# 容差,单位为千米。该变换是两个按相同次序求值的转动之积,故相符程度在
# 双精度最后几位的水平上。
TOLERANCE_KM = 1e-9
# The two-part Julian date used by the Rust example for the whole days.
# Rust 示例为整数天数所用的两部分儒略日。
DAY_PART = 2400000.5
DEFAULT_INPUT = Path("validation/data/expected/rust_teme.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("mjd")]
def reference_position(mjd: float, position: list[float]) -> list[float]:
"""Recomputes the Earth-fixed position with ERFA."""
"""Recomputes the Earth-fixed position with ERFA.
用 ERFA 重算地固位置。
"""
# The sidereal time, from the two-part Julian date.
# 恒星时,由两部分儒略日得出。
sidereal = erfa.gmst82(DAY_PART, mjd)
# The rotation of the frame about its third axis, composed as the
# reference `rz` does, and the identity polar motion.
# 坐标系绕其第三轴的转动,按参考 `rz` 的方式合成,以及单位极移。
rotation = erfa.rz(sidereal, erfa.ir())
polar = erfa.pom00(0.0, 0.0, 0.0)
matrix = erfa.rxr(polar, rotation)
# The rotation maps the frame, so the position is the matrix applied to
# the components.
# 该转动映射坐标系,故位置是矩阵施加于各分量。
return [
sum(matrix[i][j] * position[j] for j in range(3)) for i in range(3)
]
@ -82,6 +113,7 @@ def main(argv: list[str]) -> int:
return 2
# The TEME inputs used by the example, in the same order.
# 示例所用的 TEME 输入,次序相同。
inputs = [
[7000.0, 1000.0, 2000.0],
[7000.0, -3000.0, 1000.0],