///|
pub struct Uncertainty {
nominal : Float
lower : Float
upper : Float
confidence : Float
} derive(Debug, Eq)
///|
pub fn uncertainty(
nominal : Float,
lower : Float,
upper : Float,
confidence : Float,
) -> Uncertainty {
{ nominal, lower, upper, confidence }
}
///|
pub fn Uncertainty::width(self : Uncertainty) -> Float {
self.upper - self.lower
}
///|
pub fn Uncertainty::half_width(self : Uncertainty) -> Float {
self.width() / 2.0
}
///|
pub fn Uncertainty::relative_width(self : Uncertainty) -> Float {
if self.nominal == 0.0 {
0.0
} else {
self.width().abs() / self.nominal.abs()
}
}
///|
pub fn Uncertainty::contains(self : Uncertainty, value : Float) -> Bool {
value >= self.lower && value <= self.upper
}
///|
pub fn Uncertainty::is_valid(self : Uncertainty) -> Bool {
self.lower <= self.nominal &&
self.nominal <= self.upper &&
self.confidence >= 0.0 &&
self.confidence <= 1.0
}
///|
pub fn Uncertainty::expand(self : Uncertainty, factor : Float) -> Uncertainty {
let center = self.nominal
let half = self.half_width() * factor.abs()
{ ..self, lower: center - half, upper: center + half }
}
///|
pub fn Uncertainty::shift(self : Uncertainty, delta : Float) -> Uncertainty {
{
..self,
nominal: self.nominal + delta,
lower: self.lower + delta,
upper: self.upper + delta,
}
}
///|
pub struct SensitivityPoint {
parameter : String
change : Float
response : Float
} derive(Debug, Eq)
///|
pub fn sensitivity_point(
parameter : String,
change : Float,
response : Float,
) -> SensitivityPoint {
{ parameter, change, response }
}
///|
pub fn SensitivityPoint::elasticity(self : SensitivityPoint) -> Float {
if self.change == 0.0 {
0.0
} else {
self.response / self.change
}
}
///|
pub struct SensitivityAnalysis {
baseline : Float
points : Array[SensitivityPoint]
} derive(Debug, Eq)
///|
pub fn sensitivity_analysis(
baseline : Float,
points : Array[SensitivityPoint],
) -> SensitivityAnalysis {
{ baseline, points }
}
///|
pub fn SensitivityAnalysis::count(self : SensitivityAnalysis) -> Int {
self.points.length()
}
///|
pub fn SensitivityAnalysis::maximum(
self : SensitivityAnalysis,
) -> SensitivityPoint? {
if self.points.length() == 0 {
None
} else {
let mut best = self.points[0]
for point in self.points[1:] {
if point.response > best.response {
best = point
}
}
Some(best)
}
}
///|
pub fn SensitivityAnalysis::minimum(
self : SensitivityAnalysis,
) -> SensitivityPoint? {
if self.points.length() == 0 {
None
} else {
let mut best = self.points[0]
for point in self.points[1:] {
if point.response < best.response {
best = point
}
}
Some(best)
}
}
///|
pub fn SensitivityAnalysis::mean_response(self : SensitivityAnalysis) -> Float {
summarize(self.points.map(fn(item) { item.response })).mean
}
///|
pub fn SensitivityAnalysis::ranked(
self : SensitivityAnalysis,
) -> Array[SensitivityPoint] {
let result = self.points.copy()
result.sort_by(fn(left, right) {
if left.response > right.response {
-1
} else if left.response < right.response {
1
} else {
0
}
})
result
}
///|
pub fn SensitivityAnalysis::to_table(self : SensitivityAnalysis) -> ReportTable {
let rows : Array[Array[String]] = []
for point in self.ranked() {
rows.push([
point.parameter,
"{point.change}",
"{point.response}",
"{point.elasticity()}",
])
}
table(["parameter", "change", "response", "elasticity"], rows)
}
///|
pub struct MonteCarloSummary {
trials : Int
mean : Float
minimum : Float
maximum : Float
p05 : Float
p50 : Float
p95 : Float
} derive(Debug, Eq)
///|
pub fn monte_carlo_summary(values : Array[Float]) -> MonteCarloSummary {
let stats = summarize(values)
{
trials: stats.count,
mean: stats.mean,
minimum: stats.minimum,
maximum: stats.maximum,
p05: percentile(values, 5.0).unwrap_or(0.0),
p50: percentile(values, 50.0).unwrap_or(0.0),
p95: percentile(values, 95.0).unwrap_or(0.0),
}
}
///|
pub fn MonteCarloSummary::spread(self : MonteCarloSummary) -> Float {
self.p95 - self.p05
}
///|
pub fn MonteCarloSummary::is_stable(
self : MonteCarloSummary,
maximum_spread : Float,
) -> Bool {
self.spread() <= maximum_spread
}
///|
pub fn MonteCarloSummary::to_table(self : MonteCarloSummary) -> ReportTable {
table(["metric", "value"], [
["trials", "\{self.trials}"],
["mean", "\{self.mean}"],
["minimum", "\{self.minimum}"],
["maximum", "\{self.maximum}"],
["p05", "\{self.p05}"],
["p50", "\{self.p50}"],
["p95", "\{self.p95}"],
])
}
///|
pub fn propagate_add(left : Uncertainty, right : Uncertainty) -> Uncertainty {
uncertainty(
left.nominal + right.nominal,
left.lower + right.lower,
left.upper + right.upper,
if left.confidence < right.confidence {
left.confidence
} else {
right.confidence
},
)
}
///|
pub fn propagate_subtract(
left : Uncertainty,
right : Uncertainty,
) -> Uncertainty {
uncertainty(
left.nominal - right.nominal,
left.lower - right.upper,
left.upper - right.lower,
if left.confidence < right.confidence {
left.confidence
} else {
right.confidence
},
)
}
///|
pub fn propagate_scale(value : Uncertainty, factor : Float) -> Uncertainty {
if factor >= 0.0 {
uncertainty(
value.nominal * factor,
value.lower * factor,
value.upper * factor,
value.confidence,
)
} else {
uncertainty(
value.nominal * factor,
value.upper * factor,
value.lower * factor,
value.confidence,
)
}
}
///|
pub fn propagate_multiply(
left : Uncertainty,
right : Uncertainty,
) -> Uncertainty {
let values = [
left.lower * right.lower,
left.lower * right.upper,
left.upper * right.lower,
left.upper * right.upper,
]
let stats = summarize(values)
uncertainty(
left.nominal * right.nominal,
stats.minimum,
stats.maximum,
if left.confidence < right.confidence {
left.confidence
} else {
right.confidence
},
)
}
///|
pub fn propagate_divide(left : Uncertainty, right : Uncertainty) -> Uncertainty {
if right.contains(0.0) {
uncertainty(0.0, -1.0e30, 1.0e30, 0.0)
} else {
propagate_multiply(
left,
uncertainty(
1.0 / right.nominal,
1.0 / right.upper,
1.0 / right.lower,
right.confidence,
),
)
}
}
///|
pub fn uncertainty_table(values : Array[Uncertainty]) -> ReportTable {
let rows : Array[Array[String]] = []
for value in values {
rows.push([
"\{value.nominal}",
"\{value.lower}",
"\{value.upper}",
"\{value.confidence}",
"\{value.relative_width()}",
])
}
table(["nominal", "lower", "upper", "confidence", "relative width"], rows)
}
///|
pub fn uncertainty_quality_gate(values : Array[Uncertainty]) -> Bool {
for value in values {
if !value.is_valid() {
return false
}
}
true
}