docs: bilingual comments for the rf tests

This commit is contained in:
ApsisRS contributors 2026-10-02 01:57:31 +08:00
parent 1dd77d6d6e
commit 68735b2075
17 changed files with 251 additions and 0 deletions

View File

@ -1,9 +1,13 @@
//! Tests for the parabolic aperture model.
//!
//! 抛物面孔径模型的测试。
use apsis_antenna::{ParabolicAperture, beam_width};
use core::f64::consts::PI;
/// A three meter dish with a typical aperture efficiency.
///
/// 一面三米天线,具有典型孔径效率。
const DISH: ParabolicAperture = ParabolicAperture {
diameter: 3.0,
efficiency: 0.65,
@ -20,11 +24,13 @@ fn aperture_area_follows_from_the_diameter() {
fn peak_gain_matches_the_closed_form() {
// At 12 GHz the wavelength is 0.02498 m, and the gain is
// eta (pi D / lambda)^2.
// 12 GHz 下波长为 0.02498 m,增益为 eta (pi D / lambda)^2。
let wavelength = 0.024_982_710_7;
let gain = DISH.peak_gain(wavelength).expect("valid");
let expected = 0.65 * (PI * 3.0 / wavelength).powi(2);
assert!((gain.ratio() - expected).abs() < 1e-6);
// The result is about 49.66 dBi.
// 结果约为 49.66 dBi。
assert!(
(gain.dbi().expect("positive") - 49.661_764_937_398_814).abs() < 1e-9,
"dBi {}",
@ -46,6 +52,7 @@ fn a_larger_dish_has_more_gain() {
let small_gain = small.peak_gain(wavelength).expect("valid");
let large_gain = large.peak_gain(wavelength).expect("valid");
// Doubling the diameter quadruples the gain, which is six decibels.
// 直径加倍使增益变为四倍,即六分贝。
assert!(
(large_gain.dbi().expect("positive") - small_gain.dbi().expect("positive") - 6.020_6).abs()
< 1e-6
@ -76,6 +83,7 @@ fn the_beam_width_follows_the_wavelength_over_diameter() {
let tapered = DISH.half_power_beam_width(wavelength).expect("valid");
assert!((uniform - 1.02 * wavelength / 3.0).abs() < 1e-12);
// A tapered illumination widens the beam.
// 锥削照射使波束变宽。
assert!(tapered > uniform);
assert!((tapered - 1.15 * wavelength / 3.0).abs() < 1e-12);
}
@ -84,6 +92,7 @@ fn the_beam_width_follows_the_wavelength_over_diameter() {
fn the_uniform_beam_width_matches_a_known_value() {
// A 3 m dish at 12 GHz has a half power beam width near 0.0085 radians,
// which is about 0.49 degrees.
// 12 GHz 下的 3 m 天线半功率波束宽度约为 0.0085 弧度,即约 0.49 度。
let width = DISH.uniform_beam_width(0.024_982_710_7).expect("valid");
assert!((width - 0.008_494).abs() < 1e-5, "width {width}");
assert!((width.to_degrees() - 0.486_7).abs() < 0.001);
@ -134,6 +143,7 @@ fn an_invalid_aperture_produces_no_result() {
#[test]
fn the_aperture_and_gain_conversions_are_consistent() {
// The peak gain and the effective area must describe the same antenna.
// 峰值增益与有效面积必须描述同一面天线。
let wavelength = 0.025;
let gain = DISH.peak_gain(wavelength).expect("valid");
let aperture = gain.effective_aperture(wavelength).expect("valid");

View File

@ -1,4 +1,6 @@
//! Tests for off-axis gain patterns.
//!
//! 偏轴增益方向图的测试。
use apsis_antenna::{
ElementPattern, ParabolicAperture, Pattern, floor_angle, mask_gain, off_axis_attenuation,
@ -20,6 +22,8 @@ fn boresight_gain_is_the_peak() {
fn the_half_power_angle_gives_three_decibels() {
// At half the beam width the off-axis angle is half of the half power
// beam width, and the squared angle law gives 12 * (0.5)^2 = 3 dB.
// 在半个波束宽度处偏轴角是半功率波束宽度的一半,角平方律给出
// 12 * (0.5)^2 = 3 dB。
let peak = Gain::from_dbi(30.0).expect("valid");
let beam = 0.01;
let attenuation = off_axis_attenuation(peak, beam, beam / 2.0, 32.0).expect("valid arguments");
@ -36,11 +40,14 @@ fn the_attenuation_grows_with_the_square_of_the_angle() {
let beam = 0.02;
// Both angles are chosen to stay on the squared angle law, well inside
// the angle at which the pattern reaches its floor.
// 两个角都选在角平方律上,远在方向图到达其底限的角度之内。
let first = off_axis_attenuation(peak, beam, 0.02, 60.0).expect("valid");
let second = off_axis_attenuation(peak, beam, 0.04, 60.0).expect("valid");
// At the beam width the attenuation is 12 dB; at twice the beam width
// it is 48 dB. The rise of 36 dB is what a quadrupling of the
// attenuation ratio means in decibels.
// 在波束宽度处衰减为 12 dB;在两倍波束宽度处为 48 dB。36 dB 的上升即衰减比变为
// 四倍在分贝上的含义。
assert!((first.db() - (40.0 - 12.0)).abs() < 1e-9);
assert!((second.db() - (40.0 - 48.0)).abs() < 1e-9);
let rise = first.db() - second.db();
@ -53,14 +60,17 @@ fn the_pattern_floors_at_the_sidelobe_level() {
let beam = 0.02;
let floor = 32.0;
// Far off axis the gain is the peak minus the floor.
// 远偏轴处增益是峰值减底限。
let far = off_axis_attenuation(peak, beam, PI, floor).expect("valid");
assert!((far.db() - (40.0 - floor)).abs() < TOLERANCE);
// The floor is reached at the angle the helper reports.
// 在该辅助函数所报告的角度处到达底限。
let angle = floor_angle(beam, floor).expect("valid");
let at_floor = off_axis_attenuation(peak, beam, angle, floor).expect("valid");
assert!((at_floor.db() - (40.0 - floor)).abs() < 1e-9);
// Just inside that angle the gain is above the floor.
// 恰在该角之内增益高于底限。
let inside = off_axis_attenuation(peak, beam, angle * 0.99, floor).expect("valid");
assert!(inside.db() > at_floor.db());
}
@ -80,6 +90,8 @@ fn invalid_arguments_are_rejected() {
fn the_itu_mask_uses_the_large_aperture_branch() {
// For D/lambda >= 100 the pattern is 29 - 25 log10(theta) between one
// and 36 degrees and -10 beyond.
// 对 D/lambda >= 100,方向图在 1 到 36 度之间为 29 - 25 log10(theta),超出则为
// -10。
let at_ten_degrees = mask_gain(1_000.0, 10.0_f64.to_radians()).expect("valid");
assert!((at_ten_degrees - (29.0 - 25.0 * 10.0_f64.log10())).abs() < 1e-9);
assert!((at_ten_degrees - 4.0).abs() < 1e-9);
@ -87,6 +99,8 @@ fn the_itu_mask_uses_the_large_aperture_branch() {
// The falling branch ends at 36 degrees, where the expression is close
// to but not exactly -10 dB. The standard accepts that small step and
// then holds the value at -10 dB.
// 下降支在 36 度处结束,那里该表达式接近但不恰好是 -10 dB。该标准接受这一小
// 台阶,随后把值保持在 -10 dB。
let at_break = mask_gain(1_000.0, 36.0_f64.to_radians()).expect("valid");
assert!(
(at_break - (29.0 - 25.0 * 36.0_f64.log10())).abs() < 1e-9,
@ -101,10 +115,12 @@ fn the_itu_mask_uses_the_large_aperture_branch() {
#[test]
fn the_itu_mask_uses_the_small_aperture_branch() {
// For D/lambda < 100 the pattern starts at 32 and breaks at 48 degrees.
// 对 D/lambda < 100,方向图从 32 开始并在 48 度处转折。
let at_ten_degrees = mask_gain(50.0, 10.0_f64.to_radians()).expect("valid");
assert!((at_ten_degrees - (32.0 - 25.0 * 10.0_f64.log10())).abs() < 1e-9);
// The falling branch ends at 48 degrees, again close to -10 dB.
// 下降支在 48 度处结束,同样接近 -10 dB。
let at_break = mask_gain(50.0, 48.0_f64.to_radians()).expect("valid");
assert!(
(at_break - (32.0 - 25.0 * 48.0_f64.log10())).abs() < 1e-9,
@ -119,6 +135,7 @@ fn the_itu_mask_uses_the_small_aperture_branch() {
#[test]
fn the_itu_mask_is_capped_inside_one_degree() {
// Below one degree the pattern does not continue to rise without bound.
// 在一度以下,方向图不会继续无界上升。
let peak_limit = 32.0 - 25.0 * 1.0_f64.log10();
for angle_degrees in [0.0_f64, 0.01, 0.5, 0.99] {
let value = mask_gain(50.0, angle_degrees.to_radians()).expect("valid");
@ -144,6 +161,7 @@ fn an_element_pattern_reproduces_the_model() {
assert!(pattern.is_valid());
let gain = pattern.gain_at(0.01).expect("valid");
// 0.01 is half the beam width, so the attenuation is 3 dB.
// 0.01 是半个波束宽度,故衰减为 3 dB。
assert!((gain.dbi().expect("positive") - 32.0).abs() < 1e-9);
}
@ -216,6 +234,7 @@ fn the_pattern_enum_derives_the_same_values() {
beam_width: 0.05,
};
// The wavelength is ignored for a directly specified pattern.
// 对直接指定的方向图,波长被忽略。
assert!(
(direct
.peak_gain(0.025)
@ -244,6 +263,7 @@ fn the_default_pattern_is_effectively_isotropic() {
assert!(pattern.is_valid());
// With a floor at the smallest positive number the attenuation is
// negligible in every direction.
// 底限取最小正数时,各方向的衰减都可忽略。
let gain = pattern.gain_at(1.0).expect("valid");
assert!(gain.dbi().expect("positive").abs() < 1e-6);
}

View File

@ -1,4 +1,6 @@
//! Tests for the ITU-R S.465 reference antenna pattern.
//!
//! ITU-R S.465 参考天线方向图的测试。
use apsis_antenna::{
PEAK_GAIN_OFFSET_DB, diameter_over_wavelength_from_peak_gain, main_lobe_break_angle,
@ -10,12 +12,14 @@ const TOLERANCE: f64 = 1e-9;
#[test]
fn the_peak_gain_follows_the_recommendation() {
// The recommendation gives 20 log10(D / lambda) + 7.7.
// 该建议书给出 20 log10(D / lambda) + 7.7。
for ratio in [10.0_f64, 50.0, 100.0, 200.0, 1_000.0] {
let expected = 20.0 * ratio.log10() + PEAK_GAIN_OFFSET_DB;
let actual = reference_peak_gain(ratio).expect("valid ratio");
assert!((actual - expected).abs() < TOLERANCE, "ratio {ratio}");
}
// A ratio of one hundred gives 47.7 dBi.
// 比值一百给出 47.7 dBi。
assert!((reference_peak_gain(100.0).expect("valid") - 47.7).abs() < TOLERANCE);
}
@ -40,6 +44,7 @@ fn the_pattern_at_boresight_is_the_peak_gain() {
#[test]
fn the_main_lobe_falls_as_the_square_of_the_angle() {
// Inside the main lobe the attenuation is 2.5e-3 (D/lambda phi)^2.
// 主瓣内衰减为 2.5e-3 (D/lambda phi)^2。
let ratio = 100.0;
let peak = reference_peak_gain(ratio).expect("valid");
for angle_degrees in [0.05_f64, 0.1, 0.2, 0.3] {
@ -56,6 +61,7 @@ fn the_main_lobe_falls_as_the_square_of_the_angle() {
fn the_pattern_matches_recorded_values() {
// These are the values of the recommendation for a ratio of one hundred,
// computed independently from its equations.
// 这些是该建议书对比值一百的值,由其方程独立计算。
let cases: [(f64, f64); 8] = [
(0.1, 47.45),
(0.5, 41.45),
@ -81,6 +87,8 @@ fn the_pattern_matches_recorded_values_for_a_small_aperture() {
// a floor angle of 48 degrees.
// At exactly the floor angle the branch switches to the floor, so the
// last value of the falling branch is approached from below.
// 比值五十使用小孔径分支,head 为 32,底限角为 48 度。
// 恰在底限角处该分支切换到取底限,故下降支的最后值是从下方趋近的。
let cases: [(f64, f64); 6] = [
(0.1, 41.616_900_087),
(1.0, 35.429_400_087),
@ -102,6 +110,7 @@ fn the_pattern_matches_recorded_values_for_a_small_aperture() {
fn the_two_branches_meet_at_the_break_angle() {
// The main lobe and the sidelobe region must agree at the junction, or
// the pattern would jump.
// 主瓣与旁瓣区必须在交界处一致,否则方向图会跳变。
for ratio in [50.0_f64, 100.0, 200.0, 500.0] {
let peak = reference_peak_gain(ratio).expect("valid");
let break_angle = main_lobe_break_angle(ratio).expect("valid");
@ -124,6 +133,7 @@ fn the_two_branches_meet_at_the_break_angle() {
fn the_break_angle_shrinks_as_the_aperture_grows() {
// A larger antenna has a narrower main lobe, so the sidelobe region
// begins sooner.
// 更大的天线主瓣更窄,故旁瓣区开始得更早。
let small = main_lobe_break_angle(50.0).expect("valid");
let medium = main_lobe_break_angle(100.0).expect("valid");
let large = main_lobe_break_angle(400.0).expect("valid");
@ -168,6 +178,7 @@ fn the_pattern_falls_monotonically_across_the_main_lobe() {
fn the_beam_width_follows_from_the_main_lobe_law() {
// Three decibels is reached at sqrt(3 / 2.5e-3) / (D/lambda) degrees
// from boresight, so the full width is twice that.
// 三分贝在距视轴 sqrt(3 / 2.5e-3) / (D/lambda) 度处达到,故全宽是它的两倍。
let ratio = 100.0;
let width = reference_beam_width(ratio).expect("valid");
let half_degrees = (3.0_f64 / 2.5e-3).sqrt() / ratio;
@ -175,6 +186,7 @@ fn the_beam_width_follows_from_the_main_lobe_law() {
assert!((width - expected).abs() < 1e-15);
// The gain at half the beam width from boresight is three decibels down.
// 距视轴半个波束宽度处的增益低三分贝。
let gain = reference_pattern_gain(ratio, width / 2.0).expect("valid");
let peak = reference_peak_gain(ratio).expect("valid");
assert!(

View File

@ -6,6 +6,12 @@
//! with a line-by-line implementation of the recommendation and are recorded
//! here so that a change to the line tables or the formulae shows up as a
//! failing assertion.
//!
//! 气体衰减的测试。
//!
//! 期望值是 ITU-R 建议书 P.676-12 对参考大气的值:干空气气压 1013.25 hPa、水汽
//! 密度 7.5 g/m^3、温度 288.15 K。它们由该建议书的逐线实现产生并记录在此,使对
//! 线表或公式的改动表现为断言失败。
use apsis_atmosphere::{
Atmosphere, equivalent_heights, specific_attenuation, specific_attenuation_dry_air,
@ -16,9 +22,15 @@ use apsis_atmosphere::{
///
/// The implementation reproduces the reference to about twelve significant
/// digits, so this allows for the rounding of the recorded values.
///
/// 比衰减的容差,单位为分贝每千米。
///
/// 该实现把参考值复现到约十二位有效数字,故此值容许所记录值的舍入。
const TOLERANCE: f64 = 1e-6;
/// The reference atmosphere of the recommendation.
///
/// 该建议书的参考大气。
fn reference() -> Atmosphere {
Atmosphere::REFERENCE
}
@ -42,6 +54,7 @@ fn the_water_vapour_partial_pressure_follows_the_ideal_gas_law() {
#[test]
fn dry_air_attenuation_matches_the_recommendation() {
// The recorded values are from the reference implementation of P.676-12.
// 所记录的值来自 P.676-12 的参考实现。
let cases = [
(1.0, 0.005_388_658_168),
(10.0, 0.008_224_416_703),
@ -93,11 +106,13 @@ fn the_two_contributions_add() {
fn the_oxygen_complex_dominates_near_sixty_gigahertz() {
// The 60 GHz complex is a set of more than thirty lines, so the dry air
// term is many orders of magnitude above the water vapour term there.
// 60 GHz 复合体是三十多条线的集合,故那里的干空气项比水汽项高许多个数量级。
let frequency = 60.0;
let dry = specific_attenuation_dry_air(frequency, reference()).expect("valid");
let wet = specific_attenuation_water_vapour(frequency, reference()).expect("valid");
// The dry air value is about 14.6 dB/km against 0.15 dB/km for water
// vapour, a ratio near one hundred.
// 干空气值约为 14.6 dB/km,而水汽为 0.15 dB/km,比值接近一百。
assert!(dry > 50.0 * wet, "dry {dry} wet {wet}");
}
@ -108,6 +123,7 @@ fn the_water_vapour_line_dominates_at_one_hundred_and_eighty_three_gigahertz() {
let wet = specific_attenuation_water_vapour(frequency, reference()).expect("valid");
assert!(wet > dry);
// The line reaches over twenty decibels per kilometer.
// 该线达到每千米二十多分贝。
assert!(wet > 20.0, "wet {wet}");
}
@ -115,6 +131,7 @@ fn the_water_vapour_line_dominates_at_one_hundred_and_eighty_three_gigahertz() {
fn attenuation_is_small_in_the_microwave_windows() {
// Below 15 GHz the total specific attenuation is a hundredth of a
// decibel per kilometer or less, which is why those bands are used.
// 15 GHz 以下总比衰减为每千米百分之一分贝或更少,这正是那些频段被使用的原因。
for frequency in [1.0, 2.0, 4.0, 6.0, 10.0, 12.0, 15.0] {
let total = specific_attenuation(frequency, reference()).expect("valid");
assert!(total < 0.05, "{frequency} GHz gave {total} dB/km");
@ -125,6 +142,7 @@ fn attenuation_is_small_in_the_microwave_windows() {
fn dry_air_attenuation_peaks_inside_the_oxygen_complex() {
// The strongest line is near 60.4 GHz, and the value there must exceed
// the values on either side.
// 最强线在 60.4 GHz 附近,那里的值必须超过两侧的值。
let peak = specific_attenuation_dry_air(60.434_778, reference()).expect("valid");
for frequency in [50.0, 55.0, 65.0, 70.0] {
let value = specific_attenuation_dry_air(frequency, reference()).expect("valid");
@ -172,16 +190,20 @@ fn the_dry_height_is_capped_below_seventy_gigahertz() {
// the lower frequencies. The bound is `10.7 rp^0.3`, where `rp` is the
// ratio of the total barometric pressure, dry air plus water vapour, to
// the reference pressure.
// 式 30 在较低频率处把干空气高度限制到一个依赖气压的界。该界为 `10.7 rp^0.3`,
// 其中 `rp` 是总气压(干空气加水汽)与参考气压之比。
let atmosphere = reference();
let total = atmosphere.pressure_hpa + atmosphere.water_vapour_partial_pressure();
let rp = total / 1_013.25;
let bound = 10.7 * rp.powf(0.3);
// At 60 GHz the uncapped value is above the bound, so the bound applies.
// 在 60 GHz 处未设界的值高于该界,故界适用。
let heights = equivalent_heights(60.0, atmosphere).expect("valid");
assert!((heights.dry_km - bound).abs() < 1e-9, "{}", heights.dry_km);
// Above 70 GHz no cap is applied, and the value exceeds it.
// 70 GHz 以上不施加界,而该值超过它。
let high = equivalent_heights(118.0, atmosphere).expect("valid");
assert!(high.dry_km > bound);
}
@ -242,6 +264,7 @@ fn slant_path_attenuation_scales_with_the_elevation() {
let frequency = 12.0;
let zenith = zenith_attenuation(frequency, reference()).expect("valid");
// At thirty degrees elevation the path is twice the zenith path.
// 三十度仰角处路径是天顶路径的两倍。
let thirty =
slant_path_attenuation(frequency, core::f64::consts::PI / 6.0, reference()).expect("valid");
assert!((thirty - 2.0 * zenith).abs() < 1e-9);
@ -261,6 +284,7 @@ fn the_exact_slant_path_matches_the_recommendation() {
use apsis_atmosphere::slant_path_attenuation_exact;
// These values are from a reference implementation of the exact method
// of ITU-R Recommendation P.676-12.
// 这些值来自 ITU-R 建议书 P.676-12 精确方法的参考实现。
let cases: [(f64, f64, f64); 10] = [
(12.0, 90.0, 0.059926619268),
(12.0, 30.0, 0.119683280157),
@ -293,6 +317,9 @@ fn the_exact_and_approximate_methods_agree_at_high_elevation() {
// parallel geometry is exact. The recommendation notes that the
// approximate method is intended only for the elevation range above
// about five degrees and for rough work.
// 两种方法使用不同的表述,故即便在天顶(那里平面平行几何是精确的)它们也只相符
// 到百分之几而非精确相等。该建议书指出近似方法只意在于约五度以上的仰角范围以及
// 粗略工作。
let exact = slant_path_attenuation_exact(12.0, core::f64::consts::FRAC_PI_2, reference())
.expect("valid");
let approximate =
@ -307,6 +334,8 @@ fn the_exact_method_exceeds_the_approximate_one_at_low_elevation() {
// At a low elevation the curved path through the thinning atmosphere is
// shorter than the plane parallel estimate, and the refraction bends
// the ray downward, so the two methods diverge.
// 在低仰角处,穿过变稀薄大气的弯曲路径比平面平行估计短,且折射使射线向下弯折,
// 故两种方法分歧。
let elevation = 2.0_f64.to_radians();
let exact = slant_path_attenuation_exact(12.0, elevation, reference()).expect("valid");
let approximate = slant_path_attenuation(12.0, elevation, reference()).expect("valid");

View File

@ -1,4 +1,6 @@
//! Tests for the atmospheric path geometry.
//!
//! 大气路径几何的测试。
use apsis_atmosphere::{air_mass, elevation_from_zenith, zenith_to_elevation};
use core::f64::consts::{FRAC_PI_2, FRAC_PI_4, FRAC_PI_6};
@ -25,10 +27,13 @@ fn the_air_mass_is_the_cosecant_of_the_elevation() {
#[test]
fn known_elevations_give_known_air_masses() {
// Thirty degrees is a cosecant of two.
// 三十度的余割为二。
assert!((air_mass(FRAC_PI_6) - 2.0).abs() < 1e-9);
// Forty five degrees is the square root of two.
// 四十五度是根号二。
assert!((air_mass(FRAC_PI_4) - 2.0_f64.sqrt()).abs() < 1e-9);
// Ten degrees is about 5.76.
// 十度约为 5.76。
assert!((air_mass(10.0_f64.to_radians()) - 5.758_8).abs() < 1e-3);
}

View File

@ -1,4 +1,6 @@
//! Tests for the ITU-R P.835 reference standard atmosphere.
//!
//! ITU-R P.835 参考标准大气的测试。
use apsis_atmosphere::{
geopotential_height, radio_refractive_index, standard_pressure, standard_temperature,
@ -11,11 +13,13 @@ const TOLERANCE: f64 = 1e-9;
fn the_geopotential_height_is_below_the_geometric_height() {
// The conversion compresses the height because gravity weakens with
// altitude, so the geopotential height is always the smaller value.
// 该换算压缩高度,因为引力随高度减弱,故势高总是较小的值。
for geometric in [0.0, 1.0, 11.0, 50.0, 100.0] {
let geopotential = geopotential_height(geometric);
assert!(geopotential <= geometric, "height {geometric}");
}
// At the surface the two coincide.
// 在地表处二者重合。
assert_eq!(geopotential_height(0.0), 0.0);
}
@ -23,6 +27,7 @@ fn the_geopotential_height_is_below_the_geometric_height() {
fn the_surface_conditions_match_the_recommendation() {
// The mean annual global reference atmosphere starts at 288.15 K and
// 1013.25 hPa.
// 年平均全球参考大气始于 288.15 K 与 1013.25 hPa。
assert!((standard_temperature(0.0) - 288.15).abs() < TOLERANCE);
assert!((standard_pressure(0.0) - 1_013.25).abs() < TOLERANCE);
assert!((standard_water_vapour_density(0.0, 7.5) - 7.5).abs() < TOLERANCE);
@ -32,12 +37,15 @@ fn the_surface_conditions_match_the_recommendation() {
fn the_temperature_profile_has_the_tabulated_segments() {
// The troposphere cools at 6.5 K per kilometer of geopotential height,
// so the rate is expressed against that height, not the geometric one.
// 对流层以每千米势高 6.5 K 冷却,故该速率是针对势高而非几何高度表达的。
let expected = 288.15 - 6.5 * geopotential_height(1.0);
assert!((standard_temperature(1.0) - expected).abs() < TOLERANCE);
// The stratosphere is isothermal at 216.65 K between 11 and 20 km of
// geopotential height. A geometric height of 15 km has a geopotential
// height of about 14.97 km, which is inside that band.
// 平流层在 11 至 20 km 势高之间等温于 216.65 K。15 km 的几何高度其势高约为
// 14.97 km,落在该带内。
let geopotential = geopotential_height(15.0);
assert!(
(11.0..=20.0).contains(&geopotential),
@ -47,6 +55,7 @@ fn the_temperature_profile_has_the_tabulated_segments() {
// Above 20 km geopotential the temperature rises at 1 K per kilometer.
// A geometric height of 30 km reaches the middle of that segment.
// 20 km 势高以上温度以每千米 1 K 上升。30 km 的几何高度到达该段中部。
let geopotential = geopotential_height(30.0);
assert!(
(20.0..32.0).contains(&geopotential),
@ -58,6 +67,8 @@ fn the_temperature_profile_has_the_tabulated_segments() {
// The temperature is continuous across each segment boundary. The
// boundaries are in geopotential height, so they are converted to the
// geometric heights at which they occur before sampling either side.
// 温度在每个分段边界处连续。各边界以势高给出,故在两侧采样之前先换算为它们
// 发生时对应的几何高度。
for geopotential in [11.0_f64, 20.0, 32.0, 47.0, 51.0, 71.0] {
let radius = 6_356.766;
let geometric = radius * geopotential / (radius - geopotential);
@ -75,6 +86,7 @@ fn the_temperature_is_continuous_across_every_segment() {
for boundary in [11.0_f64, 20.0, 32.0, 47.0, 51.0, 71.0, 84.852] {
// The boundaries are in geopotential height, so a geometric height
// just either side is used.
// 各边界以势高给出,故用恰好在其两侧的几何高度。
let below = standard_temperature(boundary - 0.01);
let above = standard_temperature(boundary + 0.01);
assert!(
@ -111,6 +123,7 @@ fn the_pressure_is_continuous_across_the_segments() {
fn the_pressure_at_the_tropopause_matches_the_reference() {
// The tropopause is at 11 km of geopotential height, which is a
// geometric height of about 11.02 km. The pressure there is 226.32 hPa.
// 对流层顶在 11 km 势高处,即约 11.02 km 的几何高度。那里的气压是 226.32 hPa。
let geometric = 11.019_068;
assert!((geopotential_height(geometric) - 11.0).abs() < 1e-5);
let pressure = standard_pressure(geometric);
@ -121,10 +134,12 @@ fn the_pressure_at_the_tropopause_matches_the_reference() {
fn the_water_vapour_profile_is_exponential() {
// The scale height is two kilometers, so the density halves about every
// 1.386 km.
// 标高为两千米,故密度大约每 1.386 km 减半。
let surface = 7.5;
let at_two = standard_water_vapour_density(2.0, surface);
assert!((at_two - surface / core::f64::consts::E).abs() < 1e-9);
// The profile scales with the surface value.
// 该廓线随地表值缩放。
let doubled = standard_water_vapour_density(2.0, 15.0);
assert!((doubled / at_two - 2.0).abs() < 1e-12);
}
@ -133,6 +148,7 @@ fn the_water_vapour_profile_is_exponential() {
fn the_radio_refractive_index_at_the_surface_is_about_one() {
// A refractivity of about three hundred N units gives an index about
// 1.0003.
// 约三百 N 单位的折射率给出约 1.0003 的折射指数。
let index = radio_refractive_index(1_013.25, 10.0, 288.15);
assert!(index > 1.0 && index < 1.001, "index {index}");
assert!((index - 1.000_315).abs() < 1e-5, "index {index}");
@ -149,5 +165,6 @@ fn the_radio_refractive_index_grows_with_humidity() {
fn a_zero_temperature_is_handled_without_dividing_by_zero() {
// The expression divides by the temperature, so a zero would give an
// infinite value. The function returns the index of vacuum instead.
// 该表达式除以温度,故零会给出无穷值。该函数改而返回真空的折射指数。
assert_eq!(radio_refractive_index(1_013.25, 10.0, 0.0), 1.0);
}

View File

@ -3,6 +3,11 @@
//! The expected values are those of the recommendation's tabulated grid,
//! read at the grid points and interpolated between them. They were produced
//! with the reference implementation of the recommendation.
//!
//! ITU-R P.839-4 雨顶高度模型的测试。
//!
//! 期望值是该建议书所列网格的值,在网格点处读取并在其之间插值。它们由该建议书的
//! 参考实现产生。
use apsis_atmosphere::{
RAIN_HEIGHT_OFFSET_KM, grid_step_degrees, rain_height, zero_isotherm_height,
@ -12,6 +17,10 @@ use apsis_atmosphere::{
///
/// The table is stored in metres, so the interpolation is exact to the
/// resolution the recommendation publishes.
///
/// 插值高度的容差,单位为千米。
///
/// 该表以米存储,故插值在该建议书所发布的分辨率内是精确的。
const TOLERANCE: f64 = 1e-9;
#[test]
@ -28,6 +37,7 @@ fn the_rain_height_offset_matches_the_recommendation() {
fn values_at_grid_points_match_the_recommendation() {
// These are the tabulated values at exact grid points, so no
// interpolation is involved.
// 这些是精确网格点处所列的值,故不涉及插值。
let cases = [
(0.0, 0.0, 4.566),
(45.0, 0.0, 2.356),
@ -78,11 +88,13 @@ fn the_rain_height_is_the_isotherm_height_plus_the_offset() {
fn longitude_wraps_at_the_seam() {
// The grid runs to 360 degrees, which is the same meridian as zero, so
// the height must be continuous across the seam.
// 网格延伸到 360 度,它与零度是同一子午线,故高度必须在接缝处连续。
let at_zero = zero_isotherm_height(10.0, 0.0).expect("valid");
let at_360 = zero_isotherm_height(10.0, 360.0).expect("valid");
assert!((at_zero - at_360).abs() < TOLERANCE);
// A negative longitude is the same as its positive equivalent.
// 负经度与其正等价经度相同。
let negative = zero_isotherm_height(10.0, -10.0).expect("valid");
let positive = zero_isotherm_height(10.0, 350.0).expect("valid");
assert!((negative - positive).abs() < TOLERANCE);
@ -92,6 +104,7 @@ fn longitude_wraps_at_the_seam() {
fn the_height_is_bounded_by_the_table() {
// Every tabulated value lies between the minimum and maximum of the
// recommendation's global grid, which are about 0.006 km and 6.281 km.
// 每个所列值都落在该建议书全球网格的最小与最大之间,即约 0.006 km 与 6.281 km。
for latitude in [-90.0, -45.0, 0.0, 45.0, 90.0] {
for longitude in [0.0, 90.0, 180.0, 270.0] {
let height = zero_isotherm_height(latitude, longitude).expect("valid coordinates");
@ -106,6 +119,7 @@ fn the_height_is_bounded_by_the_table() {
#[test]
fn the_height_is_continuous_across_the_grid() {
// The interpolation must not jump between neighbouring grid cells.
// 插值不得在相邻网格单元之间跳变。
let mut previous = zero_isotherm_height(0.0, 0.0).expect("valid");
for step in 1..=240 {
let longitude = step as f64 * 1.5;
@ -122,6 +136,7 @@ fn the_height_is_continuous_across_the_grid() {
fn the_tropics_are_higher_than_the_poles() {
// The isotherm is highest near the equator, where the atmosphere is
// warmest, and lowest at high latitudes.
// 等温线在赤道附近最高(那里大气最暖),在高纬度最低。
let equator = zero_isotherm_height(0.0, 100.0).expect("valid");
let mid_latitude = zero_isotherm_height(50.0, 100.0).expect("valid");
let pole = zero_isotherm_height(80.0, 100.0).expect("valid");

View File

@ -3,6 +3,11 @@
//! The coefficient values are those of ITU-R Recommendation P.838-3,
//! produced with a reference implementation of the recommendation and
//! recorded here. The path results exercise the P.618-13 method.
//!
//! 降雨衰减的测试。
//!
//! 各系数值是 ITU-R 建议书 P.838-3 的值,由该建议书的参考实现产生并记录在此。
//! 路径结果检验 P.618-13 方法。
use apsis_atmosphere::{
DEFAULT_RAIN_HEIGHT_KM, polarisation_tilt, rain_attenuation, rain_coefficients,
@ -11,15 +16,25 @@ use apsis_atmosphere::{
use core::f64::consts::{FRAC_PI_2, PI};
/// Horizontal polarisation.
///
/// 水平极化。
const HORIZONTAL: f64 = 0.0;
/// Vertical polarisation.
///
/// 垂直极化。
const VERTICAL: f64 = FRAC_PI_2;
/// The zenith, where the two polarisations coincide.
///
/// 天顶,两种极化在那里重合。
const ZENITH: f64 = FRAC_PI_2;
/// A mid latitude elevation of thirty degrees.
///
/// 三十度的中纬度仰角。
const THIRTY_DEGREES: f64 = PI / 6.0;
/// The relative tolerance on the coefficient values.
///
/// 系数值的相对容差。
const TOLERANCE: f64 = 1e-9;
fn assert_relative(actual: f64, expected: f64, context: &str) {
@ -34,6 +49,7 @@ fn assert_relative(actual: f64, expected: f64, context: &str) {
fn coefficients_match_the_recommendation_at_the_zenith() {
// At the zenith the elevation term vanishes, so horizontal and vertical
// polarisation give the same coefficients.
// 天顶处仰角项消失,故水平与垂直极化给出相同的系数。
let cases = [
(12.0, 0.024_203_061_156_1, 1.151_599_196_3),
(20.0, 0.093_876_937_766_6, 1.019_877_631_17),
@ -70,6 +86,8 @@ fn horizontal_and_vertical_differ_off_the_zenith() {
// elevation the vertical coefficient is the larger of the two, and the
// two converge as the elevation rises and the geometry becomes
// symmetric.
// 远离天顶时极化要紧。12 GHz 与低仰角下垂直系数是两者中较大者,随仰角升高、
// 几何变得对称,二者趋于一致。
let low_elevation = 10.0_f64.to_radians();
let horizontal = rain_coefficients(12.0e9, HORIZONTAL, low_elevation).expect("valid");
let vertical = rain_coefficients(12.0e9, VERTICAL, low_elevation).expect("valid");
@ -77,6 +95,7 @@ fn horizontal_and_vertical_differ_off_the_zenith() {
assert_relative(vertical.0, 0.024_537_918_525_868_188, "vertical k");
// At the zenith they coincide.
// 天顶处二者重合。
let zenith_horizontal = rain_coefficients(12.0e9, HORIZONTAL, ZENITH).expect("valid");
let zenith_vertical = rain_coefficients(12.0e9, VERTICAL, ZENITH).expect("valid");
assert_relative(zenith_horizontal.0, zenith_vertical.0, "zenith k");
@ -86,6 +105,7 @@ fn horizontal_and_vertical_differ_off_the_zenith() {
fn circular_polarisation_lies_between_the_linear_cases() {
// A tilt of forty five degrees is circular polarisation, whose
// coefficients are the mean of the two linear cases.
// 四十五度倾角是圆极化,其系数是两种线极化情形的均值。
let horizontal = rain_coefficients(12.0e9, HORIZONTAL, THIRTY_DEGREES).expect("valid");
let vertical = rain_coefficients(12.0e9, VERTICAL, THIRTY_DEGREES).expect("valid");
let circular =
@ -98,6 +118,7 @@ fn circular_polarisation_lies_between_the_linear_cases() {
fn the_coefficients_are_smooth_across_the_band() {
// The curve fits are continuous, so a small change in frequency gives a
// small change in the coefficients.
// 各曲线拟合是连续的,故频率的小变化给出系数的小变化。
let mut previous = rain_coefficients(10.0e9, HORIZONTAL, ZENITH).expect("valid");
for tenth in 101..=200 {
let frequency = tenth as f64 * 0.1e9;
@ -128,6 +149,7 @@ fn specific_attenuation_follows_the_power_law() {
let (k, alpha) = rain_coefficients(frequency, HORIZONTAL, THIRTY_DEGREES).expect("valid");
assert_relative(specific, k * rain_rate.powf(alpha), "specific attenuation");
// At 12 GHz, thirty degrees elevation, and 20 mm/h this is 0.808 dB/km.
// 在 12 GHz、三十度仰角与 20 mm/h 下这是 0.808 dB/km。
assert!(
(specific - 0.808_128_772_7).abs() < 1e-6,
"specific {specific}"
@ -198,6 +220,9 @@ fn the_reduction_factors_are_physical() {
// vertical adjustment is the ratio of two path estimates and can exceed
// one when the path crosses the rain layer at a shallow angle, which is
// the case Recommendation P.618 allows for.
// 水平缩减因子计入降雨并未填满路径的整个水平范围,故它至多为一。垂直调整是两个
// 路径估计之比,可在路径以浅角穿越雨层时超过一,这正是建议书 P.618 所容许的
// 情形。
let result = rain_attenuation(
20.0,
12.0e9,
@ -245,6 +270,8 @@ fn attenuation_grows_as_the_elevation_falls() {
fn a_heavy_rain_event_gives_a_significant_attenuation() {
// A 20 mm/h rain cell at 12 GHz and thirty degrees elevation gives a few
// decibels, which is the order of magnitude a Ku band link must budget.
// 12 GHz、三十度仰角下 20 mm/h 的雨胞给出几分贝,这是 Ku 频段链路必须预算的量
// 级。
let result = rain_attenuation(
20.0,
12.0e9,
@ -335,6 +362,7 @@ fn an_elevation_at_the_horizon_is_rejected() {
fn the_latitude_enters_through_the_vertical_adjustment() {
// Below thirty six degrees the latitude term is nonzero, so the same
// geometry gives a different result at two latitudes.
// 三十六度以下纬度项非零,故同一几何在两个纬度处给出不同结果。
let temperate = rain_attenuation(
20.0,
12.0e9,

View File

@ -1,4 +1,6 @@
//! Tests for the Doppler shift.
//!
//! 多普勒频移的测试。
use apsis_doppler::{
DopplerShift, SPEED_OF_LIGHT, acquisition_span, angle_from_range_rate, classical_shift,
@ -12,16 +14,19 @@ const TOLERANCE: f64 = 1e-9;
#[test]
fn range_rate_is_the_projection_of_velocity_on_the_line_of_sight() {
// Moving away along the line of sight.
// 沿视线远离。
assert_eq!(
range_rate_from_velocity(Vec3::new(1_000.0, 0.0, 0.0), Vec3::new(10.0, 0.0, 0.0)),
Some(10.0)
);
// Moving across the line of sight gives no range rate.
// 横穿视线不给出距离变化率。
assert_eq!(
range_rate_from_velocity(Vec3::new(1_000.0, 0.0, 0.0), Vec3::new(0.0, 10.0, 0.0)),
Some(0.0)
);
// Approaching along the line of sight.
// 沿视线接近。
assert_eq!(
range_rate_from_velocity(Vec3::new(1_000.0, 0.0, 0.0), Vec3::new(-10.0, 0.0, 0.0)),
Some(-10.0)
@ -32,10 +37,12 @@ fn range_rate_is_the_projection_of_velocity_on_the_line_of_sight() {
fn range_rate_uses_the_magnitude_of_the_separation() {
// The velocity is doubled but the separation is also doubled, so the
// projection along the diagonal works out to the same fraction.
// 速度加倍但间距也加倍,故沿对角线的投影算得相同比例。
let rate = range_rate_from_velocity(Vec3::new(3.0, 4.0, 0.0), Vec3::new(6.0, 8.0, 0.0))
.expect("non-zero separation");
// The unit line of sight is (0.6, 0.8, 0), so the rate is
// 0.6 * 6 + 0.8 * 8 = 10.
// 单位视线为 (0.6, 0.8, 0),故变化率为 0.6 * 6 + 0.8 * 8 = 10。
assert!((rate - 10.0).abs() < TOLERANCE);
}
@ -69,6 +76,7 @@ fn the_classical_shift_is_negative_for_a_receding_terminal() {
#[test]
fn a_known_range_rate_gives_a_known_shift() {
// A range rate of 7 km/s at 12 GHz gives about 280.2 kHz.
// 12 GHz 下 7 km/s 的距离变化率给出约 280.2 kHz。
let shift = classical_shift(12.0e9, 7_000.0).expect("valid");
assert!((shift + 280_194.0).abs() < 1.0, "shift {shift}");
assert!((shift.abs() / 12.0e9 * 1.0e6 - 23.35).abs() < 0.01);
@ -105,13 +113,16 @@ fn the_relativistic_factor_is_one_at_rest() {
fn the_relativistic_factor_opposes_the_classical_shift() {
// For a receding terminal the factor is below one, which reinforces the
// classical redshift.
// 对远离的终端该因子小于一,这加强了经典红移。
let receding = relativistic_doppler_factor(7_000.0).expect("valid");
assert!(receding < 1.0);
// For an approaching terminal it is above one.
// 对接近的终端它大于一。
let approaching = relativistic_doppler_factor(-7_000.0).expect("valid");
assert!(approaching > 1.0);
// The two are reciprocals, because swapping the roles of the terminals
// inverts the ratio.
// 二者互为倒数,因为交换两终端的角色会反转该比值。
assert!((receding * approaching - 1.0).abs() < 1e-15);
}
@ -120,10 +131,13 @@ fn the_relativistic_factor_contains_the_classical_shift() {
// The factor's expansion begins with 1 - beta, which is the classical
// result, so the two treatments must agree to first order. They differ
// only by terms of order beta squared.
// 该因子的展开以 1 - beta 开头,即经典结果,故两种处理必须在一阶上一致。它们
// 只相差 `beta` 平方量级的项。
let beta = 7_000.0 / SPEED_OF_LIGHT;
let factor = relativistic_doppler_factor(7_000.0).expect("valid");
let difference = (factor - 1.0 + beta).abs();
// The leading difference is beta^2 / 2, about 2.7e-10.
// 主导差值为 beta^2 / 2,约 2.7e-10。
assert!(difference < beta * beta, "difference {difference}");
assert!((difference - beta * beta / 2.0).abs() < 1e-12);
}
@ -132,6 +146,7 @@ fn the_relativistic_factor_contains_the_classical_shift() {
fn the_two_treatments_agree_to_second_order() {
// The classical shift and the relativistic one differ by a fraction of
// the carrier that is of order beta squared.
// 经典频移与相对论频移相差一个 `beta` 平方量级的载波比例。
let carrier = 12.0e9;
let shift = DopplerShift::from_range_rate(carrier, 7_000.0).expect("valid");
let classical = shift.shift();
@ -144,6 +159,8 @@ fn the_two_treatments_agree_to_second_order() {
);
// The absolute difference is the second order term, about 3.3 Hz at
// 12 GHz: tiny compared with the 280 kHz classical shift, but real.
// 绝对差是二阶项,在 12 GHz 处约 3.3 Hz:与 280 kHz 的经典频移相比很小,但真实
// 存在。
assert!(
(relativistic - classical).abs() > 1.0,
"difference {}",
@ -197,10 +214,12 @@ fn the_relativistic_shift_is_close_to_the_classical_one() {
let classical = shift.shift();
let total = shift.shift_including_relativity().expect("valid");
// Both are negative for a receding terminal and agree in sign.
// 对远离的终端二者都为负,符号一致。
assert!(classical < 0.0);
assert!(total < 0.0);
// The second order difference is a few hertz on a shift of a few
// hundred kilohertz.
// 二阶差在几百千赫兹的频移上是几赫兹。
assert!((total - classical).abs() > 1.0);
assert!((total - classical).abs() < 10.0);
}
@ -217,6 +236,7 @@ fn the_shift_rate_follows_the_range_acceleration() {
let shift = DopplerShift::from_range_rate(12.0e9, 1_000.0).expect("valid");
// A range acceleration of 10 m/s^2 changes the shift by
// 12 GHz * 10 / c, about 0.4 Hz per second.
// 10 m/s^2 的距离加速度使频移改变 12 GHz * 10 / c,约每秒 0.4 Hz。
let rate = shift.shift_rate(10.0).expect("valid");
assert!((rate + 400.3).abs() < 0.1, "rate {rate}");
assert_eq!(shift.shift_rate(f64::NAN), None);
@ -226,12 +246,15 @@ fn the_shift_rate_follows_the_range_acceleration() {
fn the_angle_from_a_range_rate_is_the_angle_to_the_velocity() {
let velocity = Vec3::new(0.0, 1_000.0, 0.0);
// The full speed along the line of sight is zero degrees.
// 沿视线的全速对应零度。
let along = angle_from_range_rate(velocity, 1_000.0).expect("valid");
assert!(along.abs() < 1e-9);
// The full speed across the line of sight is a right angle.
// 横穿视线的全速对应直角。
let across = angle_from_range_rate(velocity, 0.0).expect("valid");
assert!((across - PI / 2.0).abs() < TOLERANCE);
// The opposite direction is a straight angle.
// 相反方向是平角。
let opposite = angle_from_range_rate(velocity, -1_000.0).expect("valid");
assert!((opposite - PI).abs() < 1e-9);
}
@ -248,6 +271,7 @@ fn the_maximum_shift_covers_the_line_of_sight_case() {
assert!((shift - 12.0e9 * 7_000.0 / SPEED_OF_LIGHT).abs() < 1e-3);
// A range rate beyond the speed is not physical, so the speed must be
// non-negative and finite.
// 超过该速度的距离变化率不物理,故速度必须非负且有限。
assert_eq!(maximum_shift(12.0e9, -1.0), None);
assert_eq!(maximum_shift(-1.0, 7_000.0), None);
assert_eq!(maximum_shift(12.0e9, f64::INFINITY), None);
@ -259,6 +283,7 @@ fn the_acquisition_span_is_twice_the_maximum_shift() {
let maximum = maximum_shift(12.0e9, 7_000.0).expect("valid");
assert!((span - 2.0 * maximum).abs() < 1e-3);
// For a low Earth orbit at 12 GHz the span is about 560 kHz.
// 对 12 GHz 下的低轨,该跨度为约 560 kHz。
assert!((span - 560_388.0).abs() < 2.0, "span {span}");
}

View File

@ -1,4 +1,6 @@
//! Tests for carrier to interference computation.
//!
//! 载干比计算的测试。
use apsis_interference::{
BOLTZMANN, Decibel, Interferer, Power, carrier_to_interference, carrier_to_interference_single,
@ -9,11 +11,15 @@ use apsis_interference::{
const TOLERANCE: f64 = 1e-9;
/// A wanted carrier at a convenient level.
///
/// 一个处于便利电平的有用载波。
fn carrier() -> Power {
Power::from_dbw(-130.0).expect("finite")
}
/// An interferer at the given level relative to the carrier.
///
/// 一个相对载波处于给定电平的干扰源。
fn interferer_at(decibels_below: f64) -> Interferer {
Interferer::new(Power::from_dbw(-130.0 - decibels_below).expect("finite"))
}
@ -21,6 +27,7 @@ fn interferer_at(decibels_below: f64) -> Interferer {
#[test]
fn a_single_interferer_gives_the_expected_ratio() {
// An interferer ten decibels below the carrier gives a ratio of 10 dB.
// 比载波低十分贝的干扰源给出 10 dB 的比。
let ratio = carrier_to_interference_single(carrier(), interferer_at(10.0)).expect("valid");
assert!((ratio.db() - 10.0).abs() < TOLERANCE, "{}", ratio.db());
}
@ -41,6 +48,7 @@ fn an_interferer_above_the_carrier_gives_a_negative_ratio() {
fn interfering_powers_add() {
// Two equal interferers give twice the power of one, which is three
// decibels above one alone.
// 两个相等干扰源给出的功率是单个的两倍,即比单个高三分贝。
let one = total_interference(&[interferer_at(10.0)]).expect("valid");
let two = total_interference(&[interferer_at(10.0), interferer_at(10.0)]).expect("valid");
assert!(
@ -54,6 +62,7 @@ fn an_empty_interferer_list_has_no_power() {
let total = total_interference(&[]).expect("empty is valid");
assert_eq!(total.watts(), 0.0);
// With no interference there is no ratio to compute.
// 没有干扰时没有可比。
assert_eq!(carrier_to_interference(carrier(), &[]), None);
}
@ -61,6 +70,7 @@ fn an_empty_interferer_list_has_no_power() {
fn the_ratio_combines_multiple_interferers() {
// One interferer at 10 dB below gives 10 dB. Adding a second at the
// same level halves the ratio, which is three decibels less.
// 一个低 10 dB 的干扰源给出 10 dB。再加一个相同电平的使该比减半,即少三分贝。
let single = carrier_to_interference(carrier(), &[interferer_at(10.0)]).expect("valid");
let pair = carrier_to_interference(carrier(), &[interferer_at(10.0), interferer_at(10.0)])
.expect("valid");
@ -76,6 +86,7 @@ fn the_ratio_combines_multiple_interferers() {
fn a_dominant_interferer_hides_the_others() {
// An interferer twenty decibels weaker than the carrier contributes
// almost nothing to the total, so it barely changes the ratio.
// 比载波弱二十分贝的干扰源对总量几乎无贡献,故几乎不改变该比。
let strong = carrier_to_interference(carrier(), &[interferer_at(10.0)]).expect("valid");
let with_weak = carrier_to_interference(carrier(), &[interferer_at(10.0), interferer_at(30.0)])
.expect("valid");
@ -99,6 +110,7 @@ fn invalid_inputs_are_rejected() {
assert_eq!(total_interference(&[negative]), None);
// A zero interferer gives an infinite ratio, which has no finite
// decibel value here.
// 零干扰源给出无穷的比,此处没有有限的分贝值。
let zero_interferer = Interferer::new(Power::from_watts(0.0));
assert_eq!(carrier_to_interference(carrier(), &[zero_interferer]), None);
}
@ -119,6 +131,7 @@ fn combining_two_equal_ratios_lowers_the_result_by_three_decibels() {
fn combining_a_small_ratio_with_a_large_one_gives_the_small_one() {
// A carrier to noise ratio of 30 dB alongside a carrier to interference
// ratio of 5 dB is dominated by the interference.
// 30 dB 的载噪比与 5 dB 的载干比并存时,由干扰主导。
let combined = combine(Decibel::from_db(30.0), Decibel::from_db(5.0)).expect("valid");
assert!(combined.db() < 5.0);
assert!(
@ -164,6 +177,7 @@ fn the_interference_to_noise_ratio_compares_two_powers() {
let noise = Power::from_dbw(-130.0).expect("finite");
let ratio = interference_to_noise_ratio(interference, noise).expect("valid");
// The interference is ten decibels below the noise.
// 干扰比噪声低十分贝。
assert!((ratio.db() + 10.0).abs() < TOLERANCE);
}
@ -195,6 +209,7 @@ fn the_equivalent_noise_temperature_follows_the_boltzmann_relation() {
let expected = interference.watts() / (BOLTZMANN * bandwidth);
assert!((temperature - expected).abs() < 1e-9);
// The interference is 1e-14 W, so the temperature is about 20 K.
// 干扰为 1e-14 W,故温度约为 20 K。
assert!(
temperature > 15.0 && temperature < 25.0,
"temperature {temperature}"
@ -209,6 +224,7 @@ fn the_equivalent_temperature_grows_with_the_interfering_power() {
.expect("valid");
assert!(high > low);
// Ten decibels of power is ten times the temperature.
// 十分贝的功率是温度的十倍。
assert!((high / low - 10.0).abs() < 1e-6);
}
@ -247,6 +263,7 @@ fn a_summary_reports_the_carrier_and_the_interference() {
assert!((summary.carrier_to_interference.db() - 10.0).abs() < TOLERANCE);
// The combined ratio is below both the carrier to noise and the carrier
// to interference ratio.
// 合并之比低于载噪比与载干比二者。
let combined = summary
.carrier_to_noise_plus_interference
.expect("noise was given");

View File

@ -3,6 +3,11 @@
//! The expected values are computed by hand from the link equation and
//! recorded here so that a change to any part of the chain shows up as a
//! failing assertion rather than as a silently different budget.
//!
//! 链路预算的测试。
//!
//! 期望值由手工从链路方程计算并记录在此,使该链任何部分的改动都表现为断言失败,
//! 而非无声地不同的预算。
use apsis_link::{
Decibel, Gain, LinkBudget, LinkGeometry, NoiseTemperature, Power, carrier_to_noise_density,
@ -13,6 +18,9 @@ const TOLERANCE: f64 = 1e-9;
/// A representative Ku band downlink: a ten watt transmitter, a three meter
/// ground station, a 36 MHz transponder, and a 150 K system temperature.
///
/// 一个有代表性的 Ku 频段下行:十瓦发射机、三米地面站、36 MHz 转发器,以及
/// 150 K 的系统温度。
fn downlink() -> LinkBudget {
LinkBudget {
transmit_power: Power::from_dbw(10.0).expect("finite"),
@ -33,6 +41,7 @@ fn downlink() -> LinkBudget {
fn eirp_sums_the_power_gain_and_loss() {
let budget = downlink();
// 10 dBW + 30 dBi - 1 dB = 39 dBW.
// 10 dBW + 30 dBi - 1 dB = 39 dBW。
let eirp = budget.eirp().expect("valid budget");
assert!((eirp.dbw().expect("positive") - 39.0).abs() < TOLERANCE);
}
@ -43,6 +52,7 @@ fn the_path_loss_matches_the_closed_form() {
let loss = budget.geometry.path_loss().expect("valid geometry");
// 4 pi d / lambda with d = 38 000 km and lambda = 0.02498 m gives
// 205.627 dB.
// 4 pi d / lambda,取 d = 38 000 km、lambda = 0.02498 m,给出 205.627 dB。
assert!(
(loss.db() - 205.627_080_075_172_1).abs() < 1e-9,
"{}",
@ -55,6 +65,7 @@ fn the_carrier_power_matches_the_hand_computation() {
let budget = downlink();
let carrier = budget.received_power().expect("valid budget");
// 39 dBW - 205.627 dB + 35 dBi - 0.5 dB = -132.127 dBW.
// 39 dBW - 205.627 dB + 35 dBi - 0.5 dB = -132.127 dBW。
assert!(
(carrier.dbw().expect("positive") + 132.127_080_075_172_1).abs() < 1e-9,
"{}",
@ -69,6 +80,7 @@ fn the_noise_density_is_the_boltzmann_constant_times_temperature() {
let expected = apsis_link::BOLTZMANN * 150.0;
assert!((density - expected).abs() < 1e-40);
// In decibels this is -206.838 dBW/Hz.
// 以分贝表示为 -206.838 dBW/Hz。
let db = 10.0 * density.log10();
assert!((db + 206.838_254_582_660_85).abs() < 1e-9, "{db}");
}
@ -130,8 +142,10 @@ fn a_wider_bandwidth_lowers_the_carrier_to_noise() {
let narrow_ratio = narrow.carrier_to_noise().expect("valid");
let wide_ratio = wide.carrier_to_noise().expect("valid");
// A factor of ten in bandwidth is ten decibels.
// 带宽十倍即十分贝。
assert!((narrow_ratio.db() - wide_ratio.db() - 10.0).abs() < 1e-9);
// The carrier to noise density ratio does not depend on bandwidth.
// 载噪密度比不依赖于带宽。
assert_eq!(
narrow.carrier_to_noise_density(),
wide.carrier_to_noise_density()
@ -154,6 +168,7 @@ fn added_noise_raises_the_system_temperature() {
.expect("valid temperature");
assert_eq!(warmed.system_temperature.kelvin(), 200.0);
// The carrier to noise density ratio falls by 10 log10(200 / 150).
// 载噪密度比下降 10 log10(200 / 150)。
let before = budget.carrier_to_noise_density().expect("valid");
let after = warmed.carrier_to_noise_density().expect("valid");
let expected_drop = 10.0 * (200.0_f64 / 150.0).log10();
@ -215,11 +230,13 @@ fn a_margin_reports_whether_the_link_closes() {
assert!(margin.closes());
// A link that exactly meets the requirement closes with zero margin.
// 恰好满足要求的链路以零余量闭合。
let exact = link_margin(required, required);
assert_eq!(exact.margin().db(), 0.0);
assert!(exact.closes());
// A link below the requirement does not close.
// 低于要求的链路不闭合。
let short = link_margin(Decibel::from_db(65.0), required);
assert!(!short.closes());
assert!((short.margin().db() + 5.0).abs() < TOLERANCE);
@ -234,6 +251,7 @@ fn the_gain_to_temperature_ratio_uses_reciprocal_kelvin() {
assert!((ratio - expected).abs() < 1e-9);
// In decibels this is 35 - 10 log10(150), which is 13.239087 dB/K.
// 以分贝表示为 35 - 10 log10(150),即 13.239087 dB/K。
let db = 10.0 * ratio.log10();
assert!((db - 13.239_087_409_443_187).abs() < 1e-9, "{db}");
}
@ -245,6 +263,7 @@ fn a_hotter_receiver_lowers_the_gain_to_temperature_ratio() {
let hot = gain_to_temperature(gain, NoiseTemperature::from_kelvin(200.0)).expect("valid");
assert!(cold > hot);
// Doubling the temperature halves the ratio, which is three decibels.
// 温度加倍使该比减半,即三分贝。
assert!((10.0 * (cold / hot).log10() - 3.010_299_956_639_812).abs() < 1e-9);
}
@ -266,6 +285,8 @@ fn a_budget_can_be_used_with_a_computed_antenna_pattern() {
// The receive gain of a three meter dish at 12 GHz, rounded to a single
// decibel, is used here to confirm the budget accepts a gain derived
// from the antenna layer.
// 此处使用 12 GHz 下三米天线的接收增益(舍入到一分贝),以确认预算接受由天线层
// 导出的增益。
use apsis_link::ParabolicAperture;
let dish = ParabolicAperture {
diameter: 3.0,

View File

@ -1,4 +1,6 @@
//! Tests for the noise cascade.
//!
//! 噪声级联的测试。
use apsis_noise::{
NoiseTemperature, Stage, cascade_noise_figure_db, cascade_temperature,
@ -9,6 +11,8 @@ use apsis_rf::Gain;
const TOLERANCE: f64 = 1e-9;
/// A stage that adds the given temperature with the given gain in decibels.
///
/// 一级,以给定分贝增益加入给定温度。
fn stage(temperature: f64, gain_dbi: f64) -> Stage {
Stage::new(
NoiseTemperature::from_kelvin(temperature),
@ -38,6 +42,7 @@ fn an_empty_cascade_has_no_noise() {
fn the_first_stage_dominates() {
// A 100 K first stage with 20 dB of gain followed by a 1000 K second
// stage contributes the second stage divided by 100.
// 100 K 的第一级带 20 dB 增益,其后接 1000 K 的第二级,贡献第二级除以 100。
let cascade = [stage(100.0, 20.0), stage(1_000.0, 20.0)];
let total = cascade_temperature(&cascade).expect("valid stages");
assert!((total.kelvin() - 110.0).abs() < 1e-6, "{}", total.kelvin());
@ -56,6 +61,7 @@ fn a_noiseless_stage_contributes_nothing() {
#[test]
fn the_friis_formula_matches_by_hand() {
// Three stages: 50 K at 20 dB, 100 K at 10 dB, 200 K at 0 dB.
// 三级:50 K 配 20 dB、100 K 配 10 dB、200 K 配 0 dB。
let cascade = [stage(50.0, 20.0), stage(100.0, 10.0), stage(200.0, 0.0)];
let total = cascade_temperature(&cascade).expect("valid stages");
let expected = 50.0 + 100.0 / 100.0 + 200.0 / (100.0 * 10.0);
@ -67,6 +73,8 @@ fn a_lossy_component_ahead_of_the_receiver_raises_the_noise() {
// A 3 dB loss is a gain of one half, and it contributes its own
// temperature divided by the gain of the stages before it, which for a
// loss is a division by a fraction and so an increase.
// 3 dB 损耗是二分之一增益,它贡献自身温度除以其前各级的增益,而对损耗而言即除以
// 一个分数,故是增大。
let loss = Stage::new(
NoiseTemperature::from_kelvin(0.0),
Gain::from_dbi(-3.0).expect("finite"),
@ -74,6 +82,7 @@ fn a_lossy_component_ahead_of_the_receiver_raises_the_noise() {
let receiver = stage(100.0, 30.0);
let total = cascade_temperature(&[loss, receiver]).expect("valid stages");
// The receiver contributes 100 / 0.5012 = 199.5 K.
// 接收机贡献 100 / 0.5012 = 199.5 K。
assert!(
total.kelvin() > 199.0 && total.kelvin() < 200.0,
"{}",
@ -105,6 +114,8 @@ fn a_known_receiver_chain_gives_a_plausible_figure() {
// A low noise amplifier at 1 dB and 30 dB gain followed by a mixer at
// 10 dB and 0 dB gain. The cascade figure is close to the first stage
// plus the second divided by the first stage's gain.
// 1 dB、30 dB 增益的低噪声放大器,其后接 10 dB、0 dB 增益的混频器。级联系数接近
// 第一级加上第二级除以第一级增益。
let low_noise = Stage::from_noise_figure(1.0, 30.0).expect("valid");
let mixer = Stage::from_noise_figure(10.0, 0.0).expect("valid");
let figure = cascade_noise_figure_db(&[low_noise, mixer]).expect("valid stages");
@ -143,6 +154,7 @@ fn the_conversion_helper_matches_the_method() {
fn gain_ordering_matters() {
// Placing the noisy stage first gives a worse cascade than placing it
// second, which is why a low noise amplifier goes at the front.
// 把噪声大的级放在最前给出的级联比放在第二差,这正是低噪声放大器置于前端的原因。
let noisy = stage(500.0, 10.0);
let quiet = stage(20.0, 20.0);
let quiet_first = cascade_temperature(&[quiet, noisy]).expect("valid");

View File

@ -1,4 +1,6 @@
//! Tests for noise temperature and thermal noise power.
//!
//! 噪声温度与热噪声功率的测试。
use apsis_core::constant::BOLTZMANN;
use apsis_noise::{NoiseTemperature, REFERENCE_TEMPERATURE_K, thermal_noise_power};
@ -24,6 +26,8 @@ fn the_reference_temperature_is_a_noise_figure_of_three_decibels() {
// noise of a source at the reference temperature, so a stage whose
// excess noise equals the reference temperature has a factor of two,
// which is 3.01 dB.
// 噪声系数定义为总噪声与参考温度下源的噪声之比,故超额噪声等于参考温度的级其
// 因子为二,即 3.01 dB。
let value = NoiseTemperature::from_kelvin(REFERENCE_TEMPERATURE_K);
assert!((value.noise_factor() - 2.0).abs() < TOLERANCE);
assert!((value.noise_figure_db().expect("valid") - 3.010_299_956_639_812).abs() < 1e-12);
@ -53,6 +57,7 @@ fn noise_factor_and_temperature_round_trip() {
fn a_known_noise_figure_gives_a_known_temperature() {
// A noise figure of 1 dB is a factor of 1.2589, so the temperature is
// 290 * 0.2589, about 75.1 K.
// 1 dB 的噪声系数其因子为 1.2589,故温度为 290 * 0.2589,约 75.1 K。
let temperature = NoiseTemperature::from_noise_figure_db(1.0).expect("finite");
assert!(
(temperature.kelvin() - 75.09).abs() < 0.01,
@ -97,6 +102,7 @@ fn thermal_noise_power_follows_the_boltzmann_relation() {
#[test]
fn a_one_hertz_bandwidth_at_290_kelvin_is_minus_204_dbw() {
// k T B with T = 290 K and B = 1 Hz is 4.0e-21 W, which is -203.98 dBW.
// k T B,取 T = 290 K、B = 1 Hz,为 4.0e-21 W,即 -203.98 dBW。
let power = thermal_noise_power(NoiseTemperature::from_kelvin(290.0), 1.0).expect("valid");
assert!(
(power.dbw().expect("positive") + 203.98).abs() < 0.01,
@ -110,6 +116,8 @@ fn a_thirty_six_megahertz_transponder_is_about_minus_128_dbw() {
// k T B with T = 290 K and B = 36 MHz is 1.44e-13 W, which is
// -128.41 dBW. A one hertz bandwidth at the same temperature is
// -203.98 dBW, and 36 MHz is 75.56 dB above one hertz.
// k T B,取 T = 290 K、B = 36 MHz,为 1.44e-13 W,即 -128.41 dBW。同一温度下
// 一赫兹带宽为 -203.98 dBW,而 36 MHz 比一赫兹高 75.56 dB。
let power = thermal_noise_power(NoiseTemperature::from_kelvin(290.0), 36.0e6).expect("valid");
let dbw = power.dbw().expect("positive");
assert!((dbw + 128.41).abs() < 0.01, "{dbw} dBW");
@ -123,6 +131,7 @@ fn noise_power_scales_with_temperature_and_bandwidth() {
let double_temperature =
thermal_noise_power(NoiseTemperature::from_kelvin(200.0), 1.0).expect("valid");
// Each doubling of either factor adds three decibels.
// 任一因子每加倍一次加三分贝。
assert!(
(hundred_hertz.dbw().expect("positive") - one_hertz.dbw().expect("positive") - 20.0).abs()
< 1e-9

View File

@ -1,4 +1,6 @@
//! Tests for the decibel scale.
//!
//! 分贝尺度的测试。
use apsis_rf::Decibel;
@ -10,6 +12,7 @@ fn unity_and_doubling_have_the_expected_readings() {
assert!((Decibel::UNITY.ratio() - 1.0).abs() < TOLERANCE);
// Three decibels is a ratio of 1.9953, not exactly two, which is the
// reason the doubling constant is an approximation in practice.
// 三分贝是 1.9953 的比而非恰好二,这正是加倍常量在实践中是近似的原因。
assert!((Decibel::DOUBLING.db() - 3.010_299_956_639_812).abs() < TOLERANCE);
assert!((Decibel::DOUBLING.ratio() - 2.0).abs() < TOLERANCE);
}
@ -35,6 +38,7 @@ fn a_ratio_of_ten_is_ten_decibels() {
fn amplitude_ratios_use_twenty_per_decade() {
// A voltage ratio of ten is twenty decibels, because power is
// proportional to the square of the amplitude.
// 电压比十即二十分贝,因为功率与幅度的平方成正比。
let value = Decibel::from_amplitude_ratio(10.0).expect("positive");
assert!((value.db() - 20.0).abs() < TOLERANCE);
assert!((value.amplitude_ratio() - 10.0).abs() < 1e-9);
@ -52,10 +56,12 @@ fn non_positive_ratios_are_rejected() {
#[test]
fn addition_multiplies_the_ratio() {
// The exact doubling constant added to itself quadruples the ratio.
// 精确的加倍常量自加使其比变为四倍。
let total = Decibel::DOUBLING + Decibel::DOUBLING;
assert!((total.ratio() - 4.0).abs() < 1e-9);
// Two ten decibel gains make a gain of one hundred.
// 两个十分贝增益合成一百倍增益。
let decades = Decibel::from_db(10.0) + Decibel::from_db(10.0);
assert!((decades.db() - 20.0).abs() < TOLERANCE);
assert!((decades.ratio() - 100.0).abs() < 1e-9);

View File

@ -1,4 +1,6 @@
//! Tests for free space propagation.
//!
//! 自由空间传播的测试。
use apsis_core::constant::SPEED_OF_LIGHT;
use apsis_rf::{Gain, free_space_path_loss, free_space_received_ratio, wavelength};
@ -9,8 +11,10 @@ const TOLERANCE: f64 = 1e-9;
fn wavelength_is_the_speed_of_light_over_frequency() {
assert!((wavelength(SPEED_OF_LIGHT).expect("valid") - 1.0).abs() < TOLERANCE);
// A frequency of 300 MHz has a wavelength of about one meter.
// 300 MHz 的频率波长约为米。
assert!((wavelength(300.0e6).expect("valid") - 0.999_308).abs() < 1e-5);
// The same relation holds for a microwave frequency.
// 同一关系对微波频率也成立。
assert!((wavelength(12.0e9).expect("valid") - 0.024_982_7).abs() < 1e-6);
}
@ -26,6 +30,7 @@ fn wavelength_rejects_invalid_frequencies() {
fn path_loss_matches_the_closed_form() {
// Attenuation over one meter at one meter wavelength: the ratio is
// (4 pi)^2, about 21.98 dB.
// 一米波长下一米距离的衰减:比值为 (4 pi)^2,约 21.98 dB。
let loss = free_space_path_loss(1.0, SPEED_OF_LIGHT).expect("valid arguments");
let expected = 10.0 * (4.0 * core::f64::consts::PI).powi(2).log10();
assert!((loss.db() - expected).abs() < 1e-9, "loss {}", loss.db());
@ -36,12 +41,16 @@ fn path_loss_matches_the_closed_form() {
fn path_loss_matches_the_engineering_form() {
// The form used in link budgets, with distance in kilometers and
// frequency in megahertz,
// 链路预算中所用的形式,距离以千米、频率以兆赫兹计,
//
// L = 32.44 + 20 log10(d_km) + 20 log10(f_MHz)
// L = 32.44 + 20 log10(d_km) + 20 log10(f_MHz)
//
// has a constant that follows from the speed of light rounded to
// 299 792 458 m/s, and agrees with the expression used here to the
// precision of that constant.
// 其常数由舍入到 299 792 458 m/s 的光速得出,并在该常量的精度内与此处所用的
// 表达式一致。
let distance_km = 1_000.0;
let frequency_mhz = 12_000.0;
let loss = free_space_path_loss(distance_km * 1_000.0, frequency_mhz * 1.0e6)
@ -74,6 +83,7 @@ fn a_higher_frequency_increases_the_loss() {
fn a_zero_or_negative_distance_is_rejected() {
// A zero distance is outside the far field range where the expression
// applies, and would give an infinite loss in decibels.
// 零距离在该表达式所适用的远场范围之外,会给出以分贝计的无穷损耗。
assert_eq!(free_space_path_loss(0.0, 1.0e9), None);
assert_eq!(free_space_path_loss(-1.0, 1.0e9), None);
}
@ -105,6 +115,9 @@ fn a_large_receive_gain_can_exceed_the_spreading_loss() {
// ratio. This is the near field of a large antenna pair and is the
// reason a link budget cannot be read without also checking the
// distance regime.
// 两个 40 dBi 的天线,在十米与 12 GHz 下。那里的扩散损耗约为 74 dB,故 80 dB 的
// 合成增益留下正的比值。这是一对大天线的近场,也正是链路预算不能在不检查距离
// 区间的情况下阅读的原因。
let ratio = free_space_received_ratio(
10.0,
12.0e9,

View File

@ -1,4 +1,6 @@
//! Tests for antenna gain.
//!
//! 天线增益的测试。
use apsis_rf::{DIPOLE_OVER_ISOTROPIC_DB, Gain, HALF_WAVE_DIPOLE_RATIO};
use core::f64::consts::PI;
@ -13,6 +15,7 @@ fn named_gains_match_their_definitions() {
// The linear ratio and the decibel offset are derived from each other,
// so the two must agree.
// 线性比值与分贝偏移由彼此导出,故二者必须一致。
assert!((Gain::HALF_WAVE_DIPOLE.ratio() - HALF_WAVE_DIPOLE_RATIO).abs() < 1e-15);
assert!(
(Gain::HALF_WAVE_DIPOLE.dbi().expect("positive") - DIPOLE_OVER_ISOTROPIC_DB).abs() < 1e-12
@ -66,6 +69,7 @@ fn non_finite_readings_are_rejected() {
#[test]
fn effective_aperture_follows_the_standard_relation() {
// A gain of one has an aperture of lambda^2 / (4 pi).
// 增益为一的孔径为 lambda^2 / (4 pi)。
let gain = Gain::ISOTROPIC;
let aperture = gain.effective_aperture(0.3).expect("valid wavelength");
assert!((aperture - 0.3 * 0.3 / (4.0 * PI)).abs() < TOLERANCE);
@ -78,6 +82,7 @@ fn a_larger_wavelength_needs_a_larger_aperture() {
let large = gain.effective_aperture(0.1).expect("valid");
assert!(large > small);
// The aperture grows with the square of the wavelength.
// 孔径随波长的平方增大。
assert!((large / small - 100.0).abs() < 1e-6);
}

View File

@ -1,4 +1,6 @@
//! Tests for power levels.
//!
//! 功率电平的测试。
use apsis_rf::{Decibel, Power};
@ -11,6 +13,7 @@ fn reference_powers_match_their_definitions() {
assert_eq!(Power::WATT.dbw(), Some(0.0));
assert_eq!(Power::MILLIWATT.dbm(), Some(0.0));
// One watt is thirty decibel milliwatts.
// 一瓦是三十毫瓦分贝。
assert!((Power::WATT.dbm().expect("positive") - 30.0).abs() < TOLERANCE);
}
@ -41,11 +44,13 @@ fn dbw_and_dbm_differ_by_thirty() {
#[test]
fn known_power_levels_convert_correctly() {
// Ten watts is ten decibel watts and forty decibel milliwatts.
// 十瓦是十分贝瓦与四十毫瓦分贝。
let ten_watts = Power::from_watts(10.0);
assert!((ten_watts.dbw().expect("positive") - 10.0).abs() < 1e-12);
assert!((ten_watts.dbm().expect("positive") - 40.0).abs() < 1e-12);
// One milliwatt is one milliwatt.
// 一毫瓦即一毫瓦。
assert!((Power::from_milliwatts(1.0).watts() - 1.0e-3).abs() < 1e-18);
assert!((Power::from_milliwatts(1.0).milliwatts() - 1.0).abs() < 1e-12);
}
@ -73,6 +78,7 @@ fn a_ratio_between_powers_is_a_decibel() {
assert!((ratio.db() - 20.0).abs() < 1e-12);
// The reverse ratio is the negative.
// 反向之比是负数。
let inverse = low.ratio_in_decibels(high).expect("both are positive");
assert!((inverse.db() + 20.0).abs() < 1e-12);
}
@ -113,5 +119,6 @@ fn validity_is_reported() {
fn display_prefers_decibel_watts() {
assert_eq!(Power::from_watts(1.0).to_string(), "0 dBW");
// A zero power has no decibel form, so the linear value is shown.
// 零功率没有分贝形式,故显示线性值。
assert_eq!(Power::from_watts(0.0).to_string(), "0 W");
}