diff --git a/crates/foundation/apsis-core/tests/constant.rs b/crates/foundation/apsis-core/tests/constant.rs index 9f12951..94ed639 100644 --- a/crates/foundation/apsis-core/tests/constant.rs +++ b/crates/foundation/apsis-core/tests/constant.rs @@ -1,4 +1,6 @@ //! Tests for the physical and astronomical constants. +//! +//! 物理与天文常量的测试。 use apsis_core::constant::*; @@ -23,6 +25,7 @@ fn time_constants_are_consistent() { #[test] fn j2000_minus_unix_epoch_is_the_expected_span() { // The two epochs are 10 957 days apart. + // 这两个历元相隔 10 957 天。 assert_eq!(JULIAN_DATE_J2000 - JULIAN_DATE_UNIX_EPOCH, 10_957.5); assert_eq!(SECONDS_J2000_TO_UNIX, 10_957.5 * DAY); assert_eq!(SECONDS_J2000_TO_UNIX, 946_728_000.0); diff --git a/crates/foundation/apsis-core/tests/error.rs b/crates/foundation/apsis-core/tests/error.rs index a97463c..c3ca0c8 100644 --- a/crates/foundation/apsis-core/tests/error.rs +++ b/crates/foundation/apsis-core/tests/error.rs @@ -1,4 +1,6 @@ //! Tests for the workspace error type and result alias. +//! +//! 工作区错误类型与结果别名的测试。 use apsis_core::{Error, ErrorKind, Result}; use core::fmt; diff --git a/crates/foundation/apsis-core/tests/quantity.rs b/crates/foundation/apsis-core/tests/quantity.rs index 20e856a..24a8718 100644 --- a/crates/foundation/apsis-core/tests/quantity.rs +++ b/crates/foundation/apsis-core/tests/quantity.rs @@ -1,4 +1,6 @@ //! Tests for the unit and quantity marker traits. +//! +//! 单位与量标记 trait 的测试。 use apsis_core::{Quantity, Unit}; use core::fmt; diff --git a/crates/foundation/apsis-macros/tests/probe.rs b/crates/foundation/apsis-macros/tests/probe.rs index 867c2fe..438eb85 100644 --- a/crates/foundation/apsis-macros/tests/probe.rs +++ b/crates/foundation/apsis-macros/tests/probe.rs @@ -3,11 +3,18 @@ //! These tests record what `macro_rules!` can express for the workspace. They //! are kept as executable documentation of the decision to leave this crate //! empty for now: see the crate documentation. +//! +//! 内建宏设施探查。 +//! +//! 这些测试记录 `macro_rules!` 能为工作区表达什么。它们被保留为「目前让本 crate +//! 保持为空」这一决定的可执行文档:见 crate 文档。 #[test] fn builtin_macros_can_substitute_a_type_and_a_literal() { // A `macro_rules!` macro can paste a type and a literal together, which // is the only form the workspace needs. + // `macro_rules!` 宏可以把一个类型与一个字面量拼在一起,这是本工作区唯一需要的 + // 形式。 macro_rules! quantity { ($quantity:ty, $value:literal) => { <$quantity>::new($value) @@ -15,6 +22,7 @@ fn builtin_macros_can_substitute_a_type_and_a_literal() { } // `f64` has no `new`, so the probe uses a type that does. + // `f64` 没有 `new`,故本探查用一个有的类型。 struct Meters(f64); impl Meters { const fn new(value: f64) -> Self { diff --git a/crates/foundation/apsis-math/tests/mat3.rs b/crates/foundation/apsis-math/tests/mat3.rs index 381f67d..608584e 100644 --- a/crates/foundation/apsis-math/tests/mat3.rs +++ b/crates/foundation/apsis-math/tests/mat3.rs @@ -1,4 +1,6 @@ //! Tests for three by three matrices. +//! +//! 三阶矩阵的测试。 use apsis_math::{Mat3, Quat, Vec3}; use core::f64::consts::FRAC_PI_2; @@ -133,6 +135,7 @@ fn is_rotation_rejects_non_rotations() { assert!(!Mat3::ZERO.is_rotation(1e-9)); assert!(!Mat3::from_diagonal(Vec3::splat(2.0)).is_rotation(1e-9)); // A reflection has determinant minus one. + // 反射的行列式为负一。 assert!(!Mat3::from_diagonal(Vec3::new(1.0, 1.0, -1.0)).is_rotation(1e-9)); } diff --git a/crates/foundation/apsis-math/tests/parallel.rs b/crates/foundation/apsis-math/tests/parallel.rs index 929dd45..9253a82 100644 --- a/crates/foundation/apsis-math/tests/parallel.rs +++ b/crates/foundation/apsis-math/tests/parallel.rs @@ -6,6 +6,12 @@ //! sequential answer rather than against a constant, and forces both the //! single and multi thread paths by choosing lengths that fall on each side //! of the threshold. +//! +//! 作用域并行辅助函数的测试。 +//! +//! 核心性质是并行运行与串行运行产生相同的值,因为这些辅助函数的全部意义就是作为 +//! 它们所替代的顺序循环的替代品。故每个测试都与顺序答案比较而非与常量比较,并 +//! 通过选取落在阈值两侧的长度,强制走过单线程与多线程两条路径。 use std::sync::atomic::Ordering; @@ -20,6 +26,7 @@ fn the_thread_count_is_at_least_one() { fn small_work_uses_a_single_thread() { // Below the per thread threshold there is nothing to gain from starting // a thread, so the helper says so. + // 低于每线程阈值时,启动线程没有任何收益,故该辅助函数如此表示。 assert_eq!(threads_for(0), 1); assert_eq!(threads_for(1), 1); assert_eq!(threads_for(64), 1); @@ -31,6 +38,7 @@ fn large_work_uses_more_than_one_thread() { if count > 1 { assert!(threads_for(1_000_000) > 1); // Never more threads than there are items. + // 线程数绝不超过项数。 assert!(threads_for(10) <= 10); } } @@ -67,6 +75,7 @@ fn the_parallel_result_matches_the_sequential_one() { fn the_output_is_in_index_order() { // Threads finish out of order, so this checks that the helper restores // index order rather than completion order. + // 各线程完成次序不定,故这检查该辅助函数恢复的是下标次序而非完成次序。 let len = 200_000; let values = map_index(len, |index| index); for (position, value) in values.iter().enumerate() { @@ -79,6 +88,8 @@ fn a_closure_may_capture_shared_input() { // The shape every caller in the workspace uses: read a shared slice and // produce one value per element. `map_index` owns the output buffer, so // no shared mutable state is needed. + // 本工作区中每个调用方所用的形状:读取共享切片并为每个元素产生一个值。 + // `map_index` 拥有输出缓冲,故无需共享可变状态。 let input: Vec = (0..100_000).map(f64::from).collect(); let output = map_index(input.len(), |index| input[index] * input[index]); for index in [0, 1, 999, 50_000, 99_999] { @@ -90,6 +101,8 @@ fn a_closure_may_capture_shared_input() { fn a_closure_may_accumulate_atomically() { // Writing to a shared counter needs atomics; this checks the helper // tolerates that, since summing a large range is a common pattern. + // 写共享计数器需要原子操作;这检查该辅助函数容忍它,因为对大区间求和是常见 + // 模式。 use std::sync::atomic::AtomicU64; let total = AtomicU64::new(0); let len = 200_000_u64; @@ -105,6 +118,7 @@ fn an_empty_range_does_nothing() { let empty: Vec = map_index(0, |_| 1.0); assert!(empty.is_empty()); // The index form accepts an empty range without spawning anything. + // 索引形式接受空区间而不创建任何线程。 for_each_index(0, |_| unreachable!("an empty range runs no closure")); } @@ -112,6 +126,7 @@ fn an_empty_range_does_nothing() { fn a_range_that_does_not_divide_evenly_covers_everything() { // A length that is not a multiple of the thread count is where an // off by one in the chunking would drop the tail. + // 长度不是线程数的整数倍之处,正是分块中差一会丢掉尾部的地方。 use std::sync::atomic::AtomicUsize; for &len in &[1_001_usize, 4_097, 65_537, 100_003] { let marks = (0..len).map(|_| AtomicUsize::new(0)).collect::>(); @@ -129,6 +144,7 @@ fn a_range_that_does_not_divide_evenly_covers_everything() { fn values_of_a_non_copy_type_survive_the_collect() { // A string is not `Copy`, so this exercises the unwrap-and-move path that // the parallel branch uses. + // 字符串不是 `Copy`,故这检验并行分支所用的解包并移动路径。 let values = map_index(10_000, |index| format!("item-{index}")); assert_eq!(values[0], "item-0"); assert_eq!(values[9_999], "item-9999"); diff --git a/crates/foundation/apsis-math/tests/quat.rs b/crates/foundation/apsis-math/tests/quat.rs index 1bbf16d..6566f6d 100644 --- a/crates/foundation/apsis-math/tests/quat.rs +++ b/crates/foundation/apsis-math/tests/quat.rs @@ -1,4 +1,6 @@ //! Tests for unit quaternion rotations. +//! +//! 单位四元数转动的测试。 use apsis_math::{Mat3, Quat, Vec3}; use core::f64::consts::{FRAC_PI_2, PI}; @@ -95,6 +97,7 @@ fn multiplication_composes_right_to_left() { let second = Quat::from_rotation_x(FRAC_PI_2); let v = Vec3::new(1.0, 0.0, 0.0); // `second * first` applies `first` first, then `second`. + // `second * first` 先施加 `first`,再施加 `second`。 assert_close((second * first).rotate(v), second.rotate(first.rotate(v))); } @@ -134,6 +137,7 @@ fn from_mat3_recovers_the_original_rotation() { let q = Quat::from_axis_angle(Vec3::new(0.3, -0.4, 0.5), 1.1).expect("non-zero axis"); let recovered = Quat::from_mat3(Mat3::from_quat(q)).expect("rotation matrix is accepted"); // `q` and `-q` are the same rotation, so compare through their effect. + // `q` 与 `-q` 是同一转动,故通过其效果比较。 let v = Vec3::new(1.0, -2.0, 3.0); assert_close(recovered.rotate(v), q.rotate(v)); } @@ -142,6 +146,7 @@ fn from_mat3_recovers_the_original_rotation() { fn from_mat3_covers_every_branch() { // These angles exercise the trace branch and each of the three // largest-diagonal branches. + // 这些角检验迹分支以及三个最大对角分支中的每一个。 for (axis, angle) in [ (Vec3::new(1.0, 0.0, 0.0), 0.2), (Vec3::new(1.0, 0.0, 0.0), PI - 0.1), @@ -177,6 +182,7 @@ fn slerp_selects_the_short_arc_when_endpoints_are_negated() { let end = Quat::new(-1.0, 0.0, 0.0, 0.0); // The two quaternions describe the same rotation, so any // interpolation between them must stay at the identity. + // 这两个四元数描述同一转动,故它们之间的任何插值都必须保持为恒等。 let middle = start.slerp(end, 0.5); assert_close(middle.rotate(Vec3::X), Vec3::X); } diff --git a/crates/foundation/apsis-math/tests/vec3.rs b/crates/foundation/apsis-math/tests/vec3.rs index 34ec450..17e7f15 100644 --- a/crates/foundation/apsis-math/tests/vec3.rs +++ b/crates/foundation/apsis-math/tests/vec3.rs @@ -1,4 +1,6 @@ //! Tests for three component vectors. +//! +//! 三分量矢量的测试。 use apsis_math::Vec3; @@ -45,6 +47,7 @@ fn cross_product_follows_the_right_hand_rule() { assert_eq!(Vec3::Y.cross(Vec3::Z), Vec3::X); assert_eq!(Vec3::Z.cross(Vec3::X), Vec3::Y); // The cross product of parallel vectors vanishes. + // 平行矢量的叉积为零。 assert_eq!(Vec3::X.cross(Vec3::X), Vec3::ZERO); } diff --git a/crates/foundation/apsis-units/tests/angle.rs b/crates/foundation/apsis-units/tests/angle.rs index 8898255..3bafa43 100644 --- a/crates/foundation/apsis-units/tests/angle.rs +++ b/crates/foundation/apsis-units/tests/angle.rs @@ -1,4 +1,6 @@ //! Tests for plane angle quantities. +//! +//! 平面角量的测试。 use apsis_core::{Quantity, Unit}; use apsis_units::dimension::Angle; @@ -35,6 +37,7 @@ fn wrap_places_angles_in_the_canonical_range() { assert!(wrapped.value() < PI); // Wrapping shifts by whole turns, so the angle is preserved // modulo two pi. + // 环绕按整周平移,故该角在模 2 pi 的意义下保持。 let difference = angle.value() - wrapped.value(); assert!((difference / (2.0 * PI)).fract().abs() < 1e-12); } diff --git a/crates/foundation/apsis-units/tests/frequency.rs b/crates/foundation/apsis-units/tests/frequency.rs index 4094bf4..7f93869 100644 --- a/crates/foundation/apsis-units/tests/frequency.rs +++ b/crates/foundation/apsis-units/tests/frequency.rs @@ -1,4 +1,6 @@ //! Tests for frequency quantities. +//! +//! 频率量的测试。 use apsis_core::Unit; use apsis_units::Hertz; diff --git a/crates/foundation/apsis-units/tests/length.rs b/crates/foundation/apsis-units/tests/length.rs index 5e9c2c6..723699f 100644 --- a/crates/foundation/apsis-units/tests/length.rs +++ b/crates/foundation/apsis-units/tests/length.rs @@ -1,4 +1,6 @@ //! Tests for length quantities. +//! +//! 长度量的测试。 use apsis_core::{Quantity, Unit}; use apsis_units::dimension::Length; @@ -38,6 +40,7 @@ fn units_report_their_metadata() { assert_eq!(Kilometers::factor_to_base(), 1_000.0); // Both units measure the same dimension, which is what makes the // generic conversion above type check. + // 两种单位测量同一量纲,这正是使上面的泛型换算通过类型检查的原因。 fn assert_length>() {} assert_length::(); assert_length::(); diff --git a/crates/foundation/apsis-units/tests/temperature.rs b/crates/foundation/apsis-units/tests/temperature.rs index abcdbe6..c659d6c 100644 --- a/crates/foundation/apsis-units/tests/temperature.rs +++ b/crates/foundation/apsis-units/tests/temperature.rs @@ -1,4 +1,6 @@ //! Tests for thermodynamic temperature quantities. +//! +//! 热力学温度量的测试。 use apsis_core::Unit; use apsis_core::constant::ZERO_CELSIUS_IN_KELVIN; diff --git a/crates/foundation/apsis-units/tests/time.rs b/crates/foundation/apsis-units/tests/time.rs index 12b6d2b..f87abf7 100644 --- a/crates/foundation/apsis-units/tests/time.rs +++ b/crates/foundation/apsis-units/tests/time.rs @@ -1,4 +1,6 @@ //! Tests for the duration quantity. +//! +//! 时长量的测试。 use apsis_core::Unit; use apsis_units::Seconds; diff --git a/crates/physics/apsis-coord/tests/frames.rs b/crates/physics/apsis-coord/tests/frames.rs index a0537be..faf2d05 100644 --- a/crates/physics/apsis-coord/tests/frames.rs +++ b/crates/physics/apsis-coord/tests/frames.rs @@ -1,4 +1,6 @@ //! Tests for the frame carrying position types. +//! +//! 携带坐标架的位置类型的测试。 use apsis_coord::{EarthRotationAngle, Ecef, Eci, Geodetic, WGS84}; use apsis_math::Vec3; @@ -67,6 +69,7 @@ fn frame_conversion_preserves_length() { fn a_ground_point_rotates_with_the_earth() { // A point on the equator at zero longitude in the Earth-fixed frame is // rotated by the Earth Rotation Angle in the inertial frame. + // 地固系中赤道上零经度的一个点,在惯性系中按地球自转角被转动。 let angle = EarthRotationAngle::from_ut1_days(1.5); let ground = Ecef::new(Vec3::new(6_378_137.0, 0.0, 0.0)); let inertial = ground.to_eci(angle); @@ -83,6 +86,7 @@ fn a_ground_point_rotates_with_the_earth() { fn a_geodetic_station_round_trips_through_both_frames() { // A station is fixed to the ground, so converting it to the inertial // frame and back at the same angle returns the original position. + // 地面站固定于地面,故把它转入惯性系再以相同角度转回,得到原位置。 let station = Geodetic::new(0.662, 0.175, 80.0); let angle = EarthRotationAngle::from_ut1_julian_date(2_460_000.5); let inertial = station.to_ecef().to_eci(angle); diff --git a/crates/physics/apsis-frames/tests/earth_rotation.rs b/crates/physics/apsis-frames/tests/earth_rotation.rs index 75296d3..ddaa63d 100644 --- a/crates/physics/apsis-frames/tests/earth_rotation.rs +++ b/crates/physics/apsis-frames/tests/earth_rotation.rs @@ -1,4 +1,6 @@ //! Tests for the Earth Rotation Angle and the frame rotations. +//! +//! 地球自转角与坐标架转动的测试。 use apsis_frames::{ ERA_RATE_TURNS_PER_DAY, EarthRotationAngle, rotation_ecef_to_eci, rotation_eci_to_ecef, @@ -13,6 +15,7 @@ const EPS: f64 = 1e-12; fn rate_matches_the_published_value() { // 1.002 737 811 911 354 6 turns per day is 360.985 612 288 088 degrees // per day. + // 每天 1.002 737 811 911 354 6 周即每天 360.985 612 288 088 度。 assert!((ERA_RATE_TURNS_PER_DAY - 1.002_737_811_911_354_6).abs() < 1e-15); let degrees = ERA_RATE_TURNS_PER_DAY * 360.0; assert!( @@ -28,6 +31,7 @@ fn angle_advances_by_the_rate_each_day() { assert!((second.turns() - first.turns() - ERA_RATE_TURNS_PER_DAY).abs() < 1e-15); // The angle advances a little under one degree per day more than a // full turn. + // 该角每天推进一整圈再多略小于一度。 let advance_degrees = (second.turns() - first.turns()) * 360.0 - 360.0; assert!((advance_degrees - 0.985_612_288_088).abs() < 1e-9); } @@ -36,6 +40,7 @@ fn angle_advances_by_the_rate_each_day() { fn epoch_angle_matches_the_published_constant() { // At J2000.0 the angle is 0.779 057 273 2640 turns, about 280.46 // degrees. + // 在 J2000.0 处该角为 0.779 057 273 2640 周,约 280.46 度。 let epoch = EarthRotationAngle::from_ut1_days(0.0); assert!((epoch.turns() - 0.779_057_273_264).abs() < 1e-15); let degrees = epoch.turns_positive() * 360.0; @@ -45,6 +50,7 @@ fn epoch_angle_matches_the_published_constant() { #[test] fn julian_date_and_day_count_agree() { // J2000.0 is Julian Date 2451545.0. + // J2000.0 是儒略日 2451545.0。 assert_eq!(ut1_days_from_j2000(2_451_545.0), 0.0); assert_eq!(ut1_days_from_j2000(2_451_546.0), 1.0); let via_date = EarthRotationAngle::from_ut1_julian_date(2_451_546.0); @@ -68,6 +74,7 @@ fn positive_variants_reduce_to_the_principal_interval() { assert!((0.0..1.0).contains(&turns), "turns out of range: {turns}"); // Reduction removes whole turns, so the angle is unchanged modulo // one turn. + // 归约去掉整周,故该角在模一周的意义下不变。 let difference = angle.turns() - turns; assert!((difference - difference.round()).abs() < 1e-9); @@ -88,6 +95,8 @@ fn a_positive_angle_drifts_inertial_directions_westward() { // The Earth turns eastward, so a direction fixed in inertial space // drifts westward when read in the Earth-fixed frame. In the // Earth-fixed frame that is toward negative second axis. + // 地球向东转,故在惯性空间中固定的方向在地固系中读数时向西漂移。在地固系中 + // 即朝负第二轴方向。 let angle = EarthRotationAngle::from_radians(0.25); let rotation = rotation_eci_to_ecef(angle); let rotated = rotation * Vec3::X; @@ -103,6 +112,7 @@ fn opposite_rotations_cancel() { let identity = forward * backward; assert!(identity.is_rotation(1e-12)); // Applying one after the other returns the original vector. + // 先后施加二者返回原矢量。 let vector = Vec3::new(1.5, -2.5, 3.5); assert!((backward * (forward * vector) - vector).norm() < EPS); } @@ -118,6 +128,7 @@ fn earth_rotation_turns_the_third_axis_into_itself() { fn rotation_carries_the_second_axis_toward_the_first() { // The companion to the westward drift test: the second axis of the // inertial frame maps toward the first axis of the Earth-fixed frame. + // 向西漂移测试的对应项:惯性系的第二轴映射向地固系的第一轴。 let angle = EarthRotationAngle::from_radians(0.25); let rotation = rotation_eci_to_ecef(angle); let rotated = rotation * Vec3::Y; @@ -129,6 +140,7 @@ fn rotation_carries_the_second_axis_toward_the_first() { fn a_full_turn_returns_to_the_same_rotation() { // The rotation depends on the angle modulo one turn, so angles that // differ by whole turns give the same matrix. + // 该转动依模一周的角而定,故相差整周的角给出相同的矩阵。 let first = rotation_eci_to_ecef(EarthRotationAngle::from_turns(0.25)); let second = rotation_eci_to_ecef(EarthRotationAngle::from_turns(2.25)); let difference = first * second.transpose(); @@ -141,6 +153,7 @@ fn a_full_turn_returns_to_the_same_rotation() { fn identity_angle_leaves_inertial_vectors_unchanged() { // An angle of zero, constructed directly rather than from the epoch // constant, is the identity rotation. + // 直接构建的(而非由历元常量得来的)零角是恒等转动。 let rotation = rotation_eci_to_ecef(EarthRotationAngle::from_turns(0.0)); let vector = Vec3::new(1.0, 2.0, 3.0); assert!((rotation * vector - vector).norm() < EPS); diff --git a/crates/physics/apsis-frames/tests/iau2000.rs b/crates/physics/apsis-frames/tests/iau2000.rs index ab91299..1742bf6 100644 --- a/crates/physics/apsis-frames/tests/iau2000.rs +++ b/crates/physics/apsis-frames/tests/iau2000.rs @@ -4,6 +4,11 @@ //! derivative for the same instants. They are recorded here so that a change //! to a coefficient or a rotation convention shows up as a failing //! assertion. +//! +//! IAU 2000/2006 地球定向模型的测试。 +//! +//! 期望值是 IAU SOFA 库及其 ERFA 衍生物对相同瞬时的值。它们被记录在此,使对某个 +//! 系数或转动约定的改动表现为断言失败。 use apsis_frames::iau2000::{ PolarMotion, bias_precession_nutation_matrix, celestial_to_terrestrial_matrix, @@ -14,10 +19,14 @@ use apsis_frames::iau2000::{ use core::f64::consts::PI; /// Converts arcseconds to radians. +/// +/// 把角秒换算为弧度。 const ARCSECOND: f64 = PI / (180.0 * 3_600.0); /// Returns the Terrestrial Time Julian date for a Modified Julian Date on /// the UTC scale, using the offsets in force from 2017. +/// +/// 使用自 2017 年起生效的偏移,返回 UTC 时标上某约化儒略日对应的地球时儒略日。 fn tt_julian_date(utc_mjd: f64) -> f64 { utc_mjd + 2_400_000.5 + 69.184 / 86_400.0 } @@ -25,6 +34,7 @@ fn tt_julian_date(utc_mjd: f64) -> f64 { #[test] fn the_mean_obliquity_at_j2000_matches_the_reference() { // The IAU 2006 obliquity at J2000.0 is 84381.406 arcseconds. + // J2000.0 处的 IAU 2006 黄赤交角为 84381.406 角秒。 let obliquity = mean_obliquity(0.0); assert!( (obliquity / ARCSECOND - 84_381.406).abs() < 1e-9, @@ -36,6 +46,7 @@ fn the_mean_obliquity_at_j2000_matches_the_reference() { #[test] fn the_nutation_at_j2000_matches_the_reference() { // The IAU 2000A nutation at J2000.0, from ERFA. + // J2000.0 处的 IAU 2000A 章动,取自 ERFA。 let t = 0.0; let nutation = nutation_2000a(t); assert!( @@ -54,6 +65,7 @@ fn the_nutation_at_j2000_matches_the_reference() { fn the_nutation_2006_adjustment_is_a_scaling() { // The IAU 2006 adjustment scales the IAU 2000A nutation rather than // adding an independent term, so the two must stay in proportion. + // IAU 2006 调整是对 IAU 2000A 章动缩放而非加上独立项,故二者必须保持比例。 for t in [0.0, 0.5, 1.0, 2.0] { let base = nutation_2000a(t); let adjusted = nutation_2000a_with_2006_adjustment(t); @@ -75,6 +87,7 @@ fn the_nutation_2006_adjustment_is_a_scaling() { #[test] fn the_nutation_matches_the_reference_over_a_century() { // Recorded from ERFA at each date. The values are in radians. + // 在每个日期从 ERFA 记录。各值以弧度计。 let cases = [ (40_587.0, 2.1896610891495316e-05, 4.070477585626008e-05), (51_544.5, -6.754419567422628e-05, -2.797091091919891e-05), @@ -100,6 +113,7 @@ fn the_nutation_matches_the_reference_over_a_century() { #[test] fn the_precession_angles_at_j2000_match_the_reference() { // The P03 precession angles at J2000.0. + // J2000.0 处的 P03 岁差角。 let angles = precession_angles(0.0); assert!( (angles.gamma_bar - -0.052_928 * ARCSECOND).abs() < 1e-18, @@ -127,6 +141,8 @@ fn the_precession_angles_at_j2000_match_the_reference() { fn the_bias_precession_nutation_matrix_matches_the_reference() { // The GCRS-to-true-of-date matrix at J2000.0 on the Terrestrial Time // scale, from ERFA's `pnm06a`. The values are the first row. + // 地球时时标上 J2000.0 处的 GCRS 到该日真坐标矩阵,取自 ERFA 的 `pnm06a`。 + // 各值是第一行。 let tt = 51_544.5 + 2_400_000.5 + 69.184 / 86_400.0; let t = (tt - 2_451_545.0) / 36_525.0; let matrix = bias_precession_nutation_matrix(t); @@ -192,11 +208,13 @@ fn the_celestial_to_terrestrial_matrix_is_a_rotation() { fn the_celestial_to_terrestrial_matrix_matches_the_reference() { // The matrix at J2000.0 on the Terrestrial Time scale, from ERFA with // zero polar motion. Each entry is checked to a nanoradian. + // 地球时时标上 J2000.0 处的矩阵,取自极移为零的 ERFA。每个元素检查到纳弧度。 let tt = 51_544.5 + 2_400_000.5 + 69.184 / 86_400.0; let matrix = celestial_to_terrestrial_matrix(tt, tt, PolarMotion::NONE); // The first row, from ERFA's `c2t06a` composed with the equinox based // sidereal time. + // 第一行,取自 ERFA 的 `c2t06a` 与基于春分点的恒星时之合成。 let expected_first_row = [ 0.18651846184811946, -0.982451455739228, @@ -216,6 +234,7 @@ fn the_equation_of_the_equinoxes_includes_the_complementary_terms() { // The complementary terms are a few thousandths of an arcsecond and must // not be dropped: their omission would show as a millisecond of // sidereal time. + // 补充项是几千分之一角秒,不得省略:略去它们会表现为一毫秒的恒星时。 let t = 0.0; let complementary = equation_of_the_equinoxes_complementary_terms(t); assert!( @@ -224,6 +243,7 @@ fn the_equation_of_the_equinoxes_includes_the_complementary_terms() { ); // At J2000.0 the leading term is 2640.96 microarcseconds times the sine // of the Moon's ascending node. + // 在 J2000.0 处首项是 2640.96 微角秒乘以月球升交点角的正弦。 let expected = 1.021330096302465e-08; assert!( (complementary - expected).abs() < 1e-15, @@ -235,6 +255,7 @@ fn the_equation_of_the_equinoxes_includes_the_complementary_terms() { fn the_general_precession_is_quadratic_in_time() { // The argument is (0.024381750 + 0.00000538691 t) t, so at small times // it grows linearly with the leading coefficient. + // 该辐角为 (0.024381750 + 0.00000538691 t) t,故在小时间处它随首系数线性增长。 for t in [0.1, 0.5, 1.0] { let value = general_precession(t); let expected = (0.024_381_750 + 0.000_005_386_91 * t) * t; @@ -247,6 +268,8 @@ fn sidereal_time_advances_by_about_a_turn_per_day() { // Greenwich apparent sidereal time must increase by a full turn plus a // small amount each day. The small amount is about 3.94 minutes of // time, which is 0.9856 degrees. + // 格林尼治视恒星时每天必须增加一整圈再加上一小量。该小量约为 3.94 分钟时间, + // 即 0.9856 度。 let first = greenwich_apparent_sidereal_time(60_000.0, 60_000.0); let second = greenwich_apparent_sidereal_time(60_001.0, 60_001.0); let advance = (second - first).rem_euclid(2.0 * PI); @@ -274,10 +297,12 @@ fn zero_polar_motion_is_the_identity_rotation() { fn a_polar_motion_offsets_the_rotation() { // A polar motion of one arcsecond must tilt the matrix by about that // much, which is 4.85 microradians. + // 一角秒的极移必须使矩阵倾斜约那么多,即 4.85 微弧度。 let motion = PolarMotion::new(ARCSECOND, ARCSECOND, 0.0); let matrix = motion.matrix(); assert!(matrix.is_rotation(1e-12)); // The off-diagonal term is the sine of the displacement. + // 非对角项是该位移的正弦。 let value = matrix.get(0, 2).expect("the index is in range"); assert!( (value - ARCSECOND.sin()).abs() < 1e-15, @@ -291,6 +316,7 @@ fn a_polar_motion_offsets_the_rotation() { fn the_polar_motion_shifts_the_earth_fixed_frame() { // Applying a polar motion must change the terrestrial position of a // fixed inertial direction. + // 施加极移必须改变某固定惯性方向的地球位置。 let tt = 60_000.0; let without = celestial_to_terrestrial_matrix(tt, tt, PolarMotion::NONE); let with = celestial_to_terrestrial_matrix( @@ -301,6 +327,7 @@ fn the_polar_motion_shifts_the_earth_fixed_frame() { let difference = (without.transpose() * with).transpose() * with; // The two must differ, and the difference must be of the order of the // applied angles. + // 二者必须不同,且差值必须与所施加的角度同量级。 let mut largest = 0.0_f64; for row in 0..3 { for column in 0..3 { @@ -318,10 +345,13 @@ fn the_nutation_reaches_the_expected_magnitude() { // The nutation in longitude reaches about seventeen arcseconds and the // nutation in obliquity about nine, so over a range of dates the // largest value found must be in that range. + // 黄经章动达约十七角秒,黄赤交角章动约九角秒,故在一段日期上所能找到的最大值 + // 必须落在该范围内。 let mut largest_longitude = 0.0_f64; let mut largest_obliquity = 0.0_f64; for step in 0..400 { // A span of about fifty years in steps of a fortnight. + // 约五十年、步长为两周的跨度。 let t = f64::from(step) * 0.125; let nutation = nutation_2000a(t); largest_longitude = largest_longitude.max(nutation.longitude.abs()); @@ -344,6 +374,8 @@ fn the_nutation_is_periodic() { // The dominant terms have periods of about eighteen years and half a // year, so the value must return close to its initial value after // eighteen years and must oscillate in between. + // 主导项的周期约为十八年与半年,故该值必须在十八年后回到接近其初值,并在其间 + // 振荡。 let initial = nutation_2000a(0.0).longitude; let after_eighteen_years = nutation_2000a(0.18).longitude; assert!( @@ -353,6 +385,7 @@ fn the_nutation_is_periodic() { ); // The sign must change at some point within that span. + // 符号必须在该跨度内某处改变。 let mut seen_positive = false; let mut seen_negative = false; for step in 0..200 { @@ -368,6 +401,7 @@ fn the_nutation_is_periodic() { fn the_equation_of_the_equinoxes_follows_the_nutation() { // The equation of the equinoxes is dominated by the nutation in // longitude projected onto the true equator, so it shares its sign. + // 分点方程式主要由投影到真赤道上的黄经章动主导,故它与后者同号。 for t in [0.02, 0.05, 0.1, 0.2] { let nutation = nutation_2000a_with_2006_adjustment(t); let equation = equation_of_the_equinoxes(t); @@ -384,11 +418,13 @@ fn the_equation_of_the_equinoxes_follows_the_nutation() { fn a_matrix_at_two_epochs_separated_by_a_century_differs_by_the_precession() { // The precession over a century is about 1.4 degrees, so the two // matrices must differ by an angle of that order. + // 一世纪的岁差约为 1.4 度,故两个矩阵必须相差该量级的角。 let first = bias_precession_nutation_matrix(0.0); let second = bias_precession_nutation_matrix(1.0); let difference = first.transpose() * second; // The trace of a rotation matrix is 1 + 2 cos(angle), so the angle // follows from it. + // 转动矩阵的迹为 1 + 2 cos(angle),故该角由它得出。 let cosine = ((difference.trace() - 1.0) / 2.0).clamp(-1.0, 1.0); let degrees = cosine.acos().to_degrees(); assert!( @@ -401,6 +437,7 @@ fn a_matrix_at_two_epochs_separated_by_a_century_differs_by_the_precession() { fn the_1982_sidereal_time_matches_the_reference() { // Greenwich mean sidereal time from the 1982 expression, which is the // one the SGP4 theory was fitted against. The values are from ERFA. + // 1982 表达式给出的格林尼治平恒星时,即 SGP4 理论所拟合者。各值取自 ERFA。 let cases = [ (51_544.5, 4.894_961_212_823_059), (60_476.999_999_810_5, 4.620_858_198_832_686), @@ -419,6 +456,7 @@ fn the_1982_sidereal_time_matches_the_reference() { fn the_1982_sidereal_time_is_a_full_turn_per_day() { // Sidereal time advances by one turn plus the excess of the sidereal // day over the solar day, which is 0.9856 degrees per day. + // 恒星时推进一整圈再加上恒星日超出太阳日的部分,即每天 0.9856 度。 let first = apsis_frames::iau2000::greenwich_mean_sidereal_time_1982(2_400_000.5, 60_000.0); let second = apsis_frames::iau2000::greenwich_mean_sidereal_time_1982(2_400_000.5, 60_001.0); let advance = (second - first).rem_euclid(2.0 * PI); @@ -436,6 +474,7 @@ fn the_teme_transformation_to_earth_fixed_matches_the_reference() { // The reference values are ERFA's `gmst82` applied as a third axis // rotation with the identity polar motion. + // 参考值是把 ERFA 的 `gmst82` 作为第三轴转动、配以单位极移施加所得。 let cases = [ ( 60_481.5, @@ -479,6 +518,8 @@ fn the_teme_frame_uses_the_mean_equinox_not_the_true_one() { // pairs with a mean sidereal time. Substituting the apparent value, or // the modern mean value, rotates the result by a fraction of a degree, // which is many kilometres of ground position. + // 该区别要紧。TEME 的第一轴是平春分点,故它与平恒星时配对。换成视值或现代平值 + // 会把结果转动零点几度,即许多千米的地面位置。 use apsis_frames::iau2000::{ greenwich_apparent_sidereal_time, greenwich_mean_sidereal_time, greenwich_mean_sidereal_time_1982, @@ -492,6 +533,7 @@ fn the_teme_frame_uses_the_mean_equinox_not_the_true_one() { // The apparent value includes the equation of the equinoxes, which is a // few thousandths of a degree. + // 视值包含分点方程式,即千分之几度。 let equation = (apparent - modern_mean).abs().to_degrees(); assert!( equation > 1e-4 && equation < 1e-2, @@ -501,6 +543,8 @@ fn the_teme_frame_uses_the_mean_equinox_not_the_true_one() { // The 1982 expression, which TEME is fitted against, differs from the // modern mean value by a fraction of a degree, because the precession // model changed between them. + // TEME 所拟合的 1982 表达式与现代平值相差零点几度,因为二者之间的岁差模型 + // 改变了。 let secular = (sidereal_1982 - modern_mean).abs().to_degrees(); assert!( secular > 0.1 && secular < 1.0, diff --git a/crates/physics/apsis-geodesy/tests/ellipsoid.rs b/crates/physics/apsis-geodesy/tests/ellipsoid.rs index 278f79d..c0f698b 100644 --- a/crates/physics/apsis-geodesy/tests/ellipsoid.rs +++ b/crates/physics/apsis-geodesy/tests/ellipsoid.rs @@ -1,4 +1,6 @@ //! Tests for the reference ellipsoid. +//! +//! 参考椭球的测试。 use apsis_geodesy::{Ellipsoid, WGS84}; @@ -12,8 +14,10 @@ fn defining_parameters_match_the_standard() { #[test] fn derived_axes_match_the_published_values() { // The WGS 84 semi-minor axis is 6 356 752.314 245 m. + // WGS 84 的半短轴为 6 356 752.314 245 m。 assert!((WGS84.semi_minor_axis() - 6_356_752.314_245).abs() < 1e-6); // The first eccentricity is 0.081 819 190 842 622. + // 第一偏心率为 0.081 819 190 842 622。 assert!((WGS84.eccentricity() - 0.081_819_190_842_622).abs() < 1e-12); } @@ -30,15 +34,18 @@ fn second_eccentricity_relates_to_the_first() { let second = WGS84.second_eccentricity_squared(); assert!((second - first / (1.0 - first)).abs() < 1e-18); // Derived from the defining parameters this is 0.006 739 496 742 276. + // 由定义参数导出,该值为 0.006 739 496 742 276。 assert!((second - 0.006_739_496_742_276_435).abs() < 1e-15); } #[test] fn prime_vertical_radius_spans_the_expected_range() { // At the equator it equals the semi-major axis. + // 在赤道处它等于半长轴。 assert!((WGS84.prime_vertical_radius(0.0) - WGS84.semi_major_axis()).abs() < 1e-9); // At the pole it equals the polar radius of curvature, a / sqrt(1 - e^2), // which is 6 399 593.625 9 m. + // 在极点处它等于极曲率半径 a / sqrt(1 - e^2),即 6 399 593.625 9 m。 let pole = WGS84.prime_vertical_radius(core::f64::consts::FRAC_PI_2); assert!((pole - WGS84.polar_radius_of_curvature()).abs() < 1e-9); assert!((pole - 6_399_593.625_9).abs() < 1e-3); @@ -47,11 +54,13 @@ fn prime_vertical_radius_spans_the_expected_range() { #[test] fn meridian_radius_spans_the_expected_range() { // At the equator it is a (1 - e^2), which is 6 335 439.327 m. + // 在赤道处它是 a (1 - e^2),即 6 335 439.327 m。 let equator = WGS84.meridian_radius(0.0); let expected = WGS84.semi_major_axis() * (1.0 - WGS84.eccentricity_squared()); assert!((equator - expected).abs() < 1e-6); assert!((equator - 6_335_439.327_1).abs() < 1e-3); // At the pole it is a / sqrt(1 - e^2). + // 在极点处它是 a / sqrt(1 - e^2)。 let pole = WGS84.meridian_radius(core::f64::consts::FRAC_PI_2); assert!((pole - WGS84.polar_radius_of_curvature()).abs() < 1e-6); } @@ -69,6 +78,7 @@ fn geocentric_latitude_is_smaller_than_geodetic_away_from_the_axes() { let geocentric = WGS84.geocentric_latitude(geodetic); assert!(geocentric < geodetic); // The maximum separation is about 0.19 degrees at 45 degrees. + // 最大差约在 45 度处为 0.19 度。 let difference = (geodetic - geocentric).to_degrees(); assert!((difference - 0.1924).abs() < 0.001, "got {difference}"); } diff --git a/crates/physics/apsis-geodesy/tests/geodetic.rs b/crates/physics/apsis-geodesy/tests/geodetic.rs index b1dc986..6f7490f 100644 --- a/crates/physics/apsis-geodesy/tests/geodetic.rs +++ b/crates/physics/apsis-geodesy/tests/geodetic.rs @@ -1,15 +1,23 @@ //! Tests for geodetic and Cartesian conversions. +//! +//! 大地与直角坐标换算的测试。 use apsis_geodesy::{Geodetic, WGS84, geodetic_from_cartesian}; use apsis_math::Vec3; /// Tolerance for position comparisons, in meters. +/// +/// 位置比较的容差,单位为米。 const POSITION_TOL: f64 = 1e-4; /// Tolerance for latitude and longitude comparisons, in radians. /// /// One microdegree is about 1.1e-8 radians, or roughly a centimeter on the /// Earth's surface. +/// +/// 纬度与经度比较的容差,单位为弧度。 +/// +/// 一微度约为 1.1e-8 弧度,或地表上大致一厘米。 const ANGLE_TOL: f64 = 1e-11; #[test] @@ -42,6 +50,7 @@ fn height_moves_the_point_along_the_ellipsoid_normal() { assert!((surface.distance(elevated) - 500.0).abs() < POSITION_TOL); // The normal at a non-polar latitude is not vertical, so the height // changes the horizontal components as well as the third one. + // 非极地纬度处的法线并非竖直,故高度既改变第三分量也改变水平分量。 assert!(elevated.x > surface.x); } @@ -91,6 +100,7 @@ fn polar_axis_has_no_defined_longitude() { let on_axis = Vec3::new(0.0, 0.0, WGS84.semi_minor_axis()); assert_eq!(geodetic_from_cartesian(on_axis, WGS84), None); // The origin is also on the axis. + // 原点也在该轴上。 assert_eq!(geodetic_from_cartesian(Vec3::ZERO, WGS84), None); } @@ -107,6 +117,8 @@ fn known_point_matches_an_independent_computation() { // Latitude 37.5 degrees north, longitude 121.333333... degrees west, at // zero height, evaluated with the defining WGS 84 parameters in // extended precision. + // 北纬 37.5 度、西经 121.333333... 度、零高度,用 WGS 84 定义参数以扩展精度 + // 求值。 let point = Geodetic::new( 37.5_f64.to_radians(), -121.333_333_333_333_33_f64.to_radians(), diff --git a/crates/physics/apsis-leap/tests/table.rs b/crates/physics/apsis-leap/tests/table.rs index b21f2e7..d568877 100644 --- a/crates/physics/apsis-leap/tests/table.rs +++ b/crates/physics/apsis-leap/tests/table.rs @@ -1,4 +1,6 @@ //! Tests for the leap second table. +//! +//! 闰秒表的测试。 use apsis_leap::{ LEAP_SECONDS, TABLE_COVERS_FROM_MJD, TAI_MINUS_UTC_AFTER_LAST_ENTRY, mjd_from_ymd, @@ -23,6 +25,7 @@ fn table_is_ordered_and_monotonic() { fn every_entry_increases_the_offset_by_one_second() { // The table starts at the inherited offset of ten seconds and every // later announcement inserted exactly one second. + // 该表始于继承而来的十秒偏移,其后每次宣布都恰好插入一秒。 assert_eq!(LEAP_SECONDS[0].tai_minus_utc, 10.0); for pair in LEAP_SECONDS.windows(2) { let (earlier, later) = (pair[0], pair[1]); @@ -48,6 +51,7 @@ fn table_extremes_match_the_announced_values() { fn table_contains_every_announcement_since_1972() { // Twenty seven leap seconds were introduced between 1972 and 2017 // inclusive of the initial 1972-01-01 entry. + // 1972 年到 2017 年间引入了二十七个闰秒,含 1972-01-01 的初项。 assert_eq!(LEAP_SECONDS.len(), 28); } @@ -60,12 +64,16 @@ fn coverage_constants_match_the_table_boundaries() { #[test] fn known_calendar_dates_map_to_known_modified_julian_dates() { // MJD 0 is the start of 1858-11-17. + // MJD 0 是 1858-11-17 的起点。 assert_eq!(mjd_from_ymd(1858, 11, 17), 0.0); // The calendar day of J2000.0 is MJD 51544. + // J2000.0 的日历日是 MJD 51544。 assert_eq!(mjd_from_ymd(2000, 1, 1), 51_544.0); // The Unix epoch is MJD 40587. + // Unix 历元是 MJD 40587。 assert_eq!(mjd_from_ymd(1970, 1, 1), 40_587.0); // The last leap second. + // 最后一个闰秒。 assert_eq!(mjd_from_ymd(2017, 1, 1), 57_754.0); assert_eq!(mjd_from_ymd(2024, 1, 1), 60_310.0); } @@ -73,8 +81,10 @@ fn known_calendar_dates_map_to_known_modified_julian_dates() { #[test] fn calendar_conversion_handles_leap_days_and_month_ends() { // 2000 is a leap year, 1900 is not. + // 2000 年是闰年,1900 年不是。 assert_eq!(mjd_from_ymd(2000, 3, 1) - mjd_from_ymd(2000, 2, 1), 29.0); assert_eq!(mjd_from_ymd(1900, 3, 1) - mjd_from_ymd(1900, 2, 1), 28.0); // Consecutive month ends are one day apart. + // 连续的月末相隔一天。 assert_eq!(mjd_from_ymd(2024, 3, 1) - mjd_from_ymd(2024, 2, 29), 1.0); } diff --git a/crates/physics/apsis-leap/tests/time.rs b/crates/physics/apsis-leap/tests/time.rs index 570fc32..087c920 100644 --- a/crates/physics/apsis-leap/tests/time.rs +++ b/crates/physics/apsis-leap/tests/time.rs @@ -1,4 +1,6 @@ //! Tests for UTC and TAI conversions on the Modified Julian Date scale. +//! +//! 约化儒略日尺度上 UTC 与 TAI 换算的测试。 use apsis_leap::{ MJD_AT_UNIX_EPOCH, calendar_mjd, tai_minus_utc_at_mjd, tai_mjd_from_utc_mjd, @@ -26,12 +28,14 @@ fn first_entry_supplies_the_initial_offset() { let start = calendar_mjd(1972, 1, 1); assert_eq!(tai_minus_utc_at_mjd(start), Some(10.0)); // Just before midnight one day earlier the table does not apply. + // 再早一天、午夜之前,该表不适用。 assert_eq!(tai_minus_utc_at_mjd(start - 1.0), None); } #[test] fn offset_changes_exactly_on_the_effective_date() { // The 2017-01-01 leap second raised the offset from 36 to 37. + // 2017-01-01 的闰秒把偏移从 36 升到 37。 let effective = calendar_mjd(2017, 1, 1); assert_eq!(tai_minus_utc_at_mjd(effective - 1.0), Some(36.0)); assert_eq!(tai_minus_utc_at_mjd(effective), Some(37.0)); @@ -49,6 +53,7 @@ fn every_entry_is_reachable_at_its_effective_instant() { #[test] fn offset_is_constant_between_entries() { // Mid 2016 sits between the 2015-07-01 and 2017-01-01 entries. + // 2016 年年中位于 2015-07-01 与 2017-01-01 两项之间。 let instant = calendar_mjd(2016, 6, 1); assert_eq!(tai_minus_utc_at_mjd(instant), Some(36.0)); assert_eq!(tai_minus_utc_at_mjd(instant + 0.25), Some(36.0)); @@ -66,6 +71,7 @@ fn utc_to_tai_adds_the_offset() { let tai = tai_mjd_from_utc_mjd(utc).expect("instant is inside the table"); // An MJD near 58000 has a resolution of about 7e-12 days, so the sum // can only be compared to well below a microsecond. + // 接近 58000 的 MJD 分辨率约为 7e-12 天,故该和只能比较到远低于一微秒。 assert!((tai - utc - 37.0 / SECONDS_PER_DAY).abs() < 1e-11); } @@ -93,6 +99,7 @@ fn utc_to_tai_round_trips() { fn tai_to_utc_recovers_the_offset_on_each_side_of_a_step() { let effective = calendar_mjd(2017, 1, 1); // One hour before the step the offset is 36 s, one hour after it is 37 s. + // 阶跃前一小时偏移为 36 s,其后一小时为 37 s。 let before_utc = effective - 1.0 / 24.0; let after_utc = effective + 1.0 / 24.0; let before_tai = tai_mjd_from_utc_mjd(before_utc).expect("inside the table"); @@ -111,6 +118,7 @@ fn tai_to_utc_rejects_non_finite_input() { fn tai_to_utc_at_the_boundary_stays_inside_the_table() { // The first TAI instant that the table covers is 1972-01-01 00:00:10 // TAI, because the offset there is ten seconds. + // 该表覆盖的第一个 TAI 瞬时是 1972-01-01 00:00:10 TAI,因为那里的偏移是十秒。 let boundary = calendar_mjd(1972, 1, 1) + 10.0 / SECONDS_PER_DAY; let utc = utc_mjd_from_tai_mjd(boundary).expect("boundary is covered"); assert!((utc - calendar_mjd(1972, 1, 1)).abs() < 1e-10); diff --git a/crates/physics/apsis-time/tests/calendar.rs b/crates/physics/apsis-time/tests/calendar.rs index 5061e91..205442c 100644 --- a/crates/physics/apsis-time/tests/calendar.rs +++ b/crates/physics/apsis-time/tests/calendar.rs @@ -1,4 +1,6 @@ //! Tests for the calendar conversions. +//! +//! 日历换算的测试。 use apsis_time::{Calendar, days_in_month, is_leap_year, mjd_from_ymd, ymd_from_mjd}; @@ -19,6 +21,7 @@ fn known_dates_map_to_known_modified_julian_dates() { #[test] fn calendar_conversion_round_trips_over_a_long_span() { // Walk day by day over a span that crosses every leap year rule. + // 逐日走过一个跨越每条闰年规则的跨度。 let mut mjd = mjd_from_ymd(1899, 12, 20); let end = mjd_from_ymd(2101, 1, 10); while mjd < end { @@ -38,6 +41,7 @@ fn fractional_modified_julian_dates_map_to_the_containing_day() { assert_eq!(ymd_from_mjd(day + 0.25), (2024, 6, 15)); assert_eq!(ymd_from_mjd(day + 0.75), (2024, 6, 15)); // One day later, but still the same instant of the following day. + // 晚一天,但仍是次日的同一瞬时。 assert_eq!(ymd_from_mjd(day + 1.25), (2024, 6, 16)); } @@ -49,6 +53,7 @@ fn leap_years_follow_the_gregorian_rule() { assert!(!is_leap_year(2100)); assert!(!is_leap_year(2023)); // Year zero is a leap year in the proleptic calendar. + // 在外推日历中零年是闰年。 assert!(is_leap_year(0)); } @@ -91,6 +96,7 @@ fn validation_rejects_impossible_dates_and_the_leap_second_label() { assert!(!Calendar::new(2024, 1, 1, 0, 60, 0.0).is_valid()); // Second 60 is the leap second label, which is not a representable // instant on the continuous Modified Julian Date scale. + // 第 60 秒是闰秒标签,它在连续的约化儒略日尺度上不是可表示的瞬时。 assert!(!Calendar::new(2016, 12, 31, 23, 59, 60.0).is_valid()); assert!(!Calendar::new(2024, 1, 1, 0, 0, 61.0).is_valid()); } diff --git a/crates/physics/apsis-time/tests/epoch.rs b/crates/physics/apsis-time/tests/epoch.rs index 4679e17..2010ca5 100644 --- a/crates/physics/apsis-time/tests/epoch.rs +++ b/crates/physics/apsis-time/tests/epoch.rs @@ -1,4 +1,6 @@ //! Tests for the instant type and its scale conversions. +//! +//! 瞬时类型及其时标换算的测试。 use apsis_time::{Calendar, Epoch, SECONDS_PER_DAY, TAI_MINUS_GPS, TT_MINUS_TAI, TimeScale}; @@ -8,6 +10,12 @@ use apsis_time::{Calendar, Epoch, SECONDS_PER_DAY, TAI_MINUS_GPS, TT_MINUS_TAI, /// about 1e-11 days, roughly one microsecond. Conversions that add or /// remove a scale offset each introduce one rounding at that resolution, so /// comparisons are made at ten microseconds to leave room for a few steps. +/// +/// 当今附近的约化儒略日比较的容差。 +/// +/// 双精度把接近 60 000 的约化儒略日保持到约 1e-11 天的分辨率,大致一微秒。每次 +/// 加上或减去时标偏移的换算都会在该分辨率上引入一次舍入,故比较取十微秒,为若干 +/// 步留出余量。 const MJD_TOL: f64 = 1e-9; fn assert_mjd_close(actual: f64, expected: f64) { @@ -20,6 +28,7 @@ fn assert_mjd_close(actual: f64, expected: f64) { #[test] fn j2000_constant_lands_on_the_defining_instant() { // 2000-01-01T12:00:00 TT is Julian Date 2451545.0, or MJD 51544.5. + // 2000-01-01T12:00:00 TT 是儒略日 2451545.0,即 MJD 51544.5。 let tt = Epoch::J2000 .mjd(TimeScale::Tt) .expect("TT is always available"); @@ -43,6 +52,7 @@ fn uniform_scales_differ_by_their_defining_offsets() { fn construction_from_each_scale_round_trips() { // 60 000.5 is a mid-day instant, which keeps the fractional part of // every intermediate value exactly representable. + // 60 000.5 是一个日中的瞬时,这使每个中间值的零头都精确可表示。 let input = 60_000.5; for scale in [ TimeScale::Tai, @@ -53,6 +63,7 @@ fn construction_from_each_scale_round_trips() { let epoch = Epoch::from_mjd(input, scale).expect("instant is inside the leap table"); // Reading the instant back on the same scale recovers the number // that was supplied. + // 在同一时标上读回该瞬时,恢复了所提供的那一数。 assert_mjd_close(epoch.mjd(scale).expect("scale is available"), input); } } @@ -60,6 +71,7 @@ fn construction_from_each_scale_round_trips() { #[test] fn utc_construction_applies_the_leap_second_offset() { // In 2017 the offset TAI - UTC is 37 seconds. + // 2017 年 TAI - UTC 的偏移是 37 秒。 let utc = 60_000.0; let epoch = Epoch::from_mjd(utc, TimeScale::Utc).expect("covered by the table"); assert_mjd_close(epoch.tai_mjd(), utc + 37.0 / SECONDS_PER_DAY); @@ -76,6 +88,7 @@ fn utc_conversion_is_rejected_before_the_table_begins() { let before = 40_000.0; assert_eq!(Epoch::from_mjd(before, TimeScale::Utc), None); // The same instant is representable in TAI. + // 同一瞬时在 TAI 中可表示。 let epoch = Epoch::from_mjd(before, TimeScale::Tai).expect("TAI is always available"); assert_eq!(epoch.mjd(TimeScale::Utc), None); } @@ -109,6 +122,7 @@ fn seconds_until_is_independent_of_the_scale_used() { .expect("covered by the table"); // One calendar day in early 2024 contains no leap second, so the // elapsed time is exactly 86 400 seconds. + // 2024 年初的一个日历日不含闰秒,故流逝时间恰好是 86 400 秒。 assert!((start.seconds_until(end) - 86_400.0).abs() < 1e-6); assert!((end.seconds_until(start) + 86_400.0).abs() < 1e-6); } @@ -127,10 +141,13 @@ fn add_seconds_crosses_a_leap_second_as_elapsed_time() { // 2016-12-31T23:59:59.5 UTC, half a second before the inserted leap // second. The leap second makes the next UTC midnight 1.5 SI seconds // away, because the interval labelled 23:59:60 is a whole second long. + // 2016-12-31T23:59:59.5 UTC,即所插入闰秒之前的半秒。该闰秒使下一个 UTC 午夜 + // 在 1.5 SI 秒之外,因为标为 23:59:60 的区间整整一秒长。 let before = Epoch::from_calendar(Calendar::new(2016, 12, 31, 23, 59, 59.5), TimeScale::Utc) .expect("covered by the table"); // One and a half seconds of elapsed time reaches the next midnight. + // 一秒半的流逝时间到达下一个午夜。 let after = before.add_seconds(1.5); let utc = after .calendar(TimeScale::Utc) @@ -141,10 +158,13 @@ fn add_seconds_crosses_a_leap_second_as_elapsed_time() { // An instant strictly inside the leap second has no UTC label on the // continuous Modified Julian Date scale, because it would be written // 23:59:60 and that value does not exist there. + // 严格位于闰秒内部的瞬时,在连续的约化儒略日尺度上没有 UTC 标签,因为它会被 + // 写成 23:59:60,而该值在那里不存在。 assert_eq!(before.add_seconds(1.0).mjd(TimeScale::Utc), None); // Reading the interval on the UTC clock shows half a second of clock // advance over one and a half seconds of elapsed time. + // 在 UTC 时钟上读该区间,显示一秒半的流逝时间里时钟推进了半秒。 let utc_before = before.mjd(TimeScale::Utc).expect("covered by the table"); let utc_after = after.mjd(TimeScale::Utc).expect("covered by the table"); let clock_advance = (utc_after - utc_before) * SECONDS_PER_DAY; @@ -164,6 +184,7 @@ fn ordering_follows_the_instant() { fn equality_is_scale_independent() { // The same physical instant described in two scales yields equal // epochs. + // 在两种时标中描述的同一物理瞬时给出相等的历元。 let in_tai = Epoch::from_mjd(60_000.0, TimeScale::Tai).expect("TAI is always available"); let in_tt = Epoch::from_mjd(60_000.0 + TT_MINUS_TAI / SECONDS_PER_DAY, TimeScale::Tt) .expect("TT is always available"); diff --git a/crates/physics/apsis-time/tests/julian.rs b/crates/physics/apsis-time/tests/julian.rs index c83207f..57b00c5 100644 --- a/crates/physics/apsis-time/tests/julian.rs +++ b/crates/physics/apsis-time/tests/julian.rs @@ -1,10 +1,13 @@ //! Tests for the Julian Date conversions. +//! +//! 儒略日换算的测试。 use apsis_time::{JULIAN_DATE_OFFSET, julian_date_from_mjd, mjd_from_julian_date}; #[test] fn the_offset_matches_the_defining_values() { // MJD 0 is Julian Date 2400000.5. + // MJD 0 是儒略日 2400000.5。 assert_eq!(JULIAN_DATE_OFFSET, 2_400_000.5); assert_eq!(julian_date_from_mjd(0.0), 2_400_000.5); assert_eq!(mjd_from_julian_date(2_400_000.5), 0.0); @@ -13,6 +16,7 @@ fn the_offset_matches_the_defining_values() { #[test] fn j2000_maps_to_the_expected_julian_date() { // J2000.0 is MJD 51544.5 and Julian Date 2451545.0. + // J2000.0 是 MJD 51544.5 与儒略日 2451545.0。 assert_eq!(julian_date_from_mjd(51_544.5), 2_451_545.0); assert_eq!(mjd_from_julian_date(2_451_545.0), 51_544.5); } diff --git a/crates/physics/apsis-time/tests/scale.rs b/crates/physics/apsis-time/tests/scale.rs index cfe42be..310057b 100644 --- a/crates/physics/apsis-time/tests/scale.rs +++ b/crates/physics/apsis-time/tests/scale.rs @@ -1,4 +1,6 @@ //! Tests for the time scale definitions. +//! +//! 时标定义的测试。 use apsis_time::{TAI_MINUS_GPS, TT_MINUS_TAI, TimeScale}; @@ -22,7 +24,9 @@ fn scales_report_their_names() { #[test] fn offsets_match_their_definitions() { // Terrestrial Time is ahead of TAI by the defining constant. + // 地球时比 TAI 领先一个定义常量。 assert_eq!(TT_MINUS_TAI, 32.184); // GPS time lags TAI by the defining integer number of seconds. + // GPS 时比 TAI 落后一个定义整数秒数。 assert_eq!(TAI_MINUS_GPS, 19.0); }