///|
pub fn ClausiusClapeyron::pressure_bar(
  self : ClausiusClapeyron,
  temperature_k : Double,
) -> Double raise VleError {
  assert_temperature(temperature_k)
  assert_pressure(self.reference_pressure_bar)
  if self.enthalpy_vaporization_j_per_mol <= 0.0 {
    raise VleError::InvalidParameter(
      "enthalpy of vaporization must be positive",
    )
  }
  @math.exp(
    @math.ln(self.reference_pressure_bar) -
    self.enthalpy_vaporization_j_per_mol /
    gas_constant_j_per_mol_k *
    (1.0 / temperature_k - 1.0 / self.reference_temperature_k),
  )
}

///|
pub fn ClausiusClapeyron::is_in_range(
  self : ClausiusClapeyron,
  temperature_k : Double,
) -> Bool {
  temperature_k > 0.0 && self.reference_temperature_k > 0.0
}

///|
pub fn ClausiusClapeyron::pressure_change_ratio(
  self : ClausiusClapeyron,
  low_temperature_k : Double,
  high_temperature_k : Double,
) -> Double raise VleError {
  self.pressure_bar(high_temperature_k) / self.pressure_bar(low_temperature_k)
}

///|
pub fn ClausiusClapeyron::boiling_temperature_k(
  self : ClausiusClapeyron,
  pressure_bar : Double,
  low_k? : Double = 200.0,
  high_k? : Double = 600.0,
) -> Double raise VleError {
  assert_pressure(pressure_bar)
  let options = SolverOptions::new(tolerance=1.0E-8, max_iterations=120)
  let report = solve_bisection(low=low_k, high=high_k, options~, fn(
    temperature_k : Double,
  ) -> Double raise VleError {
    self.pressure_bar(temperature_k) - pressure_bar
  })
  report.root
}

///|
pub fn ExtendedAntoine::is_in_range(
  self : ExtendedAntoine,
  temperature_k : Double,
) -> Bool {
  temperature_k >= self.t_min_k && temperature_k <= self.t_max_k
}

///|
pub fn ExtendedAntoine::pressure_bar(
  self : ExtendedAntoine,
  temperature_k : Double,
) -> Double raise VleError {
  assert_temperature(temperature_k)
  let temperature_c = temperature_k - 273.15
  let denominator = temperature_c + self.c
  if denominator == 0.0 {
    raise VleError::InvalidParameter(
      "extended Antoine denominator cannot be zero",
    )
  }
  let log_pressure = self.a -
    self.b / denominator +
    self.d * @math.pow(temperature_c, self.e)
  @math.pow(10.0, log_pressure) * 0.001333223684
}

///|
pub fn ExtendedAntoine::pressure_bar_checked(
  self : ExtendedAntoine,
  temperature_k : Double,
) -> Double raise VleError {
  if !self.is_in_range(temperature_k) {
    raise VleError::InvalidParameter(
      "temperature is outside extended Antoine range",
    )
  }
  self.pressure_bar(temperature_k)
}

///|
pub fn ExtendedAntoine::boiling_temperature_k(
  self : ExtendedAntoine,
  pressure_bar : Double,
  low_k? : Double = 200.0,
  high_k? : Double = 600.0,
) -> Double raise VleError {
  assert_pressure(pressure_bar)
  let options = SolverOptions::new(tolerance=1.0E-8, max_iterations=120)
  let report = solve_bisection(low=low_k, high=high_k, options~, fn(
    temperature_k : Double,
  ) -> Double raise VleError {
    self.pressure_bar(temperature_k) - pressure_bar
  })
  report.root
}

