diff --git a/scripts/__pycache__/bilingual.cpython-314.pyc b/scripts/__pycache__/bilingual.cpython-314.pyc index 31c25ce..ac05100 100644 Binary files a/scripts/__pycache__/bilingual.cpython-314.pyc and b/scripts/__pycache__/bilingual.cpython-314.pyc differ diff --git a/validation/python/atmosphere/check_gas.py b/validation/python/atmosphere/check_gas.py index 8a53b14..cecd4a0 100644 --- a/validation/python/atmosphere/check_gas.py +++ b/validation/python/atmosphere/check_gas.py @@ -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 diff --git a/validation/python/atmosphere/check_gas_exact.py b/validation/python/atmosphere/check_gas_exact.py index c7e0440..44e76dd 100644 --- a/validation/python/atmosphere/check_gas_exact.py +++ b/validation/python/atmosphere/check_gas_exact.py @@ -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")] diff --git a/validation/python/atmosphere/check_p839.py b/validation/python/atmosphere/check_p839.py index d1c2f5f..3b6b56b 100644 --- a/validation/python/atmosphere/check_p839.py +++ b/validation/python/atmosphere/check_p839.py @@ -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 diff --git a/validation/python/atmosphere/check_rain.py b/validation/python/atmosphere/check_rain.py index f554ce4..496037b 100644 --- a/validation/python/atmosphere/check_rain.py +++ b/validation/python/atmosphere/check_rain.py @@ -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")] diff --git a/validation/python/atmosphere/check_rain_path.py b/validation/python/atmosphere/check_rain_path.py index 30954e5..248ba6b 100644 --- a/validation/python/atmosphere/check_rain_path.py +++ b/validation/python/atmosphere/check_rain_path.py @@ -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 diff --git a/validation/python/coord/check_geodetic.py b/validation/python/coord/check_geodetic.py index 1064be0..a01f044 100644 --- a/validation/python/coord/check_geodetic.py +++ b/validation/python/coord/check_geodetic.py @@ -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, diff --git a/validation/python/coord/check_teme.py b/validation/python/coord/check_teme.py index c202400..36d87c6 100644 --- a/validation/python/coord/check_teme.py +++ b/validation/python/coord/check_teme.py @@ -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],