///|
pub fn standard_uncertainty(values : Array[Double]) -> UncertaintyEstimate {
let summary = describe_samples(values)
let standard = if summary.count == 0 {
0.0
} else {
summary.standard_deviation / Double::from_int(summary.count).sqrt()
}
{
standard,
expanded: standard * 2.0,
coverage: 0.95,
source: "repeatability",
}
}
///|
pub fn uncertainty_from_components(
components : Array[UncertaintyEstimate],
) -> UncertaintyEstimate {
let mut variance = 0.0
let mut coverage = 1.0
for component in components {
variance += component.standard * component.standard
coverage = coverage.min(component.coverage)
}
let standard = variance.sqrt()
{ standard, expanded: standard * 2.0, coverage, source: "combined" }
}
///|
pub fn propagate_scale_uncertainty(
value : Double,
scale : Double,
scale_uncertainty : Double,
) -> UncertaintyEstimate {
let standard = value.abs() * scale.abs() * scale_uncertainty.abs()
{ standard, expanded: standard * 2.0, coverage: 0.95, source: "scale" }
}
///|
pub fn propagate_addition(
first : UncertaintyEstimate,
second : UncertaintyEstimate,
) -> UncertaintyEstimate {
uncertainty_from_components([first, second])
}
///|
pub fn propagate_product(
value : Double,
uncertainty : UncertaintyEstimate,
factor : Double,
) -> UncertaintyEstimate {
let standard = value.abs() * uncertainty.standard * factor.abs()
{
standard,
expanded: standard * 2.0,
coverage: uncertainty.coverage,
source: "scaled",
}
}
///|
pub fn tolerance_guard_band(
tolerance : Tolerance,
uncertainty : UncertaintyEstimate,
) -> Tolerance {
{
..tolerance,
lower: tolerance.lower + uncertainty.expanded,
upper: tolerance.upper - uncertainty.expanded,
}
}
///|
pub fn measurement_with_uncertainty(
measurement : Measurement,
uncertainty : UncertaintyEstimate,
) -> String {
measurement.value.to_string() +
" +/- " +
uncertainty.expanded.to_string() +
" " +
measurement.unit
}
///|
pub fn confidence_from_uncertainty(
measurement : Measurement,
uncertainty : UncertaintyEstimate,
tolerance : Tolerance,
) -> Double {
let margin = (tolerance.upper - measurement.value)
.abs()
.min((measurement.value - tolerance.lower).abs())
if uncertainty.expanded <= 0.0000001 {
1.0
} else {
clamp_double(margin / uncertainty.expanded, 0.0, 1.0)
}
}
///|
pub fn repeatability_limit(
values : Array[Double],
multiplier : Double,
) -> Double {
multiplier.abs() * repeatability(values)
}
///|
pub fn reproducibility_between_groups(groups : Array[Array[Double]]) -> Double {
let means : Array[Double] = []
for group in groups {
means.push(mean_value(group))
}
standard_uncertainty(means).standard
}
///|
pub fn pooled_standard_deviation(groups : Array[Array[Double]]) -> Double {
let total_count = groups.fold(init=0, fn(total, group) {
total + group.length()
})
if total_count <= groups.length() {
0.0
} else {
let mut sum_squares = 0.0
for group in groups {
sum_squares += variance_value(group) *
Double::from_int((group.length() - 1).max(0))
}
(sum_squares / Double::from_int(total_count - groups.length())).sqrt()
}
}
///|
pub fn uncertainty_budget_report(
components : Array[UncertaintyEstimate],
) -> String {
let combined = uncertainty_from_components(components)
let out = StringBuilder()
out.write_string("source,standard,expanded,coverage\n")
for component in components {
out.write_string(component.source)
out.write_string(",")
out.write_string(component.standard.to_string())
out.write_string(",")
out.write_string(component.expanded.to_string())
out.write_string(",")
out.write_string(component.coverage.to_string())
out.write_string("\n")
}
out.write_string("combined,")
out.write_string(combined.standard.to_string())
out.write_string(",")
out.write_string(combined.expanded.to_string())
out.write_string(",")
out.write_string(combined.coverage.to_string())
out.write_string("\n")
out.to_string()
}
///|
pub fn guard_band_decision(
value : Double,
tolerance : Tolerance,
uncertainty : UncertaintyEstimate,
) -> MeasurementDecision {
let guarded = tolerance_guard_band(tolerance, uncertainty)
guarded.judge(value)
}
///|
pub fn uncertainty_ratio(
uncertainty : UncertaintyEstimate,
tolerance : Tolerance,
) -> Double {
let span = (tolerance.upper - tolerance.lower).abs()
if span <= 0.0000001 {
0.0
} else {
uncertainty.expanded / span
}
}
///|
pub fn uncertainty_is_acceptable(
uncertainty : UncertaintyEstimate,
tolerance : Tolerance,
maximum_ratio : Double,
) -> Bool {
uncertainty_ratio(uncertainty, tolerance) <= maximum_ratio.max(0.0)
}