apsis-rs/crates/rf/apsis-atmosphere/tests/p835.rs

171 lines
7.1 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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);
}