///|
pub fn summarize_pressure_benchmarks(
  points : Array[PurePressureBenchmark],
  predicted_pressures_bar : Array[Double],
) -> BenchmarkSummary raise VleError {
  assert_same_length(points.length(), predicted_pressures_bar.length())
  if points.length() == 0 {
    raise VleError::EmptyMixture
  }
  let (passed, total_error, maximum_error) = for i = 0, passed = 0, total_error = 0.0, maximum_error = 0.0; i <
                                                points.length(); {
    let error = abs_double(predicted_pressures_bar[i] - points[i].pressure_bar)
    let next_passed = if error <= points[i].tolerance {
      passed + 1
    } else {
      passed
    }
    let next_maximum = if error > maximum_error { error } else { maximum_error }
    continue i + 1, next_passed, total_error + error, next_maximum
  } nobreak {
    (passed, total_error, maximum_error)
  }
  let total = points.length()
  BenchmarkSummary::{
    total,
    passed,
    failed: total - passed,
    pass_rate: passed.to_double() / total.to_double(),
    mean_error: total_error / total.to_double(),
    max_error: maximum_error,
  }
}

///|
pub fn summarize_vle_benchmarks(
  points : Array[BenchmarkPoint],
  predicted_pressures_bar : Array[Double],
) -> BenchmarkSummary raise VleError {
  assert_same_length(points.length(), predicted_pressures_bar.length())
  if points.length() == 0 {
    raise VleError::EmptyMixture
  }
  let (passed, total_error, maximum_error) = for i = 0, passed = 0, total_error = 0.0, maximum_error = 0.0; i <
                                                points.length(); {
    let error = benchmark_pressure_error(points[i], predicted_pressures_bar[i])
    let next_passed = if error <= points[i].tolerance {
      passed + 1
    } else {
      passed
    }
    let next_maximum = if error > maximum_error { error } else { maximum_error }
    continue i + 1, next_passed, total_error + error, next_maximum
  } nobreak {
    (passed, total_error, maximum_error)
  }
  let total = points.length()
  BenchmarkSummary::{
    total,
    passed,
    failed: total - passed,
    pass_rate: passed.to_double() / total.to_double(),
    mean_error: total_error / total.to_double(),
    max_error: maximum_error,
  }
}

///|
pub fn benchmark_summary_is_successful(
  summary : BenchmarkSummary,
  required_pass_rate : Double,
) -> Bool raise VleError {
  if required_pass_rate < 0.0 || required_pass_rate > 1.0 {
    raise VleError::InvalidParameter(
      "required pass rate must be between zero and one",
    )
  }
  summary.pass_rate >= required_pass_rate
}

///|
pub fn benchmark_summary_error_budget(
  summary : BenchmarkSummary,
  maximum_allowed_error : Double,
) -> Bool raise VleError {
  if maximum_allowed_error < 0.0 {
    raise VleError::InvalidParameter("error budget cannot be negative")
  }
  summary.max_error <= maximum_allowed_error
}

///|
pub fn benchmark_prediction_from_component(
  component : Component,
  point : PurePressureBenchmark,
) -> Double raise VleError {
  if component.name != point.component {
    raise VleError::InvalidParameter(
      "benchmark component does not match prediction",
    )
  }
  component.saturation_pressure_bar(point.temperature_k)
}

///|
pub fn evaluate_pure_pressure_catalog(
  components : Array[Component],
  points : Array[PurePressureBenchmark],
) -> BenchmarkSummary raise VleError {
  assert_same_length(components.length(), points.length())
  let predictions = [
    for i in 0.. {
      benchmark_prediction_from_component(components[i], points[i])
    }
  ]
  summarize_pressure_benchmarks(points, predictions)
}

///|
pub fn benchmark_error_percent(
  reference : Double,
  predicted : Double,
) -> Double raise VleError {
  relative_error(reference, predicted) * 100.0
}

///|
pub fn pressure_catalog_components() -> Array[Component] {
  [water(), benzene(), toluene(), ethanol(), methanol()]
}

///|
pub fn pressure_catalog_summary() -> BenchmarkSummary raise VleError {
  evaluate_pure_pressure_catalog(
    pressure_catalog_components(),
    pure_pressure_benchmarks(),
  )
}