docs: bilingual comments for the rf tests
This commit is contained in:
parent
1dd77d6d6e
commit
68735b2075
|
|
@ -1,9 +1,13 @@
|
||||||
//! Tests for the parabolic aperture model.
|
//! Tests for the parabolic aperture model.
|
||||||
|
//!
|
||||||
|
//! 抛物面孔径模型的测试。
|
||||||
|
|
||||||
use apsis_antenna::{ParabolicAperture, beam_width};
|
use apsis_antenna::{ParabolicAperture, beam_width};
|
||||||
use core::f64::consts::PI;
|
use core::f64::consts::PI;
|
||||||
|
|
||||||
/// A three meter dish with a typical aperture efficiency.
|
/// A three meter dish with a typical aperture efficiency.
|
||||||
|
///
|
||||||
|
/// 一面三米天线,具有典型孔径效率。
|
||||||
const DISH: ParabolicAperture = ParabolicAperture {
|
const DISH: ParabolicAperture = ParabolicAperture {
|
||||||
diameter: 3.0,
|
diameter: 3.0,
|
||||||
efficiency: 0.65,
|
efficiency: 0.65,
|
||||||
|
|
@ -20,11 +24,13 @@ fn aperture_area_follows_from_the_diameter() {
|
||||||
fn peak_gain_matches_the_closed_form() {
|
fn peak_gain_matches_the_closed_form() {
|
||||||
// At 12 GHz the wavelength is 0.02498 m, and the gain is
|
// At 12 GHz the wavelength is 0.02498 m, and the gain is
|
||||||
// eta (pi D / lambda)^2.
|
// eta (pi D / lambda)^2.
|
||||||
|
// 12 GHz 下波长为 0.02498 m,增益为 eta (pi D / lambda)^2。
|
||||||
let wavelength = 0.024_982_710_7;
|
let wavelength = 0.024_982_710_7;
|
||||||
let gain = DISH.peak_gain(wavelength).expect("valid");
|
let gain = DISH.peak_gain(wavelength).expect("valid");
|
||||||
let expected = 0.65 * (PI * 3.0 / wavelength).powi(2);
|
let expected = 0.65 * (PI * 3.0 / wavelength).powi(2);
|
||||||
assert!((gain.ratio() - expected).abs() < 1e-6);
|
assert!((gain.ratio() - expected).abs() < 1e-6);
|
||||||
// The result is about 49.66 dBi.
|
// The result is about 49.66 dBi.
|
||||||
|
// 结果约为 49.66 dBi。
|
||||||
assert!(
|
assert!(
|
||||||
(gain.dbi().expect("positive") - 49.661_764_937_398_814).abs() < 1e-9,
|
(gain.dbi().expect("positive") - 49.661_764_937_398_814).abs() < 1e-9,
|
||||||
"dBi {}",
|
"dBi {}",
|
||||||
|
|
@ -46,6 +52,7 @@ fn a_larger_dish_has_more_gain() {
|
||||||
let small_gain = small.peak_gain(wavelength).expect("valid");
|
let small_gain = small.peak_gain(wavelength).expect("valid");
|
||||||
let large_gain = large.peak_gain(wavelength).expect("valid");
|
let large_gain = large.peak_gain(wavelength).expect("valid");
|
||||||
// Doubling the diameter quadruples the gain, which is six decibels.
|
// Doubling the diameter quadruples the gain, which is six decibels.
|
||||||
|
// 直径加倍使增益变为四倍,即六分贝。
|
||||||
assert!(
|
assert!(
|
||||||
(large_gain.dbi().expect("positive") - small_gain.dbi().expect("positive") - 6.020_6).abs()
|
(large_gain.dbi().expect("positive") - small_gain.dbi().expect("positive") - 6.020_6).abs()
|
||||||
< 1e-6
|
< 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");
|
let tapered = DISH.half_power_beam_width(wavelength).expect("valid");
|
||||||
assert!((uniform - 1.02 * wavelength / 3.0).abs() < 1e-12);
|
assert!((uniform - 1.02 * wavelength / 3.0).abs() < 1e-12);
|
||||||
// A tapered illumination widens the beam.
|
// A tapered illumination widens the beam.
|
||||||
|
// 锥削照射使波束变宽。
|
||||||
assert!(tapered > uniform);
|
assert!(tapered > uniform);
|
||||||
assert!((tapered - 1.15 * wavelength / 3.0).abs() < 1e-12);
|
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() {
|
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,
|
// A 3 m dish at 12 GHz has a half power beam width near 0.0085 radians,
|
||||||
// which is about 0.49 degrees.
|
// 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");
|
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 - 0.008_494).abs() < 1e-5, "width {width}");
|
||||||
assert!((width.to_degrees() - 0.486_7).abs() < 0.001);
|
assert!((width.to_degrees() - 0.486_7).abs() < 0.001);
|
||||||
|
|
@ -134,6 +143,7 @@ fn an_invalid_aperture_produces_no_result() {
|
||||||
#[test]
|
#[test]
|
||||||
fn the_aperture_and_gain_conversions_are_consistent() {
|
fn the_aperture_and_gain_conversions_are_consistent() {
|
||||||
// The peak gain and the effective area must describe the same antenna.
|
// The peak gain and the effective area must describe the same antenna.
|
||||||
|
// 峰值增益与有效面积必须描述同一面天线。
|
||||||
let wavelength = 0.025;
|
let wavelength = 0.025;
|
||||||
let gain = DISH.peak_gain(wavelength).expect("valid");
|
let gain = DISH.peak_gain(wavelength).expect("valid");
|
||||||
let aperture = gain.effective_aperture(wavelength).expect("valid");
|
let aperture = gain.effective_aperture(wavelength).expect("valid");
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for off-axis gain patterns.
|
//! Tests for off-axis gain patterns.
|
||||||
|
//!
|
||||||
|
//! 偏轴增益方向图的测试。
|
||||||
|
|
||||||
use apsis_antenna::{
|
use apsis_antenna::{
|
||||||
ElementPattern, ParabolicAperture, Pattern, floor_angle, mask_gain, off_axis_attenuation,
|
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() {
|
fn the_half_power_angle_gives_three_decibels() {
|
||||||
// At half the beam width the off-axis angle is half of the half power
|
// 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.
|
// 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 peak = Gain::from_dbi(30.0).expect("valid");
|
||||||
let beam = 0.01;
|
let beam = 0.01;
|
||||||
let attenuation = off_axis_attenuation(peak, beam, beam / 2.0, 32.0).expect("valid arguments");
|
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;
|
let beam = 0.02;
|
||||||
// Both angles are chosen to stay on the squared angle law, well inside
|
// Both angles are chosen to stay on the squared angle law, well inside
|
||||||
// the angle at which the pattern reaches its floor.
|
// the angle at which the pattern reaches its floor.
|
||||||
|
// 两个角都选在角平方律上,远在方向图到达其底限的角度之内。
|
||||||
let first = off_axis_attenuation(peak, beam, 0.02, 60.0).expect("valid");
|
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");
|
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
|
// 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
|
// it is 48 dB. The rise of 36 dB is what a quadrupling of the
|
||||||
// attenuation ratio means in decibels.
|
// attenuation ratio means in decibels.
|
||||||
|
// 在波束宽度处衰减为 12 dB;在两倍波束宽度处为 48 dB。36 dB 的上升即衰减比变为
|
||||||
|
// 四倍在分贝上的含义。
|
||||||
assert!((first.db() - (40.0 - 12.0)).abs() < 1e-9);
|
assert!((first.db() - (40.0 - 12.0)).abs() < 1e-9);
|
||||||
assert!((second.db() - (40.0 - 48.0)).abs() < 1e-9);
|
assert!((second.db() - (40.0 - 48.0)).abs() < 1e-9);
|
||||||
let rise = first.db() - second.db();
|
let rise = first.db() - second.db();
|
||||||
|
|
@ -53,14 +60,17 @@ fn the_pattern_floors_at_the_sidelobe_level() {
|
||||||
let beam = 0.02;
|
let beam = 0.02;
|
||||||
let floor = 32.0;
|
let floor = 32.0;
|
||||||
// Far off axis the gain is the peak minus the floor.
|
// Far off axis the gain is the peak minus the floor.
|
||||||
|
// 远偏轴处增益是峰值减底限。
|
||||||
let far = off_axis_attenuation(peak, beam, PI, floor).expect("valid");
|
let far = off_axis_attenuation(peak, beam, PI, floor).expect("valid");
|
||||||
assert!((far.db() - (40.0 - floor)).abs() < TOLERANCE);
|
assert!((far.db() - (40.0 - floor)).abs() < TOLERANCE);
|
||||||
|
|
||||||
// The floor is reached at the angle the helper reports.
|
// The floor is reached at the angle the helper reports.
|
||||||
|
// 在该辅助函数所报告的角度处到达底限。
|
||||||
let angle = floor_angle(beam, floor).expect("valid");
|
let angle = floor_angle(beam, floor).expect("valid");
|
||||||
let at_floor = off_axis_attenuation(peak, beam, angle, 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);
|
assert!((at_floor.db() - (40.0 - floor)).abs() < 1e-9);
|
||||||
// Just inside that angle the gain is above the floor.
|
// Just inside that angle the gain is above the floor.
|
||||||
|
// 恰在该角之内增益高于底限。
|
||||||
let inside = off_axis_attenuation(peak, beam, angle * 0.99, floor).expect("valid");
|
let inside = off_axis_attenuation(peak, beam, angle * 0.99, floor).expect("valid");
|
||||||
assert!(inside.db() > at_floor.db());
|
assert!(inside.db() > at_floor.db());
|
||||||
}
|
}
|
||||||
|
|
@ -80,6 +90,8 @@ fn invalid_arguments_are_rejected() {
|
||||||
fn the_itu_mask_uses_the_large_aperture_branch() {
|
fn the_itu_mask_uses_the_large_aperture_branch() {
|
||||||
// For D/lambda >= 100 the pattern is 29 - 25 log10(theta) between one
|
// For D/lambda >= 100 the pattern is 29 - 25 log10(theta) between one
|
||||||
// and 36 degrees and -10 beyond.
|
// 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");
|
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 - (29.0 - 25.0 * 10.0_f64.log10())).abs() < 1e-9);
|
||||||
assert!((at_ten_degrees - 4.0).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
|
// 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
|
// to but not exactly -10 dB. The standard accepts that small step and
|
||||||
// then holds the value at -10 dB.
|
// 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");
|
let at_break = mask_gain(1_000.0, 36.0_f64.to_radians()).expect("valid");
|
||||||
assert!(
|
assert!(
|
||||||
(at_break - (29.0 - 25.0 * 36.0_f64.log10())).abs() < 1e-9,
|
(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]
|
#[test]
|
||||||
fn the_itu_mask_uses_the_small_aperture_branch() {
|
fn the_itu_mask_uses_the_small_aperture_branch() {
|
||||||
// For D/lambda < 100 the pattern starts at 32 and breaks at 48 degrees.
|
// 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");
|
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);
|
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.
|
// 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");
|
let at_break = mask_gain(50.0, 48.0_f64.to_radians()).expect("valid");
|
||||||
assert!(
|
assert!(
|
||||||
(at_break - (32.0 - 25.0 * 48.0_f64.log10())).abs() < 1e-9,
|
(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]
|
#[test]
|
||||||
fn the_itu_mask_is_capped_inside_one_degree() {
|
fn the_itu_mask_is_capped_inside_one_degree() {
|
||||||
// Below one degree the pattern does not continue to rise without bound.
|
// Below one degree the pattern does not continue to rise without bound.
|
||||||
|
// 在一度以下,方向图不会继续无界上升。
|
||||||
let peak_limit = 32.0 - 25.0 * 1.0_f64.log10();
|
let peak_limit = 32.0 - 25.0 * 1.0_f64.log10();
|
||||||
for angle_degrees in [0.0_f64, 0.01, 0.5, 0.99] {
|
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");
|
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());
|
assert!(pattern.is_valid());
|
||||||
let gain = pattern.gain_at(0.01).expect("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 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);
|
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,
|
beam_width: 0.05,
|
||||||
};
|
};
|
||||||
// The wavelength is ignored for a directly specified pattern.
|
// The wavelength is ignored for a directly specified pattern.
|
||||||
|
// 对直接指定的方向图,波长被忽略。
|
||||||
assert!(
|
assert!(
|
||||||
(direct
|
(direct
|
||||||
.peak_gain(0.025)
|
.peak_gain(0.025)
|
||||||
|
|
@ -244,6 +263,7 @@ fn the_default_pattern_is_effectively_isotropic() {
|
||||||
assert!(pattern.is_valid());
|
assert!(pattern.is_valid());
|
||||||
// With a floor at the smallest positive number the attenuation is
|
// With a floor at the smallest positive number the attenuation is
|
||||||
// negligible in every direction.
|
// negligible in every direction.
|
||||||
|
// 底限取最小正数时,各方向的衰减都可忽略。
|
||||||
let gain = pattern.gain_at(1.0).expect("valid");
|
let gain = pattern.gain_at(1.0).expect("valid");
|
||||||
assert!(gain.dbi().expect("positive").abs() < 1e-6);
|
assert!(gain.dbi().expect("positive").abs() < 1e-6);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for the ITU-R S.465 reference antenna pattern.
|
//! Tests for the ITU-R S.465 reference antenna pattern.
|
||||||
|
//!
|
||||||
|
//! ITU-R S.465 参考天线方向图的测试。
|
||||||
|
|
||||||
use apsis_antenna::{
|
use apsis_antenna::{
|
||||||
PEAK_GAIN_OFFSET_DB, diameter_over_wavelength_from_peak_gain, main_lobe_break_angle,
|
PEAK_GAIN_OFFSET_DB, diameter_over_wavelength_from_peak_gain, main_lobe_break_angle,
|
||||||
|
|
@ -10,12 +12,14 @@ const TOLERANCE: f64 = 1e-9;
|
||||||
#[test]
|
#[test]
|
||||||
fn the_peak_gain_follows_the_recommendation() {
|
fn the_peak_gain_follows_the_recommendation() {
|
||||||
// The recommendation gives 20 log10(D / lambda) + 7.7.
|
// 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] {
|
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 expected = 20.0 * ratio.log10() + PEAK_GAIN_OFFSET_DB;
|
||||||
let actual = reference_peak_gain(ratio).expect("valid ratio");
|
let actual = reference_peak_gain(ratio).expect("valid ratio");
|
||||||
assert!((actual - expected).abs() < TOLERANCE, "ratio {ratio}");
|
assert!((actual - expected).abs() < TOLERANCE, "ratio {ratio}");
|
||||||
}
|
}
|
||||||
// A ratio of one hundred gives 47.7 dBi.
|
// A ratio of one hundred gives 47.7 dBi.
|
||||||
|
// 比值一百给出 47.7 dBi。
|
||||||
assert!((reference_peak_gain(100.0).expect("valid") - 47.7).abs() < TOLERANCE);
|
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]
|
#[test]
|
||||||
fn the_main_lobe_falls_as_the_square_of_the_angle() {
|
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.
|
// 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 ratio = 100.0;
|
||||||
let peak = reference_peak_gain(ratio).expect("valid");
|
let peak = reference_peak_gain(ratio).expect("valid");
|
||||||
for angle_degrees in [0.05_f64, 0.1, 0.2, 0.3] {
|
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() {
|
fn the_pattern_matches_recorded_values() {
|
||||||
// These are the values of the recommendation for a ratio of one hundred,
|
// These are the values of the recommendation for a ratio of one hundred,
|
||||||
// computed independently from its equations.
|
// computed independently from its equations.
|
||||||
|
// 这些是该建议书对比值一百的值,由其方程独立计算。
|
||||||
let cases: [(f64, f64); 8] = [
|
let cases: [(f64, f64); 8] = [
|
||||||
(0.1, 47.45),
|
(0.1, 47.45),
|
||||||
(0.5, 41.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.
|
// a floor angle of 48 degrees.
|
||||||
// At exactly the floor angle the branch switches to the floor, so the
|
// At exactly the floor angle the branch switches to the floor, so the
|
||||||
// last value of the falling branch is approached from below.
|
// last value of the falling branch is approached from below.
|
||||||
|
// 比值五十使用小孔径分支,head 为 32,底限角为 48 度。
|
||||||
|
// 恰在底限角处该分支切换到取底限,故下降支的最后值是从下方趋近的。
|
||||||
let cases: [(f64, f64); 6] = [
|
let cases: [(f64, f64); 6] = [
|
||||||
(0.1, 41.616_900_087),
|
(0.1, 41.616_900_087),
|
||||||
(1.0, 35.429_400_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() {
|
fn the_two_branches_meet_at_the_break_angle() {
|
||||||
// The main lobe and the sidelobe region must agree at the junction, or
|
// The main lobe and the sidelobe region must agree at the junction, or
|
||||||
// the pattern would jump.
|
// the pattern would jump.
|
||||||
|
// 主瓣与旁瓣区必须在交界处一致,否则方向图会跳变。
|
||||||
for ratio in [50.0_f64, 100.0, 200.0, 500.0] {
|
for ratio in [50.0_f64, 100.0, 200.0, 500.0] {
|
||||||
let peak = reference_peak_gain(ratio).expect("valid");
|
let peak = reference_peak_gain(ratio).expect("valid");
|
||||||
let break_angle = main_lobe_break_angle(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() {
|
fn the_break_angle_shrinks_as_the_aperture_grows() {
|
||||||
// A larger antenna has a narrower main lobe, so the sidelobe region
|
// A larger antenna has a narrower main lobe, so the sidelobe region
|
||||||
// begins sooner.
|
// begins sooner.
|
||||||
|
// 更大的天线主瓣更窄,故旁瓣区开始得更早。
|
||||||
let small = main_lobe_break_angle(50.0).expect("valid");
|
let small = main_lobe_break_angle(50.0).expect("valid");
|
||||||
let medium = main_lobe_break_angle(100.0).expect("valid");
|
let medium = main_lobe_break_angle(100.0).expect("valid");
|
||||||
let large = main_lobe_break_angle(400.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() {
|
fn the_beam_width_follows_from_the_main_lobe_law() {
|
||||||
// Three decibels is reached at sqrt(3 / 2.5e-3) / (D/lambda) degrees
|
// Three decibels is reached at sqrt(3 / 2.5e-3) / (D/lambda) degrees
|
||||||
// from boresight, so the full width is twice that.
|
// from boresight, so the full width is twice that.
|
||||||
|
// 三分贝在距视轴 sqrt(3 / 2.5e-3) / (D/lambda) 度处达到,故全宽是它的两倍。
|
||||||
let ratio = 100.0;
|
let ratio = 100.0;
|
||||||
let width = reference_beam_width(ratio).expect("valid");
|
let width = reference_beam_width(ratio).expect("valid");
|
||||||
let half_degrees = (3.0_f64 / 2.5e-3).sqrt() / ratio;
|
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);
|
assert!((width - expected).abs() < 1e-15);
|
||||||
|
|
||||||
// The gain at half the beam width from boresight is three decibels down.
|
// 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 gain = reference_pattern_gain(ratio, width / 2.0).expect("valid");
|
||||||
let peak = reference_peak_gain(ratio).expect("valid");
|
let peak = reference_peak_gain(ratio).expect("valid");
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,12 @@
|
||||||
//! with a line-by-line implementation of the recommendation and are recorded
|
//! 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
|
//! here so that a change to the line tables or the formulae shows up as a
|
||||||
//! failing assertion.
|
//! failing assertion.
|
||||||
|
//!
|
||||||
|
//! 气体衰减的测试。
|
||||||
|
//!
|
||||||
|
//! 期望值是 ITU-R 建议书 P.676-12 对参考大气的值:干空气气压 1013.25 hPa、水汽
|
||||||
|
//! 密度 7.5 g/m^3、温度 288.15 K。它们由该建议书的逐线实现产生并记录在此,使对
|
||||||
|
//! 线表或公式的改动表现为断言失败。
|
||||||
|
|
||||||
use apsis_atmosphere::{
|
use apsis_atmosphere::{
|
||||||
Atmosphere, equivalent_heights, specific_attenuation, specific_attenuation_dry_air,
|
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
|
/// The implementation reproduces the reference to about twelve significant
|
||||||
/// digits, so this allows for the rounding of the recorded values.
|
/// digits, so this allows for the rounding of the recorded values.
|
||||||
|
///
|
||||||
|
/// 比衰减的容差,单位为分贝每千米。
|
||||||
|
///
|
||||||
|
/// 该实现把参考值复现到约十二位有效数字,故此值容许所记录值的舍入。
|
||||||
const TOLERANCE: f64 = 1e-6;
|
const TOLERANCE: f64 = 1e-6;
|
||||||
|
|
||||||
/// The reference atmosphere of the recommendation.
|
/// The reference atmosphere of the recommendation.
|
||||||
|
///
|
||||||
|
/// 该建议书的参考大气。
|
||||||
fn reference() -> Atmosphere {
|
fn reference() -> Atmosphere {
|
||||||
Atmosphere::REFERENCE
|
Atmosphere::REFERENCE
|
||||||
}
|
}
|
||||||
|
|
@ -42,6 +54,7 @@ fn the_water_vapour_partial_pressure_follows_the_ideal_gas_law() {
|
||||||
#[test]
|
#[test]
|
||||||
fn dry_air_attenuation_matches_the_recommendation() {
|
fn dry_air_attenuation_matches_the_recommendation() {
|
||||||
// The recorded values are from the reference implementation of P.676-12.
|
// The recorded values are from the reference implementation of P.676-12.
|
||||||
|
// 所记录的值来自 P.676-12 的参考实现。
|
||||||
let cases = [
|
let cases = [
|
||||||
(1.0, 0.005_388_658_168),
|
(1.0, 0.005_388_658_168),
|
||||||
(10.0, 0.008_224_416_703),
|
(10.0, 0.008_224_416_703),
|
||||||
|
|
@ -93,11 +106,13 @@ fn the_two_contributions_add() {
|
||||||
fn the_oxygen_complex_dominates_near_sixty_gigahertz() {
|
fn the_oxygen_complex_dominates_near_sixty_gigahertz() {
|
||||||
// The 60 GHz complex is a set of more than thirty lines, so the dry air
|
// 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.
|
// term is many orders of magnitude above the water vapour term there.
|
||||||
|
// 60 GHz 复合体是三十多条线的集合,故那里的干空气项比水汽项高许多个数量级。
|
||||||
let frequency = 60.0;
|
let frequency = 60.0;
|
||||||
let dry = specific_attenuation_dry_air(frequency, reference()).expect("valid");
|
let dry = specific_attenuation_dry_air(frequency, reference()).expect("valid");
|
||||||
let wet = specific_attenuation_water_vapour(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
|
// The dry air value is about 14.6 dB/km against 0.15 dB/km for water
|
||||||
// vapour, a ratio near one hundred.
|
// vapour, a ratio near one hundred.
|
||||||
|
// 干空气值约为 14.6 dB/km,而水汽为 0.15 dB/km,比值接近一百。
|
||||||
assert!(dry > 50.0 * wet, "dry {dry} wet {wet}");
|
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");
|
let wet = specific_attenuation_water_vapour(frequency, reference()).expect("valid");
|
||||||
assert!(wet > dry);
|
assert!(wet > dry);
|
||||||
// The line reaches over twenty decibels per kilometer.
|
// The line reaches over twenty decibels per kilometer.
|
||||||
|
// 该线达到每千米二十多分贝。
|
||||||
assert!(wet > 20.0, "wet {wet}");
|
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() {
|
fn attenuation_is_small_in_the_microwave_windows() {
|
||||||
// Below 15 GHz the total specific attenuation is a hundredth of a
|
// Below 15 GHz the total specific attenuation is a hundredth of a
|
||||||
// decibel per kilometer or less, which is why those bands are used.
|
// 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] {
|
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");
|
let total = specific_attenuation(frequency, reference()).expect("valid");
|
||||||
assert!(total < 0.05, "{frequency} GHz gave {total} dB/km");
|
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() {
|
fn dry_air_attenuation_peaks_inside_the_oxygen_complex() {
|
||||||
// The strongest line is near 60.4 GHz, and the value there must exceed
|
// The strongest line is near 60.4 GHz, and the value there must exceed
|
||||||
// the values on either side.
|
// the values on either side.
|
||||||
|
// 最强线在 60.4 GHz 附近,那里的值必须超过两侧的值。
|
||||||
let peak = specific_attenuation_dry_air(60.434_778, reference()).expect("valid");
|
let peak = specific_attenuation_dry_air(60.434_778, reference()).expect("valid");
|
||||||
for frequency in [50.0, 55.0, 65.0, 70.0] {
|
for frequency in [50.0, 55.0, 65.0, 70.0] {
|
||||||
let value = specific_attenuation_dry_air(frequency, reference()).expect("valid");
|
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
|
// 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
|
// ratio of the total barometric pressure, dry air plus water vapour, to
|
||||||
// the reference pressure.
|
// the reference pressure.
|
||||||
|
// 式 30 在较低频率处把干空气高度限制到一个依赖气压的界。该界为 `10.7 rp^0.3`,
|
||||||
|
// 其中 `rp` 是总气压(干空气加水汽)与参考气压之比。
|
||||||
let atmosphere = reference();
|
let atmosphere = reference();
|
||||||
let total = atmosphere.pressure_hpa + atmosphere.water_vapour_partial_pressure();
|
let total = atmosphere.pressure_hpa + atmosphere.water_vapour_partial_pressure();
|
||||||
let rp = total / 1_013.25;
|
let rp = total / 1_013.25;
|
||||||
let bound = 10.7 * rp.powf(0.3);
|
let bound = 10.7 * rp.powf(0.3);
|
||||||
|
|
||||||
// At 60 GHz the uncapped value is above the bound, so the bound applies.
|
// 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");
|
let heights = equivalent_heights(60.0, atmosphere).expect("valid");
|
||||||
assert!((heights.dry_km - bound).abs() < 1e-9, "{}", heights.dry_km);
|
assert!((heights.dry_km - bound).abs() < 1e-9, "{}", heights.dry_km);
|
||||||
|
|
||||||
// Above 70 GHz no cap is applied, and the value exceeds it.
|
// Above 70 GHz no cap is applied, and the value exceeds it.
|
||||||
|
// 70 GHz 以上不施加界,而该值超过它。
|
||||||
let high = equivalent_heights(118.0, atmosphere).expect("valid");
|
let high = equivalent_heights(118.0, atmosphere).expect("valid");
|
||||||
assert!(high.dry_km > bound);
|
assert!(high.dry_km > bound);
|
||||||
}
|
}
|
||||||
|
|
@ -242,6 +264,7 @@ fn slant_path_attenuation_scales_with_the_elevation() {
|
||||||
let frequency = 12.0;
|
let frequency = 12.0;
|
||||||
let zenith = zenith_attenuation(frequency, reference()).expect("valid");
|
let zenith = zenith_attenuation(frequency, reference()).expect("valid");
|
||||||
// At thirty degrees elevation the path is twice the zenith path.
|
// At thirty degrees elevation the path is twice the zenith path.
|
||||||
|
// 三十度仰角处路径是天顶路径的两倍。
|
||||||
let thirty =
|
let thirty =
|
||||||
slant_path_attenuation(frequency, core::f64::consts::PI / 6.0, reference()).expect("valid");
|
slant_path_attenuation(frequency, core::f64::consts::PI / 6.0, reference()).expect("valid");
|
||||||
assert!((thirty - 2.0 * zenith).abs() < 1e-9);
|
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;
|
use apsis_atmosphere::slant_path_attenuation_exact;
|
||||||
// These values are from a reference implementation of the exact method
|
// These values are from a reference implementation of the exact method
|
||||||
// of ITU-R Recommendation P.676-12.
|
// of ITU-R Recommendation P.676-12.
|
||||||
|
// 这些值来自 ITU-R 建议书 P.676-12 精确方法的参考实现。
|
||||||
let cases: [(f64, f64, f64); 10] = [
|
let cases: [(f64, f64, f64); 10] = [
|
||||||
(12.0, 90.0, 0.059926619268),
|
(12.0, 90.0, 0.059926619268),
|
||||||
(12.0, 30.0, 0.119683280157),
|
(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
|
// parallel geometry is exact. The recommendation notes that the
|
||||||
// approximate method is intended only for the elevation range above
|
// approximate method is intended only for the elevation range above
|
||||||
// about five degrees and for rough work.
|
// about five degrees and for rough work.
|
||||||
|
// 两种方法使用不同的表述,故即便在天顶(那里平面平行几何是精确的)它们也只相符
|
||||||
|
// 到百分之几而非精确相等。该建议书指出近似方法只意在于约五度以上的仰角范围以及
|
||||||
|
// 粗略工作。
|
||||||
let exact = slant_path_attenuation_exact(12.0, core::f64::consts::FRAC_PI_2, reference())
|
let exact = slant_path_attenuation_exact(12.0, core::f64::consts::FRAC_PI_2, reference())
|
||||||
.expect("valid");
|
.expect("valid");
|
||||||
let approximate =
|
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
|
// At a low elevation the curved path through the thinning atmosphere is
|
||||||
// shorter than the plane parallel estimate, and the refraction bends
|
// shorter than the plane parallel estimate, and the refraction bends
|
||||||
// the ray downward, so the two methods diverge.
|
// the ray downward, so the two methods diverge.
|
||||||
|
// 在低仰角处,穿过变稀薄大气的弯曲路径比平面平行估计短,且折射使射线向下弯折,
|
||||||
|
// 故两种方法分歧。
|
||||||
let elevation = 2.0_f64.to_radians();
|
let elevation = 2.0_f64.to_radians();
|
||||||
let exact = slant_path_attenuation_exact(12.0, elevation, reference()).expect("valid");
|
let exact = slant_path_attenuation_exact(12.0, elevation, reference()).expect("valid");
|
||||||
let approximate = slant_path_attenuation(12.0, elevation, reference()).expect("valid");
|
let approximate = slant_path_attenuation(12.0, elevation, reference()).expect("valid");
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for the atmospheric path geometry.
|
//! Tests for the atmospheric path geometry.
|
||||||
|
//!
|
||||||
|
//! 大气路径几何的测试。
|
||||||
|
|
||||||
use apsis_atmosphere::{air_mass, elevation_from_zenith, zenith_to_elevation};
|
use apsis_atmosphere::{air_mass, elevation_from_zenith, zenith_to_elevation};
|
||||||
use core::f64::consts::{FRAC_PI_2, FRAC_PI_4, FRAC_PI_6};
|
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]
|
#[test]
|
||||||
fn known_elevations_give_known_air_masses() {
|
fn known_elevations_give_known_air_masses() {
|
||||||
// Thirty degrees is a cosecant of two.
|
// Thirty degrees is a cosecant of two.
|
||||||
|
// 三十度的余割为二。
|
||||||
assert!((air_mass(FRAC_PI_6) - 2.0).abs() < 1e-9);
|
assert!((air_mass(FRAC_PI_6) - 2.0).abs() < 1e-9);
|
||||||
// Forty five degrees is the square root of two.
|
// Forty five degrees is the square root of two.
|
||||||
|
// 四十五度是根号二。
|
||||||
assert!((air_mass(FRAC_PI_4) - 2.0_f64.sqrt()).abs() < 1e-9);
|
assert!((air_mass(FRAC_PI_4) - 2.0_f64.sqrt()).abs() < 1e-9);
|
||||||
// Ten degrees is about 5.76.
|
// Ten degrees is about 5.76.
|
||||||
|
// 十度约为 5.76。
|
||||||
assert!((air_mass(10.0_f64.to_radians()) - 5.758_8).abs() < 1e-3);
|
assert!((air_mass(10.0_f64.to_radians()) - 5.758_8).abs() < 1e-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for the ITU-R P.835 reference standard atmosphere.
|
//! Tests for the ITU-R P.835 reference standard atmosphere.
|
||||||
|
//!
|
||||||
|
//! ITU-R P.835 参考标准大气的测试。
|
||||||
|
|
||||||
use apsis_atmosphere::{
|
use apsis_atmosphere::{
|
||||||
geopotential_height, radio_refractive_index, standard_pressure, standard_temperature,
|
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() {
|
fn the_geopotential_height_is_below_the_geometric_height() {
|
||||||
// The conversion compresses the height because gravity weakens with
|
// The conversion compresses the height because gravity weakens with
|
||||||
// altitude, so the geopotential height is always the smaller value.
|
// altitude, so the geopotential height is always the smaller value.
|
||||||
|
// 该换算压缩高度,因为引力随高度减弱,故势高总是较小的值。
|
||||||
for geometric in [0.0, 1.0, 11.0, 50.0, 100.0] {
|
for geometric in [0.0, 1.0, 11.0, 50.0, 100.0] {
|
||||||
let geopotential = geopotential_height(geometric);
|
let geopotential = geopotential_height(geometric);
|
||||||
assert!(geopotential <= geometric, "height {geometric}");
|
assert!(geopotential <= geometric, "height {geometric}");
|
||||||
}
|
}
|
||||||
// At the surface the two coincide.
|
// At the surface the two coincide.
|
||||||
|
// 在地表处二者重合。
|
||||||
assert_eq!(geopotential_height(0.0), 0.0);
|
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() {
|
fn the_surface_conditions_match_the_recommendation() {
|
||||||
// The mean annual global reference atmosphere starts at 288.15 K and
|
// The mean annual global reference atmosphere starts at 288.15 K and
|
||||||
// 1013.25 hPa.
|
// 1013.25 hPa.
|
||||||
|
// 年平均全球参考大气始于 288.15 K 与 1013.25 hPa。
|
||||||
assert!((standard_temperature(0.0) - 288.15).abs() < TOLERANCE);
|
assert!((standard_temperature(0.0) - 288.15).abs() < TOLERANCE);
|
||||||
assert!((standard_pressure(0.0) - 1_013.25).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);
|
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() {
|
fn the_temperature_profile_has_the_tabulated_segments() {
|
||||||
// The troposphere cools at 6.5 K per kilometer of geopotential height,
|
// The troposphere cools at 6.5 K per kilometer of geopotential height,
|
||||||
// so the rate is expressed against that height, not the geometric one.
|
// 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);
|
let expected = 288.15 - 6.5 * geopotential_height(1.0);
|
||||||
assert!((standard_temperature(1.0) - expected).abs() < TOLERANCE);
|
assert!((standard_temperature(1.0) - expected).abs() < TOLERANCE);
|
||||||
|
|
||||||
// The stratosphere is isothermal at 216.65 K between 11 and 20 km of
|
// 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
|
// geopotential height. A geometric height of 15 km has a geopotential
|
||||||
// height of about 14.97 km, which is inside that band.
|
// 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);
|
let geopotential = geopotential_height(15.0);
|
||||||
assert!(
|
assert!(
|
||||||
(11.0..=20.0).contains(&geopotential),
|
(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.
|
// Above 20 km geopotential the temperature rises at 1 K per kilometer.
|
||||||
// A geometric height of 30 km reaches the middle of that segment.
|
// A geometric height of 30 km reaches the middle of that segment.
|
||||||
|
// 20 km 势高以上温度以每千米 1 K 上升。30 km 的几何高度到达该段中部。
|
||||||
let geopotential = geopotential_height(30.0);
|
let geopotential = geopotential_height(30.0);
|
||||||
assert!(
|
assert!(
|
||||||
(20.0..32.0).contains(&geopotential),
|
(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
|
// The temperature is continuous across each segment boundary. The
|
||||||
// boundaries are in geopotential height, so they are converted to the
|
// boundaries are in geopotential height, so they are converted to the
|
||||||
// geometric heights at which they occur before sampling either side.
|
// 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] {
|
for geopotential in [11.0_f64, 20.0, 32.0, 47.0, 51.0, 71.0] {
|
||||||
let radius = 6_356.766;
|
let radius = 6_356.766;
|
||||||
let geometric = radius * geopotential / (radius - geopotential);
|
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] {
|
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
|
// The boundaries are in geopotential height, so a geometric height
|
||||||
// just either side is used.
|
// just either side is used.
|
||||||
|
// 各边界以势高给出,故用恰好在其两侧的几何高度。
|
||||||
let below = standard_temperature(boundary - 0.01);
|
let below = standard_temperature(boundary - 0.01);
|
||||||
let above = standard_temperature(boundary + 0.01);
|
let above = standard_temperature(boundary + 0.01);
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -111,6 +123,7 @@ fn the_pressure_is_continuous_across_the_segments() {
|
||||||
fn the_pressure_at_the_tropopause_matches_the_reference() {
|
fn the_pressure_at_the_tropopause_matches_the_reference() {
|
||||||
// The tropopause is at 11 km of geopotential height, which is a
|
// 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.
|
// 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;
|
let geometric = 11.019_068;
|
||||||
assert!((geopotential_height(geometric) - 11.0).abs() < 1e-5);
|
assert!((geopotential_height(geometric) - 11.0).abs() < 1e-5);
|
||||||
let pressure = standard_pressure(geometric);
|
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() {
|
fn the_water_vapour_profile_is_exponential() {
|
||||||
// The scale height is two kilometers, so the density halves about every
|
// The scale height is two kilometers, so the density halves about every
|
||||||
// 1.386 km.
|
// 1.386 km.
|
||||||
|
// 标高为两千米,故密度大约每 1.386 km 减半。
|
||||||
let surface = 7.5;
|
let surface = 7.5;
|
||||||
let at_two = standard_water_vapour_density(2.0, surface);
|
let at_two = standard_water_vapour_density(2.0, surface);
|
||||||
assert!((at_two - surface / core::f64::consts::E).abs() < 1e-9);
|
assert!((at_two - surface / core::f64::consts::E).abs() < 1e-9);
|
||||||
// The profile scales with the surface value.
|
// The profile scales with the surface value.
|
||||||
|
// 该廓线随地表值缩放。
|
||||||
let doubled = standard_water_vapour_density(2.0, 15.0);
|
let doubled = standard_water_vapour_density(2.0, 15.0);
|
||||||
assert!((doubled / at_two - 2.0).abs() < 1e-12);
|
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() {
|
fn the_radio_refractive_index_at_the_surface_is_about_one() {
|
||||||
// A refractivity of about three hundred N units gives an index about
|
// A refractivity of about three hundred N units gives an index about
|
||||||
// 1.0003.
|
// 1.0003.
|
||||||
|
// 约三百 N 单位的折射率给出约 1.0003 的折射指数。
|
||||||
let index = radio_refractive_index(1_013.25, 10.0, 288.15);
|
let index = radio_refractive_index(1_013.25, 10.0, 288.15);
|
||||||
assert!(index > 1.0 && index < 1.001, "index {index}");
|
assert!(index > 1.0 && index < 1.001, "index {index}");
|
||||||
assert!((index - 1.000_315).abs() < 1e-5, "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() {
|
fn a_zero_temperature_is_handled_without_dividing_by_zero() {
|
||||||
// The expression divides by the temperature, so a zero would give an
|
// The expression divides by the temperature, so a zero would give an
|
||||||
// infinite value. The function returns the index of vacuum instead.
|
// infinite value. The function returns the index of vacuum instead.
|
||||||
|
// 该表达式除以温度,故零会给出无穷值。该函数改而返回真空的折射指数。
|
||||||
assert_eq!(radio_refractive_index(1_013.25, 10.0, 0.0), 1.0);
|
assert_eq!(radio_refractive_index(1_013.25, 10.0, 0.0), 1.0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,11 @@
|
||||||
//! The expected values are those of the recommendation's tabulated grid,
|
//! The expected values are those of the recommendation's tabulated grid,
|
||||||
//! read at the grid points and interpolated between them. They were produced
|
//! read at the grid points and interpolated between them. They were produced
|
||||||
//! with the reference implementation of the recommendation.
|
//! with the reference implementation of the recommendation.
|
||||||
|
//!
|
||||||
|
//! ITU-R P.839-4 雨顶高度模型的测试。
|
||||||
|
//!
|
||||||
|
//! 期望值是该建议书所列网格的值,在网格点处读取并在其之间插值。它们由该建议书的
|
||||||
|
//! 参考实现产生。
|
||||||
|
|
||||||
use apsis_atmosphere::{
|
use apsis_atmosphere::{
|
||||||
RAIN_HEIGHT_OFFSET_KM, grid_step_degrees, rain_height, zero_isotherm_height,
|
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
|
/// The table is stored in metres, so the interpolation is exact to the
|
||||||
/// resolution the recommendation publishes.
|
/// resolution the recommendation publishes.
|
||||||
|
///
|
||||||
|
/// 插值高度的容差,单位为千米。
|
||||||
|
///
|
||||||
|
/// 该表以米存储,故插值在该建议书所发布的分辨率内是精确的。
|
||||||
const TOLERANCE: f64 = 1e-9;
|
const TOLERANCE: f64 = 1e-9;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -28,6 +37,7 @@ fn the_rain_height_offset_matches_the_recommendation() {
|
||||||
fn values_at_grid_points_match_the_recommendation() {
|
fn values_at_grid_points_match_the_recommendation() {
|
||||||
// These are the tabulated values at exact grid points, so no
|
// These are the tabulated values at exact grid points, so no
|
||||||
// interpolation is involved.
|
// interpolation is involved.
|
||||||
|
// 这些是精确网格点处所列的值,故不涉及插值。
|
||||||
let cases = [
|
let cases = [
|
||||||
(0.0, 0.0, 4.566),
|
(0.0, 0.0, 4.566),
|
||||||
(45.0, 0.0, 2.356),
|
(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() {
|
fn longitude_wraps_at_the_seam() {
|
||||||
// The grid runs to 360 degrees, which is the same meridian as zero, so
|
// The grid runs to 360 degrees, which is the same meridian as zero, so
|
||||||
// the height must be continuous across the seam.
|
// the height must be continuous across the seam.
|
||||||
|
// 网格延伸到 360 度,它与零度是同一子午线,故高度必须在接缝处连续。
|
||||||
let at_zero = zero_isotherm_height(10.0, 0.0).expect("valid");
|
let at_zero = zero_isotherm_height(10.0, 0.0).expect("valid");
|
||||||
let at_360 = zero_isotherm_height(10.0, 360.0).expect("valid");
|
let at_360 = zero_isotherm_height(10.0, 360.0).expect("valid");
|
||||||
assert!((at_zero - at_360).abs() < TOLERANCE);
|
assert!((at_zero - at_360).abs() < TOLERANCE);
|
||||||
|
|
||||||
// A negative longitude is the same as its positive equivalent.
|
// A negative longitude is the same as its positive equivalent.
|
||||||
|
// 负经度与其正等价经度相同。
|
||||||
let negative = zero_isotherm_height(10.0, -10.0).expect("valid");
|
let negative = zero_isotherm_height(10.0, -10.0).expect("valid");
|
||||||
let positive = zero_isotherm_height(10.0, 350.0).expect("valid");
|
let positive = zero_isotherm_height(10.0, 350.0).expect("valid");
|
||||||
assert!((negative - positive).abs() < TOLERANCE);
|
assert!((negative - positive).abs() < TOLERANCE);
|
||||||
|
|
@ -92,6 +104,7 @@ fn longitude_wraps_at_the_seam() {
|
||||||
fn the_height_is_bounded_by_the_table() {
|
fn the_height_is_bounded_by_the_table() {
|
||||||
// Every tabulated value lies between the minimum and maximum of the
|
// 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.
|
// 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 latitude in [-90.0, -45.0, 0.0, 45.0, 90.0] {
|
||||||
for longitude in [0.0, 90.0, 180.0, 270.0] {
|
for longitude in [0.0, 90.0, 180.0, 270.0] {
|
||||||
let height = zero_isotherm_height(latitude, longitude).expect("valid coordinates");
|
let height = zero_isotherm_height(latitude, longitude).expect("valid coordinates");
|
||||||
|
|
@ -106,6 +119,7 @@ fn the_height_is_bounded_by_the_table() {
|
||||||
#[test]
|
#[test]
|
||||||
fn the_height_is_continuous_across_the_grid() {
|
fn the_height_is_continuous_across_the_grid() {
|
||||||
// The interpolation must not jump between neighbouring grid cells.
|
// The interpolation must not jump between neighbouring grid cells.
|
||||||
|
// 插值不得在相邻网格单元之间跳变。
|
||||||
let mut previous = zero_isotherm_height(0.0, 0.0).expect("valid");
|
let mut previous = zero_isotherm_height(0.0, 0.0).expect("valid");
|
||||||
for step in 1..=240 {
|
for step in 1..=240 {
|
||||||
let longitude = step as f64 * 1.5;
|
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() {
|
fn the_tropics_are_higher_than_the_poles() {
|
||||||
// The isotherm is highest near the equator, where the atmosphere is
|
// The isotherm is highest near the equator, where the atmosphere is
|
||||||
// warmest, and lowest at high latitudes.
|
// warmest, and lowest at high latitudes.
|
||||||
|
// 等温线在赤道附近最高(那里大气最暖),在高纬度最低。
|
||||||
let equator = zero_isotherm_height(0.0, 100.0).expect("valid");
|
let equator = zero_isotherm_height(0.0, 100.0).expect("valid");
|
||||||
let mid_latitude = zero_isotherm_height(50.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");
|
let pole = zero_isotherm_height(80.0, 100.0).expect("valid");
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,11 @@
|
||||||
//! The coefficient values are those of ITU-R Recommendation P.838-3,
|
//! The coefficient values are those of ITU-R Recommendation P.838-3,
|
||||||
//! produced with a reference implementation of the recommendation and
|
//! produced with a reference implementation of the recommendation and
|
||||||
//! recorded here. The path results exercise the P.618-13 method.
|
//! recorded here. The path results exercise the P.618-13 method.
|
||||||
|
//!
|
||||||
|
//! 降雨衰减的测试。
|
||||||
|
//!
|
||||||
|
//! 各系数值是 ITU-R 建议书 P.838-3 的值,由该建议书的参考实现产生并记录在此。
|
||||||
|
//! 路径结果检验 P.618-13 方法。
|
||||||
|
|
||||||
use apsis_atmosphere::{
|
use apsis_atmosphere::{
|
||||||
DEFAULT_RAIN_HEIGHT_KM, polarisation_tilt, rain_attenuation, rain_coefficients,
|
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};
|
use core::f64::consts::{FRAC_PI_2, PI};
|
||||||
|
|
||||||
/// Horizontal polarisation.
|
/// Horizontal polarisation.
|
||||||
|
///
|
||||||
|
/// 水平极化。
|
||||||
const HORIZONTAL: f64 = 0.0;
|
const HORIZONTAL: f64 = 0.0;
|
||||||
/// Vertical polarisation.
|
/// Vertical polarisation.
|
||||||
|
///
|
||||||
|
/// 垂直极化。
|
||||||
const VERTICAL: f64 = FRAC_PI_2;
|
const VERTICAL: f64 = FRAC_PI_2;
|
||||||
/// The zenith, where the two polarisations coincide.
|
/// The zenith, where the two polarisations coincide.
|
||||||
|
///
|
||||||
|
/// 天顶,两种极化在那里重合。
|
||||||
const ZENITH: f64 = FRAC_PI_2;
|
const ZENITH: f64 = FRAC_PI_2;
|
||||||
/// A mid latitude elevation of thirty degrees.
|
/// A mid latitude elevation of thirty degrees.
|
||||||
|
///
|
||||||
|
/// 三十度的中纬度仰角。
|
||||||
const THIRTY_DEGREES: f64 = PI / 6.0;
|
const THIRTY_DEGREES: f64 = PI / 6.0;
|
||||||
|
|
||||||
/// The relative tolerance on the coefficient values.
|
/// The relative tolerance on the coefficient values.
|
||||||
|
///
|
||||||
|
/// 系数值的相对容差。
|
||||||
const TOLERANCE: f64 = 1e-9;
|
const TOLERANCE: f64 = 1e-9;
|
||||||
|
|
||||||
fn assert_relative(actual: f64, expected: f64, context: &str) {
|
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() {
|
fn coefficients_match_the_recommendation_at_the_zenith() {
|
||||||
// At the zenith the elevation term vanishes, so horizontal and vertical
|
// At the zenith the elevation term vanishes, so horizontal and vertical
|
||||||
// polarisation give the same coefficients.
|
// polarisation give the same coefficients.
|
||||||
|
// 天顶处仰角项消失,故水平与垂直极化给出相同的系数。
|
||||||
let cases = [
|
let cases = [
|
||||||
(12.0, 0.024_203_061_156_1, 1.151_599_196_3),
|
(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),
|
(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
|
// elevation the vertical coefficient is the larger of the two, and the
|
||||||
// two converge as the elevation rises and the geometry becomes
|
// two converge as the elevation rises and the geometry becomes
|
||||||
// symmetric.
|
// symmetric.
|
||||||
|
// 远离天顶时极化要紧。12 GHz 与低仰角下垂直系数是两者中较大者,随仰角升高、
|
||||||
|
// 几何变得对称,二者趋于一致。
|
||||||
let low_elevation = 10.0_f64.to_radians();
|
let low_elevation = 10.0_f64.to_radians();
|
||||||
let horizontal = rain_coefficients(12.0e9, HORIZONTAL, low_elevation).expect("valid");
|
let horizontal = rain_coefficients(12.0e9, HORIZONTAL, low_elevation).expect("valid");
|
||||||
let vertical = rain_coefficients(12.0e9, VERTICAL, 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");
|
assert_relative(vertical.0, 0.024_537_918_525_868_188, "vertical k");
|
||||||
|
|
||||||
// At the zenith they coincide.
|
// At the zenith they coincide.
|
||||||
|
// 天顶处二者重合。
|
||||||
let zenith_horizontal = rain_coefficients(12.0e9, HORIZONTAL, ZENITH).expect("valid");
|
let zenith_horizontal = rain_coefficients(12.0e9, HORIZONTAL, ZENITH).expect("valid");
|
||||||
let zenith_vertical = rain_coefficients(12.0e9, VERTICAL, ZENITH).expect("valid");
|
let zenith_vertical = rain_coefficients(12.0e9, VERTICAL, ZENITH).expect("valid");
|
||||||
assert_relative(zenith_horizontal.0, zenith_vertical.0, "zenith k");
|
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() {
|
fn circular_polarisation_lies_between_the_linear_cases() {
|
||||||
// A tilt of forty five degrees is circular polarisation, whose
|
// A tilt of forty five degrees is circular polarisation, whose
|
||||||
// coefficients are the mean of the two linear cases.
|
// coefficients are the mean of the two linear cases.
|
||||||
|
// 四十五度倾角是圆极化,其系数是两种线极化情形的均值。
|
||||||
let horizontal = rain_coefficients(12.0e9, HORIZONTAL, THIRTY_DEGREES).expect("valid");
|
let horizontal = rain_coefficients(12.0e9, HORIZONTAL, THIRTY_DEGREES).expect("valid");
|
||||||
let vertical = rain_coefficients(12.0e9, VERTICAL, THIRTY_DEGREES).expect("valid");
|
let vertical = rain_coefficients(12.0e9, VERTICAL, THIRTY_DEGREES).expect("valid");
|
||||||
let circular =
|
let circular =
|
||||||
|
|
@ -98,6 +118,7 @@ fn circular_polarisation_lies_between_the_linear_cases() {
|
||||||
fn the_coefficients_are_smooth_across_the_band() {
|
fn the_coefficients_are_smooth_across_the_band() {
|
||||||
// The curve fits are continuous, so a small change in frequency gives a
|
// The curve fits are continuous, so a small change in frequency gives a
|
||||||
// small change in the coefficients.
|
// small change in the coefficients.
|
||||||
|
// 各曲线拟合是连续的,故频率的小变化给出系数的小变化。
|
||||||
let mut previous = rain_coefficients(10.0e9, HORIZONTAL, ZENITH).expect("valid");
|
let mut previous = rain_coefficients(10.0e9, HORIZONTAL, ZENITH).expect("valid");
|
||||||
for tenth in 101..=200 {
|
for tenth in 101..=200 {
|
||||||
let frequency = tenth as f64 * 0.1e9;
|
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");
|
let (k, alpha) = rain_coefficients(frequency, HORIZONTAL, THIRTY_DEGREES).expect("valid");
|
||||||
assert_relative(specific, k * rain_rate.powf(alpha), "specific attenuation");
|
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.
|
// 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!(
|
assert!(
|
||||||
(specific - 0.808_128_772_7).abs() < 1e-6,
|
(specific - 0.808_128_772_7).abs() < 1e-6,
|
||||||
"specific {specific}"
|
"specific {specific}"
|
||||||
|
|
@ -198,6 +220,9 @@ fn the_reduction_factors_are_physical() {
|
||||||
// vertical adjustment is the ratio of two path estimates and can exceed
|
// 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
|
// one when the path crosses the rain layer at a shallow angle, which is
|
||||||
// the case Recommendation P.618 allows for.
|
// the case Recommendation P.618 allows for.
|
||||||
|
// 水平缩减因子计入降雨并未填满路径的整个水平范围,故它至多为一。垂直调整是两个
|
||||||
|
// 路径估计之比,可在路径以浅角穿越雨层时超过一,这正是建议书 P.618 所容许的
|
||||||
|
// 情形。
|
||||||
let result = rain_attenuation(
|
let result = rain_attenuation(
|
||||||
20.0,
|
20.0,
|
||||||
12.0e9,
|
12.0e9,
|
||||||
|
|
@ -245,6 +270,8 @@ fn attenuation_grows_as_the_elevation_falls() {
|
||||||
fn a_heavy_rain_event_gives_a_significant_attenuation() {
|
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
|
// 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.
|
// decibels, which is the order of magnitude a Ku band link must budget.
|
||||||
|
// 12 GHz、三十度仰角下 20 mm/h 的雨胞给出几分贝,这是 Ku 频段链路必须预算的量
|
||||||
|
// 级。
|
||||||
let result = rain_attenuation(
|
let result = rain_attenuation(
|
||||||
20.0,
|
20.0,
|
||||||
12.0e9,
|
12.0e9,
|
||||||
|
|
@ -335,6 +362,7 @@ fn an_elevation_at_the_horizon_is_rejected() {
|
||||||
fn the_latitude_enters_through_the_vertical_adjustment() {
|
fn the_latitude_enters_through_the_vertical_adjustment() {
|
||||||
// Below thirty six degrees the latitude term is nonzero, so the same
|
// Below thirty six degrees the latitude term is nonzero, so the same
|
||||||
// geometry gives a different result at two latitudes.
|
// geometry gives a different result at two latitudes.
|
||||||
|
// 三十六度以下纬度项非零,故同一几何在两个纬度处给出不同结果。
|
||||||
let temperate = rain_attenuation(
|
let temperate = rain_attenuation(
|
||||||
20.0,
|
20.0,
|
||||||
12.0e9,
|
12.0e9,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for the Doppler shift.
|
//! Tests for the Doppler shift.
|
||||||
|
//!
|
||||||
|
//! 多普勒频移的测试。
|
||||||
|
|
||||||
use apsis_doppler::{
|
use apsis_doppler::{
|
||||||
DopplerShift, SPEED_OF_LIGHT, acquisition_span, angle_from_range_rate, classical_shift,
|
DopplerShift, SPEED_OF_LIGHT, acquisition_span, angle_from_range_rate, classical_shift,
|
||||||
|
|
@ -12,16 +14,19 @@ const TOLERANCE: f64 = 1e-9;
|
||||||
#[test]
|
#[test]
|
||||||
fn range_rate_is_the_projection_of_velocity_on_the_line_of_sight() {
|
fn range_rate_is_the_projection_of_velocity_on_the_line_of_sight() {
|
||||||
// Moving away along the line of sight.
|
// Moving away along the line of sight.
|
||||||
|
// 沿视线远离。
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
range_rate_from_velocity(Vec3::new(1_000.0, 0.0, 0.0), Vec3::new(10.0, 0.0, 0.0)),
|
range_rate_from_velocity(Vec3::new(1_000.0, 0.0, 0.0), Vec3::new(10.0, 0.0, 0.0)),
|
||||||
Some(10.0)
|
Some(10.0)
|
||||||
);
|
);
|
||||||
// Moving across the line of sight gives no range rate.
|
// Moving across the line of sight gives no range rate.
|
||||||
|
// 横穿视线不给出距离变化率。
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
range_rate_from_velocity(Vec3::new(1_000.0, 0.0, 0.0), Vec3::new(0.0, 10.0, 0.0)),
|
range_rate_from_velocity(Vec3::new(1_000.0, 0.0, 0.0), Vec3::new(0.0, 10.0, 0.0)),
|
||||||
Some(0.0)
|
Some(0.0)
|
||||||
);
|
);
|
||||||
// Approaching along the line of sight.
|
// Approaching along the line of sight.
|
||||||
|
// 沿视线接近。
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
range_rate_from_velocity(Vec3::new(1_000.0, 0.0, 0.0), Vec3::new(-10.0, 0.0, 0.0)),
|
range_rate_from_velocity(Vec3::new(1_000.0, 0.0, 0.0), Vec3::new(-10.0, 0.0, 0.0)),
|
||||||
Some(-10.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() {
|
fn range_rate_uses_the_magnitude_of_the_separation() {
|
||||||
// The velocity is doubled but the separation is also doubled, so the
|
// The velocity is doubled but the separation is also doubled, so the
|
||||||
// projection along the diagonal works out to the same fraction.
|
// 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))
|
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");
|
.expect("non-zero separation");
|
||||||
// The unit line of sight is (0.6, 0.8, 0), so the rate is
|
// The unit line of sight is (0.6, 0.8, 0), so the rate is
|
||||||
// 0.6 * 6 + 0.8 * 8 = 10.
|
// 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);
|
assert!((rate - 10.0).abs() < TOLERANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,6 +76,7 @@ fn the_classical_shift_is_negative_for_a_receding_terminal() {
|
||||||
#[test]
|
#[test]
|
||||||
fn a_known_range_rate_gives_a_known_shift() {
|
fn a_known_range_rate_gives_a_known_shift() {
|
||||||
// A range rate of 7 km/s at 12 GHz gives about 280.2 kHz.
|
// 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");
|
let shift = classical_shift(12.0e9, 7_000.0).expect("valid");
|
||||||
assert!((shift + 280_194.0).abs() < 1.0, "shift {shift}");
|
assert!((shift + 280_194.0).abs() < 1.0, "shift {shift}");
|
||||||
assert!((shift.abs() / 12.0e9 * 1.0e6 - 23.35).abs() < 0.01);
|
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() {
|
fn the_relativistic_factor_opposes_the_classical_shift() {
|
||||||
// For a receding terminal the factor is below one, which reinforces the
|
// For a receding terminal the factor is below one, which reinforces the
|
||||||
// classical redshift.
|
// classical redshift.
|
||||||
|
// 对远离的终端该因子小于一,这加强了经典红移。
|
||||||
let receding = relativistic_doppler_factor(7_000.0).expect("valid");
|
let receding = relativistic_doppler_factor(7_000.0).expect("valid");
|
||||||
assert!(receding < 1.0);
|
assert!(receding < 1.0);
|
||||||
// For an approaching terminal it is above one.
|
// For an approaching terminal it is above one.
|
||||||
|
// 对接近的终端它大于一。
|
||||||
let approaching = relativistic_doppler_factor(-7_000.0).expect("valid");
|
let approaching = relativistic_doppler_factor(-7_000.0).expect("valid");
|
||||||
assert!(approaching > 1.0);
|
assert!(approaching > 1.0);
|
||||||
// The two are reciprocals, because swapping the roles of the terminals
|
// The two are reciprocals, because swapping the roles of the terminals
|
||||||
// inverts the ratio.
|
// inverts the ratio.
|
||||||
|
// 二者互为倒数,因为交换两终端的角色会反转该比值。
|
||||||
assert!((receding * approaching - 1.0).abs() < 1e-15);
|
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
|
// The factor's expansion begins with 1 - beta, which is the classical
|
||||||
// result, so the two treatments must agree to first order. They differ
|
// result, so the two treatments must agree to first order. They differ
|
||||||
// only by terms of order beta squared.
|
// only by terms of order beta squared.
|
||||||
|
// 该因子的展开以 1 - beta 开头,即经典结果,故两种处理必须在一阶上一致。它们
|
||||||
|
// 只相差 `beta` 平方量级的项。
|
||||||
let beta = 7_000.0 / SPEED_OF_LIGHT;
|
let beta = 7_000.0 / SPEED_OF_LIGHT;
|
||||||
let factor = relativistic_doppler_factor(7_000.0).expect("valid");
|
let factor = relativistic_doppler_factor(7_000.0).expect("valid");
|
||||||
let difference = (factor - 1.0 + beta).abs();
|
let difference = (factor - 1.0 + beta).abs();
|
||||||
// The leading difference is beta^2 / 2, about 2.7e-10.
|
// 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, "difference {difference}");
|
||||||
assert!((difference - beta * beta / 2.0).abs() < 1e-12);
|
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() {
|
fn the_two_treatments_agree_to_second_order() {
|
||||||
// The classical shift and the relativistic one differ by a fraction of
|
// The classical shift and the relativistic one differ by a fraction of
|
||||||
// the carrier that is of order beta squared.
|
// the carrier that is of order beta squared.
|
||||||
|
// 经典频移与相对论频移相差一个 `beta` 平方量级的载波比例。
|
||||||
let carrier = 12.0e9;
|
let carrier = 12.0e9;
|
||||||
let shift = DopplerShift::from_range_rate(carrier, 7_000.0).expect("valid");
|
let shift = DopplerShift::from_range_rate(carrier, 7_000.0).expect("valid");
|
||||||
let classical = shift.shift();
|
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
|
// 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: tiny compared with the 280 kHz classical shift, but real.
|
||||||
|
// 绝对差是二阶项,在 12 GHz 处约 3.3 Hz:与 280 kHz 的经典频移相比很小,但真实
|
||||||
|
// 存在。
|
||||||
assert!(
|
assert!(
|
||||||
(relativistic - classical).abs() > 1.0,
|
(relativistic - classical).abs() > 1.0,
|
||||||
"difference {}",
|
"difference {}",
|
||||||
|
|
@ -197,10 +214,12 @@ fn the_relativistic_shift_is_close_to_the_classical_one() {
|
||||||
let classical = shift.shift();
|
let classical = shift.shift();
|
||||||
let total = shift.shift_including_relativity().expect("valid");
|
let total = shift.shift_including_relativity().expect("valid");
|
||||||
// Both are negative for a receding terminal and agree in sign.
|
// Both are negative for a receding terminal and agree in sign.
|
||||||
|
// 对远离的终端二者都为负,符号一致。
|
||||||
assert!(classical < 0.0);
|
assert!(classical < 0.0);
|
||||||
assert!(total < 0.0);
|
assert!(total < 0.0);
|
||||||
// The second order difference is a few hertz on a shift of a few
|
// The second order difference is a few hertz on a shift of a few
|
||||||
// hundred kilohertz.
|
// hundred kilohertz.
|
||||||
|
// 二阶差在几百千赫兹的频移上是几赫兹。
|
||||||
assert!((total - classical).abs() > 1.0);
|
assert!((total - classical).abs() > 1.0);
|
||||||
assert!((total - classical).abs() < 10.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");
|
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
|
// A range acceleration of 10 m/s^2 changes the shift by
|
||||||
// 12 GHz * 10 / c, about 0.4 Hz per second.
|
// 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");
|
let rate = shift.shift_rate(10.0).expect("valid");
|
||||||
assert!((rate + 400.3).abs() < 0.1, "rate {rate}");
|
assert!((rate + 400.3).abs() < 0.1, "rate {rate}");
|
||||||
assert_eq!(shift.shift_rate(f64::NAN), None);
|
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() {
|
fn the_angle_from_a_range_rate_is_the_angle_to_the_velocity() {
|
||||||
let velocity = Vec3::new(0.0, 1_000.0, 0.0);
|
let velocity = Vec3::new(0.0, 1_000.0, 0.0);
|
||||||
// The full speed along the line of sight is zero degrees.
|
// The full speed along the line of sight is zero degrees.
|
||||||
|
// 沿视线的全速对应零度。
|
||||||
let along = angle_from_range_rate(velocity, 1_000.0).expect("valid");
|
let along = angle_from_range_rate(velocity, 1_000.0).expect("valid");
|
||||||
assert!(along.abs() < 1e-9);
|
assert!(along.abs() < 1e-9);
|
||||||
// The full speed across the line of sight is a right angle.
|
// The full speed across the line of sight is a right angle.
|
||||||
|
// 横穿视线的全速对应直角。
|
||||||
let across = angle_from_range_rate(velocity, 0.0).expect("valid");
|
let across = angle_from_range_rate(velocity, 0.0).expect("valid");
|
||||||
assert!((across - PI / 2.0).abs() < TOLERANCE);
|
assert!((across - PI / 2.0).abs() < TOLERANCE);
|
||||||
// The opposite direction is a straight angle.
|
// The opposite direction is a straight angle.
|
||||||
|
// 相反方向是平角。
|
||||||
let opposite = angle_from_range_rate(velocity, -1_000.0).expect("valid");
|
let opposite = angle_from_range_rate(velocity, -1_000.0).expect("valid");
|
||||||
assert!((opposite - PI).abs() < 1e-9);
|
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);
|
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
|
// A range rate beyond the speed is not physical, so the speed must be
|
||||||
// non-negative and finite.
|
// non-negative and finite.
|
||||||
|
// 超过该速度的距离变化率不物理,故速度必须非负且有限。
|
||||||
assert_eq!(maximum_shift(12.0e9, -1.0), None);
|
assert_eq!(maximum_shift(12.0e9, -1.0), None);
|
||||||
assert_eq!(maximum_shift(-1.0, 7_000.0), None);
|
assert_eq!(maximum_shift(-1.0, 7_000.0), None);
|
||||||
assert_eq!(maximum_shift(12.0e9, f64::INFINITY), 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");
|
let maximum = maximum_shift(12.0e9, 7_000.0).expect("valid");
|
||||||
assert!((span - 2.0 * maximum).abs() < 1e-3);
|
assert!((span - 2.0 * maximum).abs() < 1e-3);
|
||||||
// For a low Earth orbit at 12 GHz the span is about 560 kHz.
|
// 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}");
|
assert!((span - 560_388.0).abs() < 2.0, "span {span}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for carrier to interference computation.
|
//! Tests for carrier to interference computation.
|
||||||
|
//!
|
||||||
|
//! 载干比计算的测试。
|
||||||
|
|
||||||
use apsis_interference::{
|
use apsis_interference::{
|
||||||
BOLTZMANN, Decibel, Interferer, Power, carrier_to_interference, carrier_to_interference_single,
|
BOLTZMANN, Decibel, Interferer, Power, carrier_to_interference, carrier_to_interference_single,
|
||||||
|
|
@ -9,11 +11,15 @@ use apsis_interference::{
|
||||||
const TOLERANCE: f64 = 1e-9;
|
const TOLERANCE: f64 = 1e-9;
|
||||||
|
|
||||||
/// A wanted carrier at a convenient level.
|
/// A wanted carrier at a convenient level.
|
||||||
|
///
|
||||||
|
/// 一个处于便利电平的有用载波。
|
||||||
fn carrier() -> Power {
|
fn carrier() -> Power {
|
||||||
Power::from_dbw(-130.0).expect("finite")
|
Power::from_dbw(-130.0).expect("finite")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An interferer at the given level relative to the carrier.
|
/// An interferer at the given level relative to the carrier.
|
||||||
|
///
|
||||||
|
/// 一个相对载波处于给定电平的干扰源。
|
||||||
fn interferer_at(decibels_below: f64) -> Interferer {
|
fn interferer_at(decibels_below: f64) -> Interferer {
|
||||||
Interferer::new(Power::from_dbw(-130.0 - decibels_below).expect("finite"))
|
Interferer::new(Power::from_dbw(-130.0 - decibels_below).expect("finite"))
|
||||||
}
|
}
|
||||||
|
|
@ -21,6 +27,7 @@ fn interferer_at(decibels_below: f64) -> Interferer {
|
||||||
#[test]
|
#[test]
|
||||||
fn a_single_interferer_gives_the_expected_ratio() {
|
fn a_single_interferer_gives_the_expected_ratio() {
|
||||||
// An interferer ten decibels below the carrier gives a ratio of 10 dB.
|
// 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");
|
let ratio = carrier_to_interference_single(carrier(), interferer_at(10.0)).expect("valid");
|
||||||
assert!((ratio.db() - 10.0).abs() < TOLERANCE, "{}", ratio.db());
|
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() {
|
fn interfering_powers_add() {
|
||||||
// Two equal interferers give twice the power of one, which is three
|
// Two equal interferers give twice the power of one, which is three
|
||||||
// decibels above one alone.
|
// decibels above one alone.
|
||||||
|
// 两个相等干扰源给出的功率是单个的两倍,即比单个高三分贝。
|
||||||
let one = total_interference(&[interferer_at(10.0)]).expect("valid");
|
let one = total_interference(&[interferer_at(10.0)]).expect("valid");
|
||||||
let two = total_interference(&[interferer_at(10.0), interferer_at(10.0)]).expect("valid");
|
let two = total_interference(&[interferer_at(10.0), interferer_at(10.0)]).expect("valid");
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -54,6 +62,7 @@ fn an_empty_interferer_list_has_no_power() {
|
||||||
let total = total_interference(&[]).expect("empty is valid");
|
let total = total_interference(&[]).expect("empty is valid");
|
||||||
assert_eq!(total.watts(), 0.0);
|
assert_eq!(total.watts(), 0.0);
|
||||||
// With no interference there is no ratio to compute.
|
// With no interference there is no ratio to compute.
|
||||||
|
// 没有干扰时没有可比。
|
||||||
assert_eq!(carrier_to_interference(carrier(), &[]), None);
|
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() {
|
fn the_ratio_combines_multiple_interferers() {
|
||||||
// One interferer at 10 dB below gives 10 dB. Adding a second at the
|
// One interferer at 10 dB below gives 10 dB. Adding a second at the
|
||||||
// same level halves the ratio, which is three decibels less.
|
// 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 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)])
|
let pair = carrier_to_interference(carrier(), &[interferer_at(10.0), interferer_at(10.0)])
|
||||||
.expect("valid");
|
.expect("valid");
|
||||||
|
|
@ -76,6 +86,7 @@ fn the_ratio_combines_multiple_interferers() {
|
||||||
fn a_dominant_interferer_hides_the_others() {
|
fn a_dominant_interferer_hides_the_others() {
|
||||||
// An interferer twenty decibels weaker than the carrier contributes
|
// An interferer twenty decibels weaker than the carrier contributes
|
||||||
// almost nothing to the total, so it barely changes the ratio.
|
// almost nothing to the total, so it barely changes the ratio.
|
||||||
|
// 比载波弱二十分贝的干扰源对总量几乎无贡献,故几乎不改变该比。
|
||||||
let strong = carrier_to_interference(carrier(), &[interferer_at(10.0)]).expect("valid");
|
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)])
|
let with_weak = carrier_to_interference(carrier(), &[interferer_at(10.0), interferer_at(30.0)])
|
||||||
.expect("valid");
|
.expect("valid");
|
||||||
|
|
@ -99,6 +110,7 @@ fn invalid_inputs_are_rejected() {
|
||||||
assert_eq!(total_interference(&[negative]), None);
|
assert_eq!(total_interference(&[negative]), None);
|
||||||
// A zero interferer gives an infinite ratio, which has no finite
|
// A zero interferer gives an infinite ratio, which has no finite
|
||||||
// decibel value here.
|
// decibel value here.
|
||||||
|
// 零干扰源给出无穷的比,此处没有有限的分贝值。
|
||||||
let zero_interferer = Interferer::new(Power::from_watts(0.0));
|
let zero_interferer = Interferer::new(Power::from_watts(0.0));
|
||||||
assert_eq!(carrier_to_interference(carrier(), &[zero_interferer]), None);
|
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() {
|
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
|
// A carrier to noise ratio of 30 dB alongside a carrier to interference
|
||||||
// ratio of 5 dB is dominated by the 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");
|
let combined = combine(Decibel::from_db(30.0), Decibel::from_db(5.0)).expect("valid");
|
||||||
assert!(combined.db() < 5.0);
|
assert!(combined.db() < 5.0);
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -164,6 +177,7 @@ fn the_interference_to_noise_ratio_compares_two_powers() {
|
||||||
let noise = Power::from_dbw(-130.0).expect("finite");
|
let noise = Power::from_dbw(-130.0).expect("finite");
|
||||||
let ratio = interference_to_noise_ratio(interference, noise).expect("valid");
|
let ratio = interference_to_noise_ratio(interference, noise).expect("valid");
|
||||||
// The interference is ten decibels below the noise.
|
// The interference is ten decibels below the noise.
|
||||||
|
// 干扰比噪声低十分贝。
|
||||||
assert!((ratio.db() + 10.0).abs() < TOLERANCE);
|
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);
|
let expected = interference.watts() / (BOLTZMANN * bandwidth);
|
||||||
assert!((temperature - expected).abs() < 1e-9);
|
assert!((temperature - expected).abs() < 1e-9);
|
||||||
// The interference is 1e-14 W, so the temperature is about 20 K.
|
// The interference is 1e-14 W, so the temperature is about 20 K.
|
||||||
|
// 干扰为 1e-14 W,故温度约为 20 K。
|
||||||
assert!(
|
assert!(
|
||||||
temperature > 15.0 && temperature < 25.0,
|
temperature > 15.0 && temperature < 25.0,
|
||||||
"temperature {temperature}"
|
"temperature {temperature}"
|
||||||
|
|
@ -209,6 +224,7 @@ fn the_equivalent_temperature_grows_with_the_interfering_power() {
|
||||||
.expect("valid");
|
.expect("valid");
|
||||||
assert!(high > low);
|
assert!(high > low);
|
||||||
// Ten decibels of power is ten times the temperature.
|
// Ten decibels of power is ten times the temperature.
|
||||||
|
// 十分贝的功率是温度的十倍。
|
||||||
assert!((high / low - 10.0).abs() < 1e-6);
|
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);
|
assert!((summary.carrier_to_interference.db() - 10.0).abs() < TOLERANCE);
|
||||||
// The combined ratio is below both the carrier to noise and the carrier
|
// The combined ratio is below both the carrier to noise and the carrier
|
||||||
// to interference ratio.
|
// to interference ratio.
|
||||||
|
// 合并之比低于载噪比与载干比二者。
|
||||||
let combined = summary
|
let combined = summary
|
||||||
.carrier_to_noise_plus_interference
|
.carrier_to_noise_plus_interference
|
||||||
.expect("noise was given");
|
.expect("noise was given");
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,11 @@
|
||||||
//! The expected values are computed by hand from the link equation and
|
//! 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
|
//! 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.
|
//! failing assertion rather than as a silently different budget.
|
||||||
|
//!
|
||||||
|
//! 链路预算的测试。
|
||||||
|
//!
|
||||||
|
//! 期望值由手工从链路方程计算并记录在此,使该链任何部分的改动都表现为断言失败,
|
||||||
|
//! 而非无声地不同的预算。
|
||||||
|
|
||||||
use apsis_link::{
|
use apsis_link::{
|
||||||
Decibel, Gain, LinkBudget, LinkGeometry, NoiseTemperature, Power, carrier_to_noise_density,
|
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
|
/// A representative Ku band downlink: a ten watt transmitter, a three meter
|
||||||
/// ground station, a 36 MHz transponder, and a 150 K system temperature.
|
/// ground station, a 36 MHz transponder, and a 150 K system temperature.
|
||||||
|
///
|
||||||
|
/// 一个有代表性的 Ku 频段下行:十瓦发射机、三米地面站、36 MHz 转发器,以及
|
||||||
|
/// 150 K 的系统温度。
|
||||||
fn downlink() -> LinkBudget {
|
fn downlink() -> LinkBudget {
|
||||||
LinkBudget {
|
LinkBudget {
|
||||||
transmit_power: Power::from_dbw(10.0).expect("finite"),
|
transmit_power: Power::from_dbw(10.0).expect("finite"),
|
||||||
|
|
@ -33,6 +41,7 @@ fn downlink() -> LinkBudget {
|
||||||
fn eirp_sums_the_power_gain_and_loss() {
|
fn eirp_sums_the_power_gain_and_loss() {
|
||||||
let budget = downlink();
|
let budget = downlink();
|
||||||
// 10 dBW + 30 dBi - 1 dB = 39 dBW.
|
// 10 dBW + 30 dBi - 1 dB = 39 dBW.
|
||||||
|
// 10 dBW + 30 dBi - 1 dB = 39 dBW。
|
||||||
let eirp = budget.eirp().expect("valid budget");
|
let eirp = budget.eirp().expect("valid budget");
|
||||||
assert!((eirp.dbw().expect("positive") - 39.0).abs() < TOLERANCE);
|
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");
|
let loss = budget.geometry.path_loss().expect("valid geometry");
|
||||||
// 4 pi d / lambda with d = 38 000 km and lambda = 0.02498 m gives
|
// 4 pi d / lambda with d = 38 000 km and lambda = 0.02498 m gives
|
||||||
// 205.627 dB.
|
// 205.627 dB.
|
||||||
|
// 4 pi d / lambda,取 d = 38 000 km、lambda = 0.02498 m,给出 205.627 dB。
|
||||||
assert!(
|
assert!(
|
||||||
(loss.db() - 205.627_080_075_172_1).abs() < 1e-9,
|
(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 budget = downlink();
|
||||||
let carrier = budget.received_power().expect("valid budget");
|
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.
|
||||||
|
// 39 dBW - 205.627 dB + 35 dBi - 0.5 dB = -132.127 dBW。
|
||||||
assert!(
|
assert!(
|
||||||
(carrier.dbw().expect("positive") + 132.127_080_075_172_1).abs() < 1e-9,
|
(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;
|
let expected = apsis_link::BOLTZMANN * 150.0;
|
||||||
assert!((density - expected).abs() < 1e-40);
|
assert!((density - expected).abs() < 1e-40);
|
||||||
// In decibels this is -206.838 dBW/Hz.
|
// In decibels this is -206.838 dBW/Hz.
|
||||||
|
// 以分贝表示为 -206.838 dBW/Hz。
|
||||||
let db = 10.0 * density.log10();
|
let db = 10.0 * density.log10();
|
||||||
assert!((db + 206.838_254_582_660_85).abs() < 1e-9, "{db}");
|
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 narrow_ratio = narrow.carrier_to_noise().expect("valid");
|
||||||
let wide_ratio = wide.carrier_to_noise().expect("valid");
|
let wide_ratio = wide.carrier_to_noise().expect("valid");
|
||||||
// A factor of ten in bandwidth is ten decibels.
|
// A factor of ten in bandwidth is ten decibels.
|
||||||
|
// 带宽十倍即十分贝。
|
||||||
assert!((narrow_ratio.db() - wide_ratio.db() - 10.0).abs() < 1e-9);
|
assert!((narrow_ratio.db() - wide_ratio.db() - 10.0).abs() < 1e-9);
|
||||||
// The carrier to noise density ratio does not depend on bandwidth.
|
// The carrier to noise density ratio does not depend on bandwidth.
|
||||||
|
// 载噪密度比不依赖于带宽。
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
narrow.carrier_to_noise_density(),
|
narrow.carrier_to_noise_density(),
|
||||||
wide.carrier_to_noise_density()
|
wide.carrier_to_noise_density()
|
||||||
|
|
@ -154,6 +168,7 @@ fn added_noise_raises_the_system_temperature() {
|
||||||
.expect("valid temperature");
|
.expect("valid temperature");
|
||||||
assert_eq!(warmed.system_temperature.kelvin(), 200.0);
|
assert_eq!(warmed.system_temperature.kelvin(), 200.0);
|
||||||
// The carrier to noise density ratio falls by 10 log10(200 / 150).
|
// 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 before = budget.carrier_to_noise_density().expect("valid");
|
||||||
let after = warmed.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();
|
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());
|
assert!(margin.closes());
|
||||||
|
|
||||||
// A link that exactly meets the requirement closes with zero margin.
|
// A link that exactly meets the requirement closes with zero margin.
|
||||||
|
// 恰好满足要求的链路以零余量闭合。
|
||||||
let exact = link_margin(required, required);
|
let exact = link_margin(required, required);
|
||||||
assert_eq!(exact.margin().db(), 0.0);
|
assert_eq!(exact.margin().db(), 0.0);
|
||||||
assert!(exact.closes());
|
assert!(exact.closes());
|
||||||
|
|
||||||
// A link below the requirement does not close.
|
// A link below the requirement does not close.
|
||||||
|
// 低于要求的链路不闭合。
|
||||||
let short = link_margin(Decibel::from_db(65.0), required);
|
let short = link_margin(Decibel::from_db(65.0), required);
|
||||||
assert!(!short.closes());
|
assert!(!short.closes());
|
||||||
assert!((short.margin().db() + 5.0).abs() < TOLERANCE);
|
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);
|
assert!((ratio - expected).abs() < 1e-9);
|
||||||
|
|
||||||
// In decibels this is 35 - 10 log10(150), which is 13.239087 dB/K.
|
// 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();
|
let db = 10.0 * ratio.log10();
|
||||||
assert!((db - 13.239_087_409_443_187).abs() < 1e-9, "{db}");
|
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");
|
let hot = gain_to_temperature(gain, NoiseTemperature::from_kelvin(200.0)).expect("valid");
|
||||||
assert!(cold > hot);
|
assert!(cold > hot);
|
||||||
// Doubling the temperature halves the ratio, which is three decibels.
|
// 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);
|
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
|
// 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
|
// decibel, is used here to confirm the budget accepts a gain derived
|
||||||
// from the antenna layer.
|
// from the antenna layer.
|
||||||
|
// 此处使用 12 GHz 下三米天线的接收增益(舍入到一分贝),以确认预算接受由天线层
|
||||||
|
// 导出的增益。
|
||||||
use apsis_link::ParabolicAperture;
|
use apsis_link::ParabolicAperture;
|
||||||
let dish = ParabolicAperture {
|
let dish = ParabolicAperture {
|
||||||
diameter: 3.0,
|
diameter: 3.0,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for the noise cascade.
|
//! Tests for the noise cascade.
|
||||||
|
//!
|
||||||
|
//! 噪声级联的测试。
|
||||||
|
|
||||||
use apsis_noise::{
|
use apsis_noise::{
|
||||||
NoiseTemperature, Stage, cascade_noise_figure_db, cascade_temperature,
|
NoiseTemperature, Stage, cascade_noise_figure_db, cascade_temperature,
|
||||||
|
|
@ -9,6 +11,8 @@ use apsis_rf::Gain;
|
||||||
const TOLERANCE: f64 = 1e-9;
|
const TOLERANCE: f64 = 1e-9;
|
||||||
|
|
||||||
/// A stage that adds the given temperature with the given gain in decibels.
|
/// A stage that adds the given temperature with the given gain in decibels.
|
||||||
|
///
|
||||||
|
/// 一级,以给定分贝增益加入给定温度。
|
||||||
fn stage(temperature: f64, gain_dbi: f64) -> Stage {
|
fn stage(temperature: f64, gain_dbi: f64) -> Stage {
|
||||||
Stage::new(
|
Stage::new(
|
||||||
NoiseTemperature::from_kelvin(temperature),
|
NoiseTemperature::from_kelvin(temperature),
|
||||||
|
|
@ -38,6 +42,7 @@ fn an_empty_cascade_has_no_noise() {
|
||||||
fn the_first_stage_dominates() {
|
fn the_first_stage_dominates() {
|
||||||
// A 100 K first stage with 20 dB of gain followed by a 1000 K second
|
// A 100 K first stage with 20 dB of gain followed by a 1000 K second
|
||||||
// stage contributes the second stage divided by 100.
|
// 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 cascade = [stage(100.0, 20.0), stage(1_000.0, 20.0)];
|
||||||
let total = cascade_temperature(&cascade).expect("valid stages");
|
let total = cascade_temperature(&cascade).expect("valid stages");
|
||||||
assert!((total.kelvin() - 110.0).abs() < 1e-6, "{}", total.kelvin());
|
assert!((total.kelvin() - 110.0).abs() < 1e-6, "{}", total.kelvin());
|
||||||
|
|
@ -56,6 +61,7 @@ fn a_noiseless_stage_contributes_nothing() {
|
||||||
#[test]
|
#[test]
|
||||||
fn the_friis_formula_matches_by_hand() {
|
fn the_friis_formula_matches_by_hand() {
|
||||||
// Three stages: 50 K at 20 dB, 100 K at 10 dB, 200 K at 0 dB.
|
// 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 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 total = cascade_temperature(&cascade).expect("valid stages");
|
||||||
let expected = 50.0 + 100.0 / 100.0 + 200.0 / (100.0 * 10.0);
|
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
|
// 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
|
// temperature divided by the gain of the stages before it, which for a
|
||||||
// loss is a division by a fraction and so an increase.
|
// loss is a division by a fraction and so an increase.
|
||||||
|
// 3 dB 损耗是二分之一增益,它贡献自身温度除以其前各级的增益,而对损耗而言即除以
|
||||||
|
// 一个分数,故是增大。
|
||||||
let loss = Stage::new(
|
let loss = Stage::new(
|
||||||
NoiseTemperature::from_kelvin(0.0),
|
NoiseTemperature::from_kelvin(0.0),
|
||||||
Gain::from_dbi(-3.0).expect("finite"),
|
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 receiver = stage(100.0, 30.0);
|
||||||
let total = cascade_temperature(&[loss, receiver]).expect("valid stages");
|
let total = cascade_temperature(&[loss, receiver]).expect("valid stages");
|
||||||
// The receiver contributes 100 / 0.5012 = 199.5 K.
|
// The receiver contributes 100 / 0.5012 = 199.5 K.
|
||||||
|
// 接收机贡献 100 / 0.5012 = 199.5 K。
|
||||||
assert!(
|
assert!(
|
||||||
total.kelvin() > 199.0 && total.kelvin() < 200.0,
|
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
|
// 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
|
// 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.
|
// 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 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 mixer = Stage::from_noise_figure(10.0, 0.0).expect("valid");
|
||||||
let figure = cascade_noise_figure_db(&[low_noise, mixer]).expect("valid stages");
|
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() {
|
fn gain_ordering_matters() {
|
||||||
// Placing the noisy stage first gives a worse cascade than placing it
|
// Placing the noisy stage first gives a worse cascade than placing it
|
||||||
// second, which is why a low noise amplifier goes at the front.
|
// second, which is why a low noise amplifier goes at the front.
|
||||||
|
// 把噪声大的级放在最前给出的级联比放在第二差,这正是低噪声放大器置于前端的原因。
|
||||||
let noisy = stage(500.0, 10.0);
|
let noisy = stage(500.0, 10.0);
|
||||||
let quiet = stage(20.0, 20.0);
|
let quiet = stage(20.0, 20.0);
|
||||||
let quiet_first = cascade_temperature(&[quiet, noisy]).expect("valid");
|
let quiet_first = cascade_temperature(&[quiet, noisy]).expect("valid");
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for noise temperature and thermal noise power.
|
//! Tests for noise temperature and thermal noise power.
|
||||||
|
//!
|
||||||
|
//! 噪声温度与热噪声功率的测试。
|
||||||
|
|
||||||
use apsis_core::constant::BOLTZMANN;
|
use apsis_core::constant::BOLTZMANN;
|
||||||
use apsis_noise::{NoiseTemperature, REFERENCE_TEMPERATURE_K, thermal_noise_power};
|
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
|
// noise of a source at the reference temperature, so a stage whose
|
||||||
// excess noise equals the reference temperature has a factor of two,
|
// excess noise equals the reference temperature has a factor of two,
|
||||||
// which is 3.01 dB.
|
// which is 3.01 dB.
|
||||||
|
// 噪声系数定义为总噪声与参考温度下源的噪声之比,故超额噪声等于参考温度的级其
|
||||||
|
// 因子为二,即 3.01 dB。
|
||||||
let value = NoiseTemperature::from_kelvin(REFERENCE_TEMPERATURE_K);
|
let value = NoiseTemperature::from_kelvin(REFERENCE_TEMPERATURE_K);
|
||||||
assert!((value.noise_factor() - 2.0).abs() < TOLERANCE);
|
assert!((value.noise_factor() - 2.0).abs() < TOLERANCE);
|
||||||
assert!((value.noise_figure_db().expect("valid") - 3.010_299_956_639_812).abs() < 1e-12);
|
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() {
|
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
|
// A noise figure of 1 dB is a factor of 1.2589, so the temperature is
|
||||||
// 290 * 0.2589, about 75.1 K.
|
// 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");
|
let temperature = NoiseTemperature::from_noise_figure_db(1.0).expect("finite");
|
||||||
assert!(
|
assert!(
|
||||||
(temperature.kelvin() - 75.09).abs() < 0.01,
|
(temperature.kelvin() - 75.09).abs() < 0.01,
|
||||||
|
|
@ -97,6 +102,7 @@ fn thermal_noise_power_follows_the_boltzmann_relation() {
|
||||||
#[test]
|
#[test]
|
||||||
fn a_one_hertz_bandwidth_at_290_kelvin_is_minus_204_dbw() {
|
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 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");
|
let power = thermal_noise_power(NoiseTemperature::from_kelvin(290.0), 1.0).expect("valid");
|
||||||
assert!(
|
assert!(
|
||||||
(power.dbw().expect("positive") + 203.98).abs() < 0.01,
|
(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
|
// 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
|
// -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.
|
// -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 power = thermal_noise_power(NoiseTemperature::from_kelvin(290.0), 36.0e6).expect("valid");
|
||||||
let dbw = power.dbw().expect("positive");
|
let dbw = power.dbw().expect("positive");
|
||||||
assert!((dbw + 128.41).abs() < 0.01, "{dbw} dBW");
|
assert!((dbw + 128.41).abs() < 0.01, "{dbw} dBW");
|
||||||
|
|
@ -123,6 +131,7 @@ fn noise_power_scales_with_temperature_and_bandwidth() {
|
||||||
let double_temperature =
|
let double_temperature =
|
||||||
thermal_noise_power(NoiseTemperature::from_kelvin(200.0), 1.0).expect("valid");
|
thermal_noise_power(NoiseTemperature::from_kelvin(200.0), 1.0).expect("valid");
|
||||||
// Each doubling of either factor adds three decibels.
|
// Each doubling of either factor adds three decibels.
|
||||||
|
// 任一因子每加倍一次加三分贝。
|
||||||
assert!(
|
assert!(
|
||||||
(hundred_hertz.dbw().expect("positive") - one_hertz.dbw().expect("positive") - 20.0).abs()
|
(hundred_hertz.dbw().expect("positive") - one_hertz.dbw().expect("positive") - 20.0).abs()
|
||||||
< 1e-9
|
< 1e-9
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for the decibel scale.
|
//! Tests for the decibel scale.
|
||||||
|
//!
|
||||||
|
//! 分贝尺度的测试。
|
||||||
|
|
||||||
use apsis_rf::Decibel;
|
use apsis_rf::Decibel;
|
||||||
|
|
||||||
|
|
@ -10,6 +12,7 @@ fn unity_and_doubling_have_the_expected_readings() {
|
||||||
assert!((Decibel::UNITY.ratio() - 1.0).abs() < TOLERANCE);
|
assert!((Decibel::UNITY.ratio() - 1.0).abs() < TOLERANCE);
|
||||||
// Three decibels is a ratio of 1.9953, not exactly two, which is the
|
// Three decibels is a ratio of 1.9953, not exactly two, which is the
|
||||||
// reason the doubling constant is an approximation in practice.
|
// 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.db() - 3.010_299_956_639_812).abs() < TOLERANCE);
|
||||||
assert!((Decibel::DOUBLING.ratio() - 2.0).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() {
|
fn amplitude_ratios_use_twenty_per_decade() {
|
||||||
// A voltage ratio of ten is twenty decibels, because power is
|
// A voltage ratio of ten is twenty decibels, because power is
|
||||||
// proportional to the square of the amplitude.
|
// proportional to the square of the amplitude.
|
||||||
|
// 电压比十即二十分贝,因为功率与幅度的平方成正比。
|
||||||
let value = Decibel::from_amplitude_ratio(10.0).expect("positive");
|
let value = Decibel::from_amplitude_ratio(10.0).expect("positive");
|
||||||
assert!((value.db() - 20.0).abs() < TOLERANCE);
|
assert!((value.db() - 20.0).abs() < TOLERANCE);
|
||||||
assert!((value.amplitude_ratio() - 10.0).abs() < 1e-9);
|
assert!((value.amplitude_ratio() - 10.0).abs() < 1e-9);
|
||||||
|
|
@ -52,10 +56,12 @@ fn non_positive_ratios_are_rejected() {
|
||||||
#[test]
|
#[test]
|
||||||
fn addition_multiplies_the_ratio() {
|
fn addition_multiplies_the_ratio() {
|
||||||
// The exact doubling constant added to itself quadruples the ratio.
|
// The exact doubling constant added to itself quadruples the ratio.
|
||||||
|
// 精确的加倍常量自加使其比变为四倍。
|
||||||
let total = Decibel::DOUBLING + Decibel::DOUBLING;
|
let total = Decibel::DOUBLING + Decibel::DOUBLING;
|
||||||
assert!((total.ratio() - 4.0).abs() < 1e-9);
|
assert!((total.ratio() - 4.0).abs() < 1e-9);
|
||||||
|
|
||||||
// Two ten decibel gains make a gain of one hundred.
|
// Two ten decibel gains make a gain of one hundred.
|
||||||
|
// 两个十分贝增益合成一百倍增益。
|
||||||
let decades = Decibel::from_db(10.0) + Decibel::from_db(10.0);
|
let decades = Decibel::from_db(10.0) + Decibel::from_db(10.0);
|
||||||
assert!((decades.db() - 20.0).abs() < TOLERANCE);
|
assert!((decades.db() - 20.0).abs() < TOLERANCE);
|
||||||
assert!((decades.ratio() - 100.0).abs() < 1e-9);
|
assert!((decades.ratio() - 100.0).abs() < 1e-9);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for free space propagation.
|
//! Tests for free space propagation.
|
||||||
|
//!
|
||||||
|
//! 自由空间传播的测试。
|
||||||
|
|
||||||
use apsis_core::constant::SPEED_OF_LIGHT;
|
use apsis_core::constant::SPEED_OF_LIGHT;
|
||||||
use apsis_rf::{Gain, free_space_path_loss, free_space_received_ratio, wavelength};
|
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() {
|
fn wavelength_is_the_speed_of_light_over_frequency() {
|
||||||
assert!((wavelength(SPEED_OF_LIGHT).expect("valid") - 1.0).abs() < TOLERANCE);
|
assert!((wavelength(SPEED_OF_LIGHT).expect("valid") - 1.0).abs() < TOLERANCE);
|
||||||
// A frequency of 300 MHz has a wavelength of about one meter.
|
// 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);
|
assert!((wavelength(300.0e6).expect("valid") - 0.999_308).abs() < 1e-5);
|
||||||
// The same relation holds for a microwave frequency.
|
// The same relation holds for a microwave frequency.
|
||||||
|
// 同一关系对微波频率也成立。
|
||||||
assert!((wavelength(12.0e9).expect("valid") - 0.024_982_7).abs() < 1e-6);
|
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() {
|
fn path_loss_matches_the_closed_form() {
|
||||||
// Attenuation over one meter at one meter wavelength: the ratio is
|
// Attenuation over one meter at one meter wavelength: the ratio is
|
||||||
// (4 pi)^2, about 21.98 dB.
|
// (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 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();
|
let expected = 10.0 * (4.0 * core::f64::consts::PI).powi(2).log10();
|
||||||
assert!((loss.db() - expected).abs() < 1e-9, "loss {}", loss.db());
|
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() {
|
fn path_loss_matches_the_engineering_form() {
|
||||||
// The form used in link budgets, with distance in kilometers and
|
// The form used in link budgets, with distance in kilometers and
|
||||||
// frequency in megahertz,
|
// 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)
|
||||||
|
// L = 32.44 + 20 log10(d_km) + 20 log10(f_MHz)
|
||||||
//
|
//
|
||||||
// has a constant that follows from the speed of light rounded to
|
// 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
|
// 299 792 458 m/s, and agrees with the expression used here to the
|
||||||
// precision of that constant.
|
// precision of that constant.
|
||||||
|
// 其常数由舍入到 299 792 458 m/s 的光速得出,并在该常量的精度内与此处所用的
|
||||||
|
// 表达式一致。
|
||||||
let distance_km = 1_000.0;
|
let distance_km = 1_000.0;
|
||||||
let frequency_mhz = 12_000.0;
|
let frequency_mhz = 12_000.0;
|
||||||
let loss = free_space_path_loss(distance_km * 1_000.0, frequency_mhz * 1.0e6)
|
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() {
|
fn a_zero_or_negative_distance_is_rejected() {
|
||||||
// A zero distance is outside the far field range where the expression
|
// A zero distance is outside the far field range where the expression
|
||||||
// applies, and would give an infinite loss in decibels.
|
// 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(0.0, 1.0e9), None);
|
||||||
assert_eq!(free_space_path_loss(-1.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
|
// 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
|
// reason a link budget cannot be read without also checking the
|
||||||
// distance regime.
|
// distance regime.
|
||||||
|
// 两个 40 dBi 的天线,在十米与 12 GHz 下。那里的扩散损耗约为 74 dB,故 80 dB 的
|
||||||
|
// 合成增益留下正的比值。这是一对大天线的近场,也正是链路预算不能在不检查距离
|
||||||
|
// 区间的情况下阅读的原因。
|
||||||
let ratio = free_space_received_ratio(
|
let ratio = free_space_received_ratio(
|
||||||
10.0,
|
10.0,
|
||||||
12.0e9,
|
12.0e9,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for antenna gain.
|
//! Tests for antenna gain.
|
||||||
|
//!
|
||||||
|
//! 天线增益的测试。
|
||||||
|
|
||||||
use apsis_rf::{DIPOLE_OVER_ISOTROPIC_DB, Gain, HALF_WAVE_DIPOLE_RATIO};
|
use apsis_rf::{DIPOLE_OVER_ISOTROPIC_DB, Gain, HALF_WAVE_DIPOLE_RATIO};
|
||||||
use core::f64::consts::PI;
|
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,
|
// The linear ratio and the decibel offset are derived from each other,
|
||||||
// so the two must agree.
|
// so the two must agree.
|
||||||
|
// 线性比值与分贝偏移由彼此导出,故二者必须一致。
|
||||||
assert!((Gain::HALF_WAVE_DIPOLE.ratio() - HALF_WAVE_DIPOLE_RATIO).abs() < 1e-15);
|
assert!((Gain::HALF_WAVE_DIPOLE.ratio() - HALF_WAVE_DIPOLE_RATIO).abs() < 1e-15);
|
||||||
assert!(
|
assert!(
|
||||||
(Gain::HALF_WAVE_DIPOLE.dbi().expect("positive") - DIPOLE_OVER_ISOTROPIC_DB).abs() < 1e-12
|
(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]
|
#[test]
|
||||||
fn effective_aperture_follows_the_standard_relation() {
|
fn effective_aperture_follows_the_standard_relation() {
|
||||||
// A gain of one has an aperture of lambda^2 / (4 pi).
|
// A gain of one has an aperture of lambda^2 / (4 pi).
|
||||||
|
// 增益为一的孔径为 lambda^2 / (4 pi)。
|
||||||
let gain = Gain::ISOTROPIC;
|
let gain = Gain::ISOTROPIC;
|
||||||
let aperture = gain.effective_aperture(0.3).expect("valid wavelength");
|
let aperture = gain.effective_aperture(0.3).expect("valid wavelength");
|
||||||
assert!((aperture - 0.3 * 0.3 / (4.0 * PI)).abs() < TOLERANCE);
|
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");
|
let large = gain.effective_aperture(0.1).expect("valid");
|
||||||
assert!(large > small);
|
assert!(large > small);
|
||||||
// The aperture grows with the square of the wavelength.
|
// The aperture grows with the square of the wavelength.
|
||||||
|
// 孔径随波长的平方增大。
|
||||||
assert!((large / small - 100.0).abs() < 1e-6);
|
assert!((large / small - 100.0).abs() < 1e-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for power levels.
|
//! Tests for power levels.
|
||||||
|
//!
|
||||||
|
//! 功率电平的测试。
|
||||||
|
|
||||||
use apsis_rf::{Decibel, Power};
|
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::WATT.dbw(), Some(0.0));
|
||||||
assert_eq!(Power::MILLIWATT.dbm(), Some(0.0));
|
assert_eq!(Power::MILLIWATT.dbm(), Some(0.0));
|
||||||
// One watt is thirty decibel milliwatts.
|
// One watt is thirty decibel milliwatts.
|
||||||
|
// 一瓦是三十毫瓦分贝。
|
||||||
assert!((Power::WATT.dbm().expect("positive") - 30.0).abs() < TOLERANCE);
|
assert!((Power::WATT.dbm().expect("positive") - 30.0).abs() < TOLERANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,11 +44,13 @@ fn dbw_and_dbm_differ_by_thirty() {
|
||||||
#[test]
|
#[test]
|
||||||
fn known_power_levels_convert_correctly() {
|
fn known_power_levels_convert_correctly() {
|
||||||
// Ten watts is ten decibel watts and forty decibel milliwatts.
|
// Ten watts is ten decibel watts and forty decibel milliwatts.
|
||||||
|
// 十瓦是十分贝瓦与四十毫瓦分贝。
|
||||||
let ten_watts = Power::from_watts(10.0);
|
let ten_watts = Power::from_watts(10.0);
|
||||||
assert!((ten_watts.dbw().expect("positive") - 10.0).abs() < 1e-12);
|
assert!((ten_watts.dbw().expect("positive") - 10.0).abs() < 1e-12);
|
||||||
assert!((ten_watts.dbm().expect("positive") - 40.0).abs() < 1e-12);
|
assert!((ten_watts.dbm().expect("positive") - 40.0).abs() < 1e-12);
|
||||||
|
|
||||||
// One milliwatt is one milliwatt.
|
// One milliwatt is one milliwatt.
|
||||||
|
// 一毫瓦即一毫瓦。
|
||||||
assert!((Power::from_milliwatts(1.0).watts() - 1.0e-3).abs() < 1e-18);
|
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);
|
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);
|
assert!((ratio.db() - 20.0).abs() < 1e-12);
|
||||||
|
|
||||||
// The reverse ratio is the negative.
|
// The reverse ratio is the negative.
|
||||||
|
// 反向之比是负数。
|
||||||
let inverse = low.ratio_in_decibels(high).expect("both are positive");
|
let inverse = low.ratio_in_decibels(high).expect("both are positive");
|
||||||
assert!((inverse.db() + 20.0).abs() < 1e-12);
|
assert!((inverse.db() + 20.0).abs() < 1e-12);
|
||||||
}
|
}
|
||||||
|
|
@ -113,5 +119,6 @@ fn validity_is_reported() {
|
||||||
fn display_prefers_decibel_watts() {
|
fn display_prefers_decibel_watts() {
|
||||||
assert_eq!(Power::from_watts(1.0).to_string(), "0 dBW");
|
assert_eq!(Power::from_watts(1.0).to_string(), "0 dBW");
|
||||||
// A zero power has no decibel form, so the linear value is shown.
|
// A zero power has no decibel form, so the linear value is shown.
|
||||||
|
// 零功率没有分贝形式,故显示线性值。
|
||||||
assert_eq!(Power::from_watts(0.0).to_string(), "0 W");
|
assert_eq!(Power::from_watts(0.0).to_string(), "0 W");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue