///|
/// 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
}