///|
fn compare_single_correlation(
  reference : Component,
  temperatures_k : Array[Double],
  correlation : ExtendedAntoine,
) -> CorrelationComparison raise VleError {
  if temperatures_k.length() == 0 {
    raise VleError::EmptyMixture
  }
  let (total, maximum) = for i = 0, total = 0.0, maximum = 0.0; i <
                            temperatures_k.length(); {
    let reference_pressure = reference.saturation_pressure_bar(
      temperatures_k[i],
    )
    let candidate_pressure = correlation.pressure_bar(temperatures_k[i])
    let difference = abs_double(candidate_pressure - reference_pressure) /
      clamp_positive(reference_pressure)
    continue i + 1,
      total + difference,
      if difference > maximum {
        difference
      } else {
        maximum
      }
  } nobreak {
    (total, maximum)
  }
  CorrelationComparison::{
    name: correlation.source,
    points: temperatures_k.length(),
    mean_relative_difference: total / temperatures_k.length().to_double(),
    maximum_relative_difference: maximum,
  }
}

///|
pub fn compare_pressure_correlations(
  reference : Component,
  temperatures_k : Array[Double],
  correlations : Array[ExtendedAntoine],
) -> Array[CorrelationComparison] raise VleError {
  if correlations.length() == 0 {
    raise VleError::EmptyMixture
  }
  [
    for correlation in correlations => {
      compare_single_correlation(reference, temperatures_k, correlation)
    }
  ]
}

///|
pub fn correlation_relative_difference(
  reference_pressure_bar : Double,
  candidate_pressure_bar : Double,
) -> Double raise VleError {
  if reference_pressure_bar <= 0.0 || candidate_pressure_bar <= 0.0 {
    raise VleError::InvalidParameter("correlation pressures must be positive")
  }
  abs_double(candidate_pressure_bar - reference_pressure_bar) /
  reference_pressure_bar
}

///|
pub fn correlation_mean_relative_difference(
  reference_pressures_bar : Array[Double],
  candidate_pressures_bar : Array[Double],
) -> Double raise VleError {
  assert_same_length(
    reference_pressures_bar.length(),
    candidate_pressures_bar.length(),
  )
  if reference_pressures_bar.length() == 0 {
    raise VleError::EmptyMixture
  }
  for i = 0, value = 0.0; i < reference_pressures_bar.length(); {
    continue i + 1,
      value +
      correlation_relative_difference(
        reference_pressures_bar[i],
        candidate_pressures_bar[i],
      )
  } nobreak {
    value / reference_pressures_bar.length().to_double()
  }
}

///|
pub fn correlation_maximum_relative_difference(
  reference_pressures_bar : Array[Double],
  candidate_pressures_bar : Array[Double],
) -> Double raise VleError {
  assert_same_length(
    reference_pressures_bar.length(),
    candidate_pressures_bar.length(),
  )
  if reference_pressures_bar.length() == 0 {
    raise VleError::EmptyMixture
  }
  for i = 0, value = 0.0; i < reference_pressures_bar.length(); {
    let difference = correlation_relative_difference(
      reference_pressures_bar[i],
      candidate_pressures_bar[i],
    )
    continue i + 1, if difference > value { difference } else { value }
  } nobreak {
    value
  }
}

///|
pub fn compare_antoine_to_dippr(
  component : Component,
  dippr : Dippr101,
  temperatures_k : Array[Double],
) -> Double raise VleError {
  let reference = [
    for temperature in temperatures_k => {
      component.saturation_pressure_bar(temperature)
    }
  ]
  let candidate = [
    for temperature in temperatures_k => dippr.pressure_bar(temperature)
  ]
  correlation_mean_relative_difference(reference, candidate)
}

///|
pub fn compare_antoine_to_wagner(
  component : Component,
  wagner : Wagner,
  temperatures_k : Array[Double],
) -> Double raise VleError {
  let reference = [
    for temperature in temperatures_k => {
      component.saturation_pressure_bar(temperature)
    }
  ]
  let candidate = [
    for temperature in temperatures_k => wagner.pressure_bar(temperature)
  ]
  correlation_mean_relative_difference(reference, candidate)
}