///|
pub struct OptimizationResult {
  parameters : Array[Double]
  objective : Double
  iterations : Int
  converged : Bool
} derive(Debug, Eq)

///|
pub fn finite_difference_gradient(
  objective : (Array[Double]) -> Double,
  parameters : Array[Double],
  step : Double,
) -> Array[Double] {
  let gradient : Array[Double] = []
  for i in 0.. Double,
  initial : Array[Double],
  learning_rate : Double,
  iterations : Int,
) -> OptimizationResult {
  let parameters = initial.copy()
  let mut completed = 0
  for _ in 0.. sum + value * value).sqrt() <
      1.0e-8 {
      break
    }
  }
  {
    parameters,
    objective: objective(parameters),
    iterations: completed,
    converged: completed < iterations.max(0) || iterations <= 0,
  }
}

///|
pub fn grid_search_1d(
  objective : (Double) -> Double,
  lower : Double,
  upper : Double,
  samples : Int,
) -> (Double, Double) {
  let mut best_x = lower
  let mut best_value = objective(lower)
  for i in 0.. Double {
  if values.length() != weights.length() {
    1.0e30
  } else {
    let normalized = normalize_weights(weights)
    let mut total = 0.0
    for i in 0.. (Double, Double) {
  if radii.length() < 2 {
    (0.0, 0.0)
  } else {
    let mut best = radii[0]
    let mut cost = 1.0e30
    for radius in radii {
      let value = circular_speed(mu, radius)
      if value < cost {
        cost = value
        best = radius
      }
    }
    (best, cost)
  }
}

///|
pub fn pareto_front(
  values : Array[(Double, Double)],
) -> Array[(Double, Double)] {
  let result : Array[(Double, Double)] = []
  for candidate in values {
    let dominated = values.any(other => {
      other.0 <= candidate.0 &&
      other.1 <= candidate.1 &&
      (other.0 < candidate.0 || other.1 < candidate.1)
    })
    if !dominated {
      result.push(candidate)
    }
  }
  result
}

///|
pub fn normalize_parameter(
  value : Double,
  lower : Double,
  upper : Double,
) -> Double {
  clamp(inverse_lerp(lower, upper, value), 0.0, 1.0)
}

///|
pub fn denormalize_parameter(
  value : Double,
  lower : Double,
  upper : Double,
) -> Double {
  lerp_scalar(lower, upper, clamp(value, 0.0, 1.0))
}

///|
pub fn objective_orbit_altitude(
  mu : Double,
  elements : ClassicalElements,
  target_altitude_km : Double,
) -> Double {
  let actual = elements.semi_major_axis_km - earth_radius_km
  (actual - target_altitude_km).abs() +
  elements.eccentricity * 100.0 +
  orbital_period(mu, elements.semi_major_axis_km) * 1.0e-5
}

///|
pub fn select_best_candidate(
  candidates : Array[DesignCandidate],
) -> DesignCandidate? {
  let feasible = feasible_designs(candidates)
  let ranked = rank_designs(feasible)
  if ranked.length() == 0 {
    None
  } else {
    Some(ranked[0])
  }
}