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