///|
pub fn stream_is_physically_valid(stream : Stream) -> Bool {
  validate_stream(stream).length() == 0
}

///|
pub fn substance_is_physically_valid(
  substance : Substance,
  temperature : Double,
) -> Bool {
  valid_absolute_temperature(temperature) &&
  substance.cp_model.cp_at(temperature) > 0.0
}

///|
pub fn heat_exchanger_temperatures_valid(
  hot_in_c : Double,
  hot_out_c : Double,
  cold_in_c : Double,
  cold_out_c : Double,
  minimum_approach_c : Double,
) -> Bool {
  hot_in_c >= hot_out_c &&
  cold_out_c >= cold_in_c &&
  hot_in_c - cold_out_c >= minimum_approach_c &&
  hot_out_c - cold_in_c >= minimum_approach_c
}

///|
pub fn heat_capacity_flow_is_valid(value : Double) -> Bool {
  value >= 0.0
}

///|
pub fn fraction_array_is_valid(
  values : Array[Double],
  tolerance : Double,
) -> Bool {
  let mut total = 0.0
  for value in values {
    if value < 0.0 {
      return false
    }
    total += value
  }
  (total - 1.0).abs() <= tolerance.abs()
}

///|
pub fn network_fraction_sum_is_valid(
  fractions : Array[Double],
  tolerance : Double,
) -> Bool {
  let mut total = 0.0
  for fraction in fractions {
    if fraction < 0.0 || fraction > 1.0 {
      return false
    }
    total += fraction
  }
  (total - 1.0).abs() <= tolerance.abs()
}

///|
pub fn safe_required_heat(
  inlets : Array[Stream],
  outlets : Array[Stream],
  shaft_work_w : Double,
  kinetic_w : Double,
  potential_w : Double,
) -> Double? {
  if inlets.length() == 0 || outlets.length() == 0 {
    None
  } else {
    Some(
      required_heat(
        inlets,
        outlets,
        shaft_work=shaft_work_w,
        kinetic=kinetic_w,
        potential=potential_w,
      ),
    )
  }
}

///|
pub fn duty_direction_is_consistent(
  inlet : Stream,
  outlet : Stream,
  expected_heating : Bool,
) -> Bool {
  let duty = heat_duty(inlet, outlet)
  if expected_heating {
    duty >= 0.0
  } else {
    duty <= 0.0
  }
}

///|
pub fn numerical_guard(
  value : Double,
  lower : Double,
  upper : Double,
) -> Double {
  value.max(lower.min(upper)).min(upper.max(lower))
}

///|
pub fn positive_or_zero(value : Double) -> Double {
  value.max(0.0)
}

///|
pub fn normalized_fraction(value : Double) -> Double {
  value.max(0.0).min(1.0)
}

///|
pub fn absolute_or_floor(value : Double, floor : Double) -> Double {
  value.abs().max(floor.abs())
}

///|
pub fn valid_temperature_pair(first : Double, second : Double) -> Bool {
  valid_absolute_temperature(first) && valid_absolute_temperature(second)
}

///|
pub fn valid_pressure_pair(first_kpa : Double, second_kpa : Double) -> Bool {
  first_kpa > 0.0 && second_kpa > 0.0
}

///|
pub fn valid_flow_pair(first : Double, second : Double) -> Bool {
  first >= 0.0 && second >= 0.0
}

///|
pub fn conservation_error(before : Double, after : Double) -> Double {
  (after - before).abs()
}

///|
pub fn conservation_is_closed(
  before : Double,
  after : Double,
  tolerance : Double,
) -> Bool {
  conservation_error(before, after) <= tolerance.abs()
}