///|
/// Deterministic one-dimensional search utilities for solvent sizing.
pub(all) struct SearchResult {
argument : Double
value : Double
evaluations : Int
converged : Bool
} derive(Debug, Eq, ToJson)
///|
pub fn bounded_minimize(
objective : (Double) -> Double raise @domain.ExtractionError,
lower~ : Double,
upper~ : Double,
tolerance? : Double = 1.0e-8,
maximum_iterations? : Int = 128,
) -> SearchResult raise @domain.ExtractionError {
@domain.require_finite("lower bound", lower)
@domain.require_finite("upper bound", upper)
@domain.require_positive("search tolerance", tolerance)
if upper <= lower {
raise @domain.ExtractionError::InvalidArgument(
message="upper bound must exceed lower bound",
)
}
if maximum_iterations < 1 {
raise @domain.ExtractionError::InvalidArgument(
message="maximum iterations must be positive",
)
}
let golden = 0.6180339887498949
let mut left = lower
let mut right = upper
let mut x1 = right - golden * (right - left)
let mut x2 = left + golden * (right - left)
let mut f1 = objective(x1)
let mut f2 = objective(x2)
let mut evaluations = 2
let mut iterations = 0
while iterations < maximum_iterations && (right - left).abs() > tolerance {
if f1 <= f2 {
right = x2
x2 = x1
f2 = f1
x1 = right - golden * (right - left)
f1 = objective(x1)
} else {
left = x1
x1 = x2
f1 = f2
x2 = left + golden * (right - left)
f2 = objective(x2)
}
evaluations += 1
iterations += 1
}
let argument = (left + right) / 2.0
{
argument,
value: objective(argument),
evaluations: evaluations + 1,
converged: (right - left).abs() <= tolerance,
}
}
///|
pub fn grid_minimize(
objective : (Double) -> Double raise @domain.ExtractionError,
lower~ : Double,
upper~ : Double,
samples~ : Int,
) -> SearchResult raise @domain.ExtractionError {
if samples < 2 {
raise @domain.ExtractionError::InsufficientData(required=2, actual=samples)
}
if upper <= lower {
raise @domain.ExtractionError::InvalidArgument(
message="upper bound must exceed lower bound",
)
}
let mut best_x = lower
let mut best_value = objective(lower)
let mut evaluations = 1
for index in 1.. Bool {
let no_worse = self.solvent <= other.solvent &&
self.cost <= other.cost &&
self.recovery >= other.recovery
let strict = self.solvent < other.solvent ||
self.cost < other.cost ||
self.recovery > other.recovery
no_worse && strict
}
///|
pub fn pareto_front(points : ArrayView[DesignPoint]) -> Array[DesignPoint] {
let result = []
for index, candidate in points {
let mut dominated = false
for other_index, other in points {
if index != other_index && other.dominates(candidate) {
dominated = true
}
}
if !dominated {
result.push(candidate)
}
}
result
}