apsis-rs/crates/rf/apsis-interference/tests/ratio.rs

283 lines
9.8 KiB
Rust

//! Tests for carrier to interference computation.
use apsis_interference::{
BOLTZMANN, Decibel, Interferer, Power, carrier_to_interference, carrier_to_interference_single,
carrier_to_noise_plus_interference, combine, equivalent_noise_temperature,
interference_to_noise_ratio, summarise, total_interference,
};
const TOLERANCE: f64 = 1e-9;
/// A wanted carrier at a convenient level.
fn carrier() -> Power {
Power::from_dbw(-130.0).expect("finite")
}
/// An interferer at the given level relative to the carrier.
fn interferer_at(decibels_below: f64) -> Interferer {
Interferer::new(Power::from_dbw(-130.0 - decibels_below).expect("finite"))
}
#[test]
fn a_single_interferer_gives_the_expected_ratio() {
// An interferer ten decibels below the carrier gives a ratio of 10 dB.
let ratio = carrier_to_interference_single(carrier(), interferer_at(10.0)).expect("valid");
assert!((ratio.db() - 10.0).abs() < TOLERANCE, "{}", ratio.db());
}
#[test]
fn an_equal_interferer_gives_zero_decibels() {
let ratio = carrier_to_interference_single(carrier(), interferer_at(0.0)).expect("valid");
assert!(ratio.db().abs() < TOLERANCE);
}
#[test]
fn an_interferer_above_the_carrier_gives_a_negative_ratio() {
let ratio = carrier_to_interference_single(carrier(), interferer_at(-5.0)).expect("valid");
assert!((ratio.db() + 5.0).abs() < TOLERANCE);
}
#[test]
fn interfering_powers_add() {
// Two equal interferers give twice the power of one, which is three
// decibels above one alone.
let one = total_interference(&[interferer_at(10.0)]).expect("valid");
let two = total_interference(&[interferer_at(10.0), interferer_at(10.0)]).expect("valid");
assert!(
(two.dbw().expect("positive") - one.dbw().expect("positive") - 3.010_299_956_639_812).abs()
< 1e-9
);
}
#[test]
fn an_empty_interferer_list_has_no_power() {
let total = total_interference(&[]).expect("empty is valid");
assert_eq!(total.watts(), 0.0);
// With no interference there is no ratio to compute.
assert_eq!(carrier_to_interference(carrier(), &[]), None);
}
#[test]
fn the_ratio_combines_multiple_interferers() {
// One interferer at 10 dB below gives 10 dB. Adding a second at the
// same level halves the ratio, which is three decibels less.
let single = carrier_to_interference(carrier(), &[interferer_at(10.0)]).expect("valid");
let pair = carrier_to_interference(carrier(), &[interferer_at(10.0), interferer_at(10.0)])
.expect("valid");
assert!(
(single.db() - pair.db() - 3.010_299_956_639_812).abs() < 1e-9,
"{} vs {}",
single.db(),
pair.db()
);
}
#[test]
fn a_dominant_interferer_hides_the_others() {
// An interferer twenty decibels weaker than the carrier contributes
// almost nothing to the total, so it barely changes the ratio.
let strong = carrier_to_interference(carrier(), &[interferer_at(10.0)]).expect("valid");
let with_weak = carrier_to_interference(carrier(), &[interferer_at(10.0), interferer_at(30.0)])
.expect("valid");
assert!(with_weak.db() < strong.db());
assert!(
strong.db() - with_weak.db() < 0.05,
"change was {}",
strong.db() - with_weak.db()
);
}
#[test]
fn invalid_inputs_are_rejected() {
let zero_carrier = Power::from_watts(0.0);
assert_eq!(
carrier_to_interference(zero_carrier, &[interferer_at(10.0)]),
None
);
let negative = Interferer::new(Power::from_watts(-1.0));
assert_eq!(carrier_to_interference(carrier(), &[negative]), None);
assert_eq!(total_interference(&[negative]), None);
// A zero interferer gives an infinite ratio, which has no finite
// decibel value here.
let zero_interferer = Interferer::new(Power::from_watts(0.0));
assert_eq!(carrier_to_interference(carrier(), &[zero_interferer]), None);
}
#[test]
fn combining_two_equal_ratios_lowers_the_result_by_three_decibels() {
let first = Decibel::from_db(20.0);
let second = Decibel::from_db(20.0);
let combined = combine(first, second).expect("valid");
assert!(
(combined.db() - 16.989_700_043_360_188).abs() < 1e-9,
"{}",
combined.db()
);
}
#[test]
fn combining_a_small_ratio_with_a_large_one_gives_the_small_one() {
// A carrier to noise ratio of 30 dB alongside a carrier to interference
// ratio of 5 dB is dominated by the interference.
let combined = combine(Decibel::from_db(30.0), Decibel::from_db(5.0)).expect("valid");
assert!(combined.db() < 5.0);
assert!(
(combined.db() - 4.986_288_071_673_166).abs() < 1e-9,
"{}",
combined.db()
);
}
#[test]
fn combining_is_symmetric() {
let first = Decibel::from_db(17.0);
let second = Decibel::from_db(9.0);
let forward = combine(first, second).expect("valid");
let backward = combine(second, first).expect("valid");
assert!((forward.db() - backward.db()).abs() < TOLERANCE);
}
#[test]
fn the_same_helper_is_available_for_noise_plus_interference() {
let noise = Decibel::from_db(30.0);
let interference = Decibel::from_db(5.0);
let direct = combine(noise, interference).expect("valid");
let named = carrier_to_noise_plus_interference(noise, interference).expect("valid");
assert!((direct.db() - named.db()).abs() < TOLERANCE);
}
#[test]
fn invalid_ratios_are_rejected_when_combining() {
assert_eq!(
combine(Decibel::from_db(f64::NAN), Decibel::from_db(1.0)),
None
);
assert_eq!(
combine(Decibel::from_db(1.0), Decibel::from_db(f64::INFINITY)),
None
);
}
#[test]
fn the_interference_to_noise_ratio_compares_two_powers() {
let interference = Power::from_dbw(-140.0).expect("finite");
let noise = Power::from_dbw(-130.0).expect("finite");
let ratio = interference_to_noise_ratio(interference, noise).expect("valid");
// The interference is ten decibels below the noise.
assert!((ratio.db() + 10.0).abs() < TOLERANCE);
}
#[test]
fn invalid_powers_are_rejected_in_the_interference_to_noise_ratio() {
let noise = Power::from_dbw(-130.0).expect("finite");
assert_eq!(
interference_to_noise_ratio(Power::from_watts(0.0), noise),
None
);
assert_eq!(
interference_to_noise_ratio(Power::from_watts(-1.0), noise),
None
);
assert_eq!(
interference_to_noise_ratio(
Power::from_dbw(-140.0).expect("finite"),
Power::from_watts(0.0)
),
None
);
}
#[test]
fn the_equivalent_noise_temperature_follows_the_boltzmann_relation() {
let interference = Power::from_dbw(-140.0).expect("finite");
let bandwidth = 36.0e6;
let temperature = equivalent_noise_temperature(interference, bandwidth).expect("valid");
let expected = interference.watts() / (BOLTZMANN * bandwidth);
assert!((temperature - expected).abs() < 1e-9);
// The interference is 1e-14 W, so the temperature is about 20 K.
assert!(
temperature > 15.0 && temperature < 25.0,
"temperature {temperature}"
);
}
#[test]
fn the_equivalent_temperature_grows_with_the_interfering_power() {
let low = equivalent_noise_temperature(Power::from_dbw(-150.0).expect("finite"), 1.0e6)
.expect("valid");
let high = equivalent_noise_temperature(Power::from_dbw(-140.0).expect("finite"), 1.0e6)
.expect("valid");
assert!(high > low);
// Ten decibels of power is ten times the temperature.
assert!((high / low - 10.0).abs() < 1e-6);
}
#[test]
fn the_equivalent_temperature_falls_with_bandwidth() {
let narrow = equivalent_noise_temperature(Power::from_dbw(-140.0).expect("finite"), 1.0e6)
.expect("valid");
let wide = equivalent_noise_temperature(Power::from_dbw(-140.0).expect("finite"), 10.0e6)
.expect("valid");
assert!(narrow > wide);
assert!((narrow / wide - 10.0).abs() < 1e-6);
}
#[test]
fn invalid_arguments_are_rejected_for_the_temperature() {
let interference = Power::from_dbw(-140.0).expect("finite");
assert_eq!(
equivalent_noise_temperature(Power::from_watts(0.0), 1.0e6),
None
);
assert_eq!(equivalent_noise_temperature(interference, 0.0), None);
assert_eq!(equivalent_noise_temperature(interference, -1.0), None);
assert_eq!(equivalent_noise_temperature(interference, f64::NAN), None);
}
#[test]
fn a_summary_reports_the_carrier_and_the_interference() {
let summary = summarise(
carrier(),
&[interferer_at(10.0)],
Some(Power::from_dbw(-140.0).expect("finite")),
)
.expect("valid");
assert_eq!(summary.carrier, carrier());
assert!(summary.interference.watts() > 0.0);
assert!((summary.carrier_to_interference.db() - 10.0).abs() < TOLERANCE);
// The combined ratio is below both the carrier to noise and the carrier
// to interference ratio.
let combined = summary
.carrier_to_noise_plus_interference
.expect("noise was given");
assert!(combined.db() < summary.carrier_to_interference.db());
}
#[test]
fn a_summary_without_noise_omits_the_combined_ratio() {
let summary = summarise(carrier(), &[interferer_at(10.0)], None).expect("valid");
assert_eq!(summary.carrier_to_noise_plus_interference, None);
}
#[test]
fn the_summary_reports_the_interference_to_carrier_ratio() {
let summary = summarise(carrier(), &[interferer_at(10.0)], None).expect("valid");
let interference_to_carrier = summary.interference_to_carrier();
assert!((interference_to_carrier.db() + 10.0).abs() < TOLERANCE);
}
#[test]
fn a_summary_rejects_invalid_inputs() {
let invalid = Interferer::new(Power::from_watts(-1.0));
assert_eq!(summarise(carrier(), &[invalid], None), None);
assert_eq!(summarise(carrier(), &[], None), None);
assert_eq!(
summarise(
carrier(),
&[interferer_at(10.0)],
Some(Power::from_watts(0.0))
),
None
);
}