///|
pub(all) struct NumericSummary {
count : Int
minimum : Double
maximum : Double
mean : Double
sample_standard_deviation : Double
} derive(Debug, Eq, ToJson)
///|
pub fn summarize(
values : ArrayView[Double],
) -> NumericSummary raise @domain.ExtractionError {
if values.length() == 0 {
raise @domain.ExtractionError::InsufficientData(required=1, actual=0)
}
let mut minimum = values[0]
let mut maximum = values[0]
let mut mean = 0.0
let mut sum_squared_deviation = 0.0
for index, value in values {
@domain.require_finite("value[\{index}]", value)
minimum = minimum.min(value)
maximum = maximum.max(value)
let count = (index + 1).to_double()
let delta = value - mean
mean += delta / count
let delta_after = value - mean
sum_squared_deviation += delta * delta_after
}
{
count: values.length(),
minimum,
maximum,
mean,
sample_standard_deviation: if values.length() > 1 {
(sum_squared_deviation / (values.length() - 1).to_double()).sqrt()
} else {
0.0
},
}
}
///|
pub(all) struct ScenarioSummary {
recovery : NumericSummary
raffinate_concentration : NumericSummary
extract_concentration : NumericSummary
extraction_factor : NumericSummary
maximum_absolute_balance_residual : Double
} derive(Debug, Eq, ToJson)
///|
pub fn summarize_scenarios(
results : ArrayView[ScenarioResult],
) -> ScenarioSummary raise @domain.ExtractionError {
if results.length() == 0 {
raise @domain.ExtractionError::InsufficientData(required=1, actual=0)
}
let recoveries : Array[Double] = []
let raffinates : Array[Double] = []
let extracts : Array[Double] = []
let factors : Array[Double] = []
let mut max_residual = 0.0
for item in results {
recoveries.push(item.result.recovery)
raffinates.push(item.result.raffinate_concentration)
extracts.push(item.result.extract_concentration)
factors.push(item.extraction_factor)
max_residual = max_residual.max(item.result.balance_residual.abs())
}
{
recovery: summarize(recoveries),
raffinate_concentration: summarize(raffinates),
extract_concentration: summarize(extracts),
extraction_factor: summarize(factors),
maximum_absolute_balance_residual: max_residual,
}
}
///|
pub(all) struct BalanceAudit {
passed : Bool
tolerance : Double
checked : Int
failed_names : Array[String]
maximum_absolute_residual : Double
} derive(Debug, Eq, ToJson)
///|
pub fn audit_balances(
results : ArrayView[ScenarioResult],
tolerance? : Double = 1.0e-10,
) -> BalanceAudit raise @domain.ExtractionError {
@domain.require_positive("balance tolerance", tolerance)
if results.length() == 0 {
raise @domain.ExtractionError::InsufficientData(required=1, actual=0)
}
let failed_names : Array[String] = []
let mut maximum = 0.0
for item in results {
let residual = item.result.balance_residual.abs()
maximum = maximum.max(residual)
if residual > tolerance {
failed_names.push(item.name)
}
}
{
passed: failed_names.length() == 0,
tolerance,
checked: results.length(),
failed_names,
maximum_absolute_residual: maximum,
}
}