///|
/// Operating-window validation keeps a calculation inside a declared process envelope.
pub(all) struct OperatingWindow {
minimum_flow : Double
maximum_flow : Double
minimum_concentration : Double
maximum_concentration : Double
minimum_distribution : Double
maximum_distribution : Double
minimum_recovery : Double
maximum_recovery : Double
} derive(Debug, Eq, ToJson)
///|
pub fn OperatingWindow::new(
flow~ : Range,
concentration~ : Range,
distribution~ : Range,
recovery~ : Range,
) -> OperatingWindow {
{
minimum_flow: flow.lower,
maximum_flow: flow.upper,
minimum_concentration: concentration.lower,
maximum_concentration: concentration.upper,
minimum_distribution: distribution.lower,
maximum_distribution: distribution.upper,
minimum_recovery: recovery.lower,
maximum_recovery: recovery.upper,
}
}
///|
pub(all) struct Range {
lower : Double
upper : Double
} derive(Debug, Eq, ToJson)
///|
pub fn Range::new(
lower~ : Double,
upper~ : Double,
) -> Range raise @domain.ExtractionError {
@domain.require_finite("range lower", lower)
@domain.require_finite("range upper", upper)
if upper < lower {
raise @domain.ExtractionError::InvalidArgument(
message="range upper is below lower",
)
}
{ lower, upper }
}
///|
pub fn OperatingWindow::contains(
self : OperatingWindow,
flow : Double,
concentration : Double,
distribution : Double,
recovery : Double,
) -> Bool {
flow >= self.minimum_flow &&
flow <= self.maximum_flow &&
concentration >= self.minimum_concentration &&
concentration <= self.maximum_concentration &&
distribution >= self.minimum_distribution &&
distribution <= self.maximum_distribution &&
recovery >= self.minimum_recovery &&
recovery <= self.maximum_recovery
}
///|
pub(all) enum Violation {
BelowMinimum(name~ : String, value~ : Double, minimum~ : Double)
AboveMaximum(name~ : String, value~ : Double, maximum~ : Double)
} derive(Debug, Eq, ToJson)
///|
pub fn OperatingWindow::validate(
self : OperatingWindow,
flow : Double,
concentration : Double,
distribution : Double,
recovery : Double,
) -> Array[Violation] {
let violations = []
check(violations, "flow", flow, self.minimum_flow, self.maximum_flow)
check(
violations,
"concentration",
concentration,
self.minimum_concentration,
self.maximum_concentration,
)
check(
violations,
"distribution",
distribution,
self.minimum_distribution,
self.maximum_distribution,
)
check(
violations,
"recovery",
recovery,
self.minimum_recovery,
self.maximum_recovery,
)
violations
}
///|
fn check(
violations : Array[Violation],
name : String,
value : Double,
lower : Double,
upper : Double,
) -> Unit {
if value < lower {
violations.push(Violation::BelowMinimum(name~, value~, minimum=lower))
}
if value > upper {
violations.push(Violation::AboveMaximum(name~, value~, maximum=upper))
}
}
///|
pub(all) struct LedgerEntry {
name : String
inlet : Double
outlet : Double
generated : Double
consumed : Double
residual : Double
} derive(Debug, Eq, ToJson)
///|
pub fn ledger_entry(
name~ : String,
inlet~ : Double,
outlet~ : Double,
generated? : Double = 0.0,
consumed? : Double = 0.0,
) -> LedgerEntry raise @domain.ExtractionError {
@domain.require_finite("ledger inlet", inlet)
@domain.require_finite("ledger outlet", outlet)
@domain.require_non_negative("ledger generated", generated)
@domain.require_non_negative("ledger consumed", consumed)
{
name,
inlet,
outlet,
generated,
consumed,
residual: inlet + generated - consumed - outlet,
}
}
///|
pub fn ledger_passes(
entries : ArrayView[LedgerEntry],
tolerance~ : Double,
) -> Bool {
for entry in entries {
if entry.residual.abs() > tolerance {
return false
}
}
true
}
///|
pub fn maximum_residual(entries : ArrayView[LedgerEntry]) -> Double {
let mut maximum = 0.0
for entry in entries {
maximum = maximum.max(entry.residual.abs())
}
maximum
}
///|
pub(all) struct SafetyFactor {
nominal : Double
factor : Double
design : Double
} derive(Debug, Eq, ToJson)
///|
pub fn apply_safety_factor(
nominal~ : Double,
factor~ : Double,
) -> SafetyFactor raise @domain.ExtractionError {
@domain.require_non_negative("nominal quantity", nominal)
@domain.require_positive("safety factor", factor)
{ nominal, factor, design: nominal * factor }
}
///|
pub fn recommended_safety_factor(
uncertainty_fraction : Double,
minimum? : Double = 1.05,
) -> Double raise @domain.ExtractionError {
@domain.require_non_negative("uncertainty fraction", uncertainty_fraction)
@domain.require_positive("minimum safety factor", minimum)
minimum.max(1.0 + 2.0 * uncertainty_fraction)
}