use crate::math;
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct UncertaintyComponent {
pub name: &'static str,
pub ty: GumType,
pub standard_uncertainty: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum GumType {
A,
B,
}
impl GumType {
#[inline]
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::A => "TypeA",
Self::B => "TypeB",
}
}
}
#[must_use]
pub fn combined_standard_uncertainty(components: &[UncertaintyComponent]) -> Option<f64> {
if components.is_empty() {
return None;
}
let mut ssq = 0.0_f64;
for c in components {
let u = c.standard_uncertainty;
if !u.is_finite() || u < 0.0 {
return None;
}
ssq += u * u;
}
math::sqrt_f64(ssq)
}
#[must_use]
pub fn expanded_uncertainty(
components: &[UncertaintyComponent],
coverage_factor: f64,
) -> Option<f64> {
if !coverage_factor.is_finite() || coverage_factor < 0.0 {
return None;
}
let u_c = combined_standard_uncertainty(components)?;
Some(coverage_factor * u_c)
}
#[cfg(test)]
mod tests {
use super::*;
fn mk(u: f64, ty: GumType) -> UncertaintyComponent {
UncertaintyComponent { name: "test", ty, standard_uncertainty: u }
}
#[test]
fn empty_components_is_none() {
assert!(combined_standard_uncertainty(&[]).is_none());
}
#[test]
fn single_component_passthrough() {
let c = [mk(0.5, GumType::A)];
let u = combined_standard_uncertainty(&c).expect("finite");
assert!((u - 0.5).abs() < 1e-12);
}
#[test]
fn quadrature_sum() {
let c = [mk(3.0, GumType::A), mk(4.0, GumType::B)];
let u = combined_standard_uncertainty(&c).expect("finite");
assert!((u - 5.0).abs() < 1e-12);
}
#[test]
fn rejects_negative_or_non_finite() {
assert!(combined_standard_uncertainty(&[mk(-0.1, GumType::A)]).is_none());
assert!(combined_standard_uncertainty(&[mk(f64::NAN, GumType::A)]).is_none());
}
#[test]
fn expanded_is_k_times_combined() {
let c = [mk(3.0, GumType::A), mk(4.0, GumType::B)];
let u95 = expanded_uncertainty(&c, 2.0).expect("finite");
assert!((u95 - 10.0).abs() < 1e-12);
}
#[test]
fn labels_are_stable() {
assert_eq!(GumType::A.label(), "TypeA");
assert_eq!(GumType::B.label(), "TypeB");
}
}