///|
pub(all) struct RecoveryEnvelope {
nominal : @liquid.SingleStageResult
minimum_recovery : Double
maximum_recovery : Double
minimum_case : SingleStageScenario
maximum_case : SingleStageScenario
evaluated_cases : Int
} derive(Debug, Eq, ToJson)
///|
/// Evaluate the corner cases of bounded solvent-flow and distribution ranges.
/// Monotonicity is not assumed by the implementation even though the ideal
/// constant-K model is monotone in both inputs.
pub fn single_stage_recovery_envelope(
feed_flow~ : Double,
feed_concentration~ : Double,
nominal_solvent_flow~ : Double,
solvent_flow_lower~ : Double,
solvent_flow_upper~ : Double,
nominal_distribution~ : Double,
distribution_lower~ : Double,
distribution_upper~ : Double,
) -> RecoveryEnvelope raise @domain.ExtractionError {
@domain.require_positive("feed flow", feed_flow)
@domain.require_non_negative("feed concentration", feed_concentration)
@domain.require_positive("nominal solvent flow", nominal_solvent_flow)
@domain.require_positive("solvent flow lower", solvent_flow_lower)
@domain.require_positive("solvent flow upper", solvent_flow_upper)
@domain.require_positive("nominal distribution", nominal_distribution)
@domain.require_positive("distribution lower", distribution_lower)
@domain.require_positive("distribution upper", distribution_upper)
if solvent_flow_lower > nominal_solvent_flow ||
nominal_solvent_flow > solvent_flow_upper {
raise @domain.ExtractionError::InvalidArgument(
message="nominal solvent flow must lie inside its bounds",
)
}
if distribution_lower > nominal_distribution ||
nominal_distribution > distribution_upper {
raise @domain.ExtractionError::InvalidArgument(
message="nominal distribution must lie inside its bounds",
)
}
let cases = [
SingleStageScenario::new(
name="lower-flow/lower-K",
feed_flow~,
feed_concentration~,
solvent_flow=solvent_flow_lower,
distribution=distribution_lower,
),
SingleStageScenario::new(
name="lower-flow/upper-K",
feed_flow~,
feed_concentration~,
solvent_flow=solvent_flow_lower,
distribution=distribution_upper,
),
SingleStageScenario::new(
name="upper-flow/lower-K",
feed_flow~,
feed_concentration~,
solvent_flow=solvent_flow_upper,
distribution=distribution_lower,
),
SingleStageScenario::new(
name="upper-flow/upper-K",
feed_flow~,
feed_concentration~,
solvent_flow=solvent_flow_upper,
distribution=distribution_upper,
),
]
let results = run_single_stage_scenarios(cases)
let mut minimum = results[0]
let mut maximum = results[0]
for result in results {
if result.result.recovery < minimum.result.recovery {
minimum = result
}
if result.result.recovery > maximum.result.recovery {
maximum = result
}
}
{
nominal: @liquid.single_stage_constant_k(
feed_flow~,
feed_concentration~,
solvent_flow=nominal_solvent_flow,
distribution=nominal_distribution,
),
minimum_recovery: minimum.result.recovery,
maximum_recovery: maximum.result.recovery,
minimum_case: minimum.input,
maximum_case: maximum.input,
evaluated_cases: cases.length(),
}
}