rf: add carrier to interference and noise combination
This commit is contained in:
parent
1581a1951c
commit
8144df75a3
|
|
@ -87,6 +87,10 @@ version = "0.1.0"
|
|||
[[package]]
|
||||
name = "apsis-interference"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"apsis-core",
|
||||
"apsis-rf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "apsis-isl"
|
||||
|
|
|
|||
|
|
@ -11,3 +11,5 @@ publish = false
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
apsis-core.workspace = true
|
||||
apsis-rf.workspace = true
|
||||
|
|
|
|||
|
|
@ -1 +1,58 @@
|
|||
//! Interference and carrier-to-interference computation.
|
||||
//!
|
||||
//! A wanted signal rarely arrives alone. This crate combines the wanted
|
||||
//! carrier with one or more interfering carriers and with noise, and reports
|
||||
//! the ratios that decide whether the wanted link is usable.
|
||||
//!
|
||||
//! The figures computed here are:
|
||||
//!
|
||||
//! - The carrier to interference ratio, over one interferer or several.
|
||||
//! - The carrier to noise plus interference ratio, which is what a receiver
|
||||
//! actually sees, because it cannot separate the two.
|
||||
//! - The equivalent noise temperature of an interferer, which lets an
|
||||
//! interference contribution be folded into the link budget as if it were
|
||||
//! noise.
|
||||
//!
|
||||
//! Interference from a second satellite sharing the same frequency is
|
||||
//! described by the angle between the two satellites as seen from the ground
|
||||
//! station. The receive antenna has a sidelobe response in that direction,
|
||||
//! and the ratio of the interferer's power to the wanted power follows from
|
||||
//! the two gains and the two path losses. This crate takes the two gains as
|
||||
//! inputs, so it composes with the pattern models in `apsis-antenna`.
|
||||
//!
|
||||
//! Source: the definitions of carrier to interference and carrier to noise
|
||||
//! plus interference, and the practice of expressing interference as an
|
||||
//! equivalent noise temperature, are standard in satellite link design and
|
||||
//! appear for example in the ITU-R Handbook on Satellite Communications.
|
||||
//!
|
||||
//! ```
|
||||
//! use apsis_interference::{Interferer, carrier_to_interference, combine};
|
||||
//! use apsis_rf::{Decibel, Power};
|
||||
//!
|
||||
//! let wanted = Power::from_dbw(-130.0).expect("finite");
|
||||
//! let interferer = Interferer {
|
||||
//! power: Power::from_dbw(-140.0).expect("finite"),
|
||||
//! };
|
||||
//!
|
||||
//! // One interferer ten decibels below the carrier leaves a C/I of 10 dB.
|
||||
//! let ratio = carrier_to_interference(wanted, &[interferer]).expect("valid");
|
||||
//! assert!((ratio.db() - 10.0).abs() < 1e-9);
|
||||
//!
|
||||
//! // Combining with noise can only lower the result.
|
||||
//! let combined = combine(ratio, Decibel::from_db(15.0)).expect("valid");
|
||||
//! assert!(combined.db() < ratio.db());
|
||||
//! ```
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
mod ratio;
|
||||
|
||||
pub use ratio::{
|
||||
InterferenceSummary, Interferer, carrier_to_interference, carrier_to_interference_single,
|
||||
carrier_to_noise_plus_interference, combine, equivalent_noise_temperature,
|
||||
interference_to_noise_ratio, summarise, total_interference,
|
||||
};
|
||||
|
||||
pub use apsis_core::constant::BOLTZMANN;
|
||||
pub use apsis_rf::{Decibel, Power};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,220 @@
|
|||
//! Carrier to interference ratios and their combination with noise.
|
||||
|
||||
use apsis_core::constant::BOLTZMANN;
|
||||
use apsis_rf::{Decibel, Power};
|
||||
|
||||
/// One interfering carrier seen at the receiver input.
|
||||
///
|
||||
/// The power is the interfering power that reaches the receiver, already
|
||||
/// including the transmit power, the transmit gain, the path loss, the
|
||||
/// receive antenna gain in the direction of the interferer, and any
|
||||
/// propagation loss. This crate combines the powers; it does not compute the
|
||||
/// geometry.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Interferer {
|
||||
/// The interfering power at the receiver input, in watts.
|
||||
pub power: Power,
|
||||
}
|
||||
|
||||
impl Interferer {
|
||||
/// Creates an interferer from a power at the receiver input.
|
||||
pub const fn new(power: Power) -> Self {
|
||||
Self { power }
|
||||
}
|
||||
}
|
||||
|
||||
/// The combined result of a wanted carrier and its interference.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct InterferenceSummary {
|
||||
/// The wanted carrier power, in watts.
|
||||
pub carrier: Power,
|
||||
/// The total interfering power, in watts.
|
||||
pub interference: Power,
|
||||
/// The carrier to interference ratio.
|
||||
pub carrier_to_interference: Decibel,
|
||||
/// The carrier to noise plus interference ratio, when a noise power was
|
||||
/// given.
|
||||
pub carrier_to_noise_plus_interference: Option<Decibel>,
|
||||
}
|
||||
|
||||
impl InterferenceSummary {
|
||||
/// Returns the interference to carrier ratio.
|
||||
///
|
||||
/// This is the reciprocal of the carrier to interference ratio and is
|
||||
/// the form some specifications use.
|
||||
pub fn interference_to_carrier(self) -> Decibel {
|
||||
self.carrier_to_interference.negated()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the total interfering power from a set of interferers.
|
||||
///
|
||||
/// Interfering powers add, because they are independent signals and their
|
||||
/// power is what matters, not their amplitudes. Two equal interferers
|
||||
/// therefore give twice the power of one, which is three decibels and not
|
||||
/// six.
|
||||
///
|
||||
/// Returns `None` when any interferer has an invalid power.
|
||||
pub fn total_interference(interferers: &[Interferer]) -> Option<Power> {
|
||||
let mut total = 0.0;
|
||||
for interferer in interferers {
|
||||
if !interferer.power.is_valid() {
|
||||
return None;
|
||||
}
|
||||
total += interferer.power.watts();
|
||||
}
|
||||
Some(Power::from_watts(total))
|
||||
}
|
||||
|
||||
/// Returns the carrier to interference ratio for a carrier and a set of
|
||||
/// interferers.
|
||||
///
|
||||
/// The ratio is the wanted carrier power divided by the sum of the
|
||||
/// interfering powers. A large ratio means the interferers are weak.
|
||||
///
|
||||
/// Returns `None` when the carrier is not positive, when any interferer is
|
||||
/// invalid, or when the total interference is zero, which would give an
|
||||
/// infinite ratio.
|
||||
pub fn carrier_to_interference(carrier: Power, interferers: &[Interferer]) -> Option<Decibel> {
|
||||
if !carrier.is_valid() || carrier.watts() <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
let interference = total_interference(interferers)?;
|
||||
if interference.watts() <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
Decibel::from_ratio(carrier.watts() / interference.watts())
|
||||
}
|
||||
|
||||
/// Returns the carrier to interference ratio for a single interferer.
|
||||
///
|
||||
/// This is a convenience for the common case of one dominant interferer.
|
||||
///
|
||||
/// Returns `None` when either power is not positive.
|
||||
pub fn carrier_to_interference_single(carrier: Power, interferer: Interferer) -> Option<Decibel> {
|
||||
carrier_to_interference(carrier, &[interferer])
|
||||
}
|
||||
|
||||
/// Combines a ratio with another ratio of the same kind.
|
||||
///
|
||||
/// When two independent impairments act together, their effects add as
|
||||
/// reciprocal ratios, which in decibels means
|
||||
///
|
||||
/// ```text
|
||||
/// 1 / R = 1 / R_1 + 1 / R_2
|
||||
/// ```
|
||||
///
|
||||
/// The result is always below the smaller of the two, so adding a second
|
||||
/// impairment can only lower the combined ratio. This is how a carrier to
|
||||
/// noise ratio and a carrier to interference ratio are combined into a
|
||||
/// carrier to noise plus interference ratio.
|
||||
///
|
||||
/// Returns `None` when either value is not finite.
|
||||
pub fn combine(first: Decibel, second: Decibel) -> Option<Decibel> {
|
||||
if !first.is_finite() || !second.is_finite() {
|
||||
return None;
|
||||
}
|
||||
// A ratio of zero in decibels means an infinite impairment, so the
|
||||
// combination is also infinite.
|
||||
let first_ratio = first.ratio();
|
||||
let second_ratio = second.ratio();
|
||||
if !first_ratio.is_finite() || !second_ratio.is_finite() {
|
||||
return None;
|
||||
}
|
||||
Decibel::from_ratio(1.0 / (1.0 / first_ratio + 1.0 / second_ratio))
|
||||
}
|
||||
|
||||
/// Returns the carrier to noise plus interference ratio.
|
||||
///
|
||||
/// This combines a carrier to noise ratio and a carrier to interference
|
||||
/// ratio through [`combine`]. It is the ratio a receiver actually
|
||||
/// experiences, because it cannot separate thermal noise from a competing
|
||||
/// carrier.
|
||||
///
|
||||
/// Returns `None` when either ratio is not finite.
|
||||
pub fn carrier_to_noise_plus_interference(
|
||||
carrier_to_noise: Decibel,
|
||||
carrier_to_interference: Decibel,
|
||||
) -> Option<Decibel> {
|
||||
combine(carrier_to_noise, carrier_to_interference)
|
||||
}
|
||||
|
||||
/// Returns the interference to noise ratio.
|
||||
///
|
||||
/// This compares the interfering power with the thermal noise power in the
|
||||
/// receiver. A value well below zero decibels means the interference is
|
||||
/// negligible against the noise and can be ignored; a value near or above
|
||||
/// zero means it must be accounted for.
|
||||
///
|
||||
/// Returns `None` when either power is not positive.
|
||||
pub fn interference_to_noise_ratio(interference: Power, noise: Power) -> Option<Decibel> {
|
||||
if !interference.is_valid() || !noise.is_valid() {
|
||||
return None;
|
||||
}
|
||||
if interference.watts() <= 0.0 || noise.watts() <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
Decibel::from_ratio(interference.watts() / noise.watts())
|
||||
}
|
||||
|
||||
/// Returns the noise temperature equivalent to an interfering power.
|
||||
///
|
||||
/// An interferer that a receiver treats as noise can be folded into the link
|
||||
/// budget as an increase in system noise temperature. In temperature the
|
||||
/// relation is
|
||||
///
|
||||
/// ```text
|
||||
/// T = P / (k B)
|
||||
/// ```
|
||||
///
|
||||
/// with `P` the interfering power, `k` the Boltzmann constant, and `B` the
|
||||
/// receiver noise bandwidth. The temperature is the value that, added to the
|
||||
/// system temperature, would raise the noise by the same amount as the
|
||||
/// interferer.
|
||||
///
|
||||
/// Returns `None` when the power is not positive or the bandwidth is not
|
||||
/// positive and finite.
|
||||
pub fn equivalent_noise_temperature(interference: Power, bandwidth: f64) -> Option<f64> {
|
||||
if !interference.is_valid() || interference.watts() <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
if !bandwidth.is_finite() || bandwidth <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
Some(interference.watts() / (BOLTZMANN * bandwidth))
|
||||
}
|
||||
|
||||
/// Returns a summary of a carrier with its interference.
|
||||
///
|
||||
/// When a noise power is given, the carrier to noise plus interference ratio
|
||||
/// is computed as well. Pass `None` for the noise power when only the
|
||||
/// interference is of interest.
|
||||
///
|
||||
/// Returns `None` when the carrier is not positive, when any interferer is
|
||||
/// invalid, or when the total interference is zero.
|
||||
pub fn summarise(
|
||||
carrier: Power,
|
||||
interferers: &[Interferer],
|
||||
noise: Option<Power>,
|
||||
) -> Option<InterferenceSummary> {
|
||||
let interference = total_interference(interferers)?;
|
||||
let carrier_to_interference = carrier_to_interference(carrier, interferers)?;
|
||||
|
||||
let carrier_to_noise_plus_interference = match noise {
|
||||
Some(noise) => {
|
||||
if !noise.is_valid() || noise.watts() <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
let carrier_to_noise = Decibel::from_ratio(carrier.watts() / noise.watts())?;
|
||||
Some(combine(carrier_to_noise, carrier_to_interference)?)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
Some(InterferenceSummary {
|
||||
carrier,
|
||||
interference,
|
||||
carrier_to_interference,
|
||||
carrier_to_noise_plus_interference,
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,282 @@
|
|||
//! 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
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue