///|
pub fn scalar_grid_search(
  objective : (Double) -> Double,
  minimum : Double,
  maximum : Double,
  steps : Int,
) -> (Double, Double) {
  let safe = Int::max(1, steps)
  let mut best_x = minimum
  let mut best_value = objective(minimum)
  for i in 0..<=safe {
    let fraction = Double::from_int(i) / Double::from_int(safe)
    let x = lerp(minimum, maximum, fraction)
    let value = objective(x)
    if value < best_value {
      best_x = x
      best_value = value
    }
  }
  (best_x, best_value)
}

///|
pub fn golden_section_minimize(
  objective : (Double) -> Double,
  minimum : Double,
  maximum : Double,
  iterations : Int,
) -> (Double, Double) {
  let mut left = Double::min(minimum, maximum)
  let mut right = Double::max(minimum, maximum)
  let ratio = (5.0.sqrt() - 1.0) / 2.0
  let mut x1 = right - ratio * (right - left)
  let mut x2 = left + ratio * (right - left)
  let mut f1 = objective(x1)
  let mut f2 = objective(x2)
  for _ in 0.. f2 {
      left = x1
      x1 = x2
      f1 = f2
      x2 = left + ratio * (right - left)
      f2 = objective(x2)
    } else {
      right = x2
      x2 = x1
      f2 = f1
      x1 = right - ratio * (right - left)
      f1 = objective(x1)
    }
  }
  let x = (left + right) / 2.0
  (x, objective(x))
}

///|
pub fn scalar_maximize(
  objective : (Double) -> Double,
  minimum : Double,
  maximum : Double,
  iterations : Int,
) -> (Double, Double) {
  let result = golden_section_minimize(
    fn(value) { -objective(value) },
    minimum,
    maximum,
    iterations,
  )
  (result.0, -result.1)
}

///|
pub fn weighted_linear_fit(
  x : ArrayView[Double],
  y : ArrayView[Double],
  weights : ArrayView[Double],
) -> LeastSquaresResult {
  let count = Int::min(x.length(), Int::min(y.length(), weights.length()))
  let mut sum_w = 0.0
  let mut sum_x = 0.0
  let mut sum_y = 0.0
  let mut sum_xx = 0.0
  let mut sum_xy = 0.0
  for i in 0.. LeastSquaresResult {
  let count = Int::min(x.length(), y.length())
  let weights : Array[Double] = []
  for _ in 0.. LeastSquaresResult {
  let count = Int::min(observed.length(), reference.length())
  let weights : Array[Double] = []
  for _ in 0.. Double {
  let count = Int::min(observed.length(), predicted.length())
  let errors : Array[Double] = []
  for i in 0.. Double {
  let count = Int::min(observed.length(), predicted.length())
  let mut total = 0.0
  for i in 0.. Double,
) -> Array[(Double, Double)] {
  let output : Array[(Double, Double)] = []
  for value in values {
    output.push((value, objective(value)))
  }
  output
}

///|
pub fn best_parameter(
  results : ArrayView[(Double, Double)],
) -> (Double, Double)? {
  if results.length() == 0 {
    None
  } else {
    let mut best = results[0]
    for result in results[1:] {
      if result.1 < best.1 {
        best = result
      }
    }
    Some(best)
  }
}

///|
pub fn residual_summary(
  observed : ArrayView[Double],
  predicted : ArrayView[Double],
) -> Summary {
  let count = Int::min(observed.length(), predicted.length())
  let residuals : Array[Double] = []
  for i in 0.. Array[Double] {
  let safe = Double::max(0.0, penalty)
  coefficients.map(fn(value) { value / (1.0 + safe) })
}

///|
pub fn finite_objective(
  values : ArrayView[Double],
  objective : (Double) -> Double,
) -> Double {
  let mut best = 0.0 / 0.0
  for value in values {
    let candidate = objective(value)
    if is_finite(candidate) && (!is_finite(best) || candidate < best) {
      best = candidate
    }
  }
  best
}