///|
pub struct UncertaintyEnvelope {
  nominal : Double
  low : Double
  high : Double
  relative_half_width : Double
} derive(Debug)

///|
pub fn relative_uncertainty_envelope(
  nominal : Double,
  relative_uncertainty : Double,
) -> UncertaintyEnvelope {
  let relative = relative_uncertainty.abs()
  let half_width = nominal.abs() * relative
  {
    nominal,
    low: nominal - half_width,
    high: nominal + half_width,
    relative_half_width: relative,
  }
}

///|
pub fn duty_uncertainty_envelope(
  inlet : Stream,
  target_temperature : Double,
  flow_relative_uncertainty : Double,
  cp_relative_uncertainty : Double,
) -> UncertaintyEnvelope {
  let nominal = heat_duty(inlet, inlet.with_temperature(target_temperature))
  let relative = flow_relative_uncertainty.abs() + cp_relative_uncertainty.abs()
  relative_uncertainty_envelope(nominal, relative)
}

///|
pub fn combine_uncertainties(
  first_relative : Double,
  second_relative : Double,
) -> Double {
  let first = first_relative.abs()
  let second = second_relative.abs()
  (first * first + second * second).sqrt()
}

///|
pub fn propagated_product_uncertainty(
  first_value : Double,
  first_relative_uncertainty : Double,
  second_value : Double,
  second_relative_uncertainty : Double,
) -> UncertaintyEnvelope {
  let nominal = first_value * second_value
  relative_uncertainty_envelope(
    nominal,
    combine_uncertainties(
      first_relative_uncertainty, second_relative_uncertainty,
    ),
  )
}

///|
pub fn propagated_sum_uncertainty(
  first_value : Double,
  first_absolute_uncertainty : Double,
  second_value : Double,
  second_absolute_uncertainty : Double,
) -> UncertaintyEnvelope {
  let nominal = first_value + second_value
  let absolute = combine_uncertainties(
    first_absolute_uncertainty, second_absolute_uncertainty,
  )
  let relative = if nominal.abs() < 1.0e-12 {
    0.0
  } else {
    absolute / nominal.abs()
  }
  relative_uncertainty_envelope(nominal, relative)
}

///|
pub fn worst_case_interval(
  values : Array[Double],
  relative_uncertainty : Double,
) -> UncertaintyEnvelope {
  let mut nominal = 0.0
  for value in values {
    nominal += value
  }
  relative_uncertainty_envelope(nominal, relative_uncertainty)
}

///|
pub fn uncertainty_margin(
  nominal : Double,
  low : Double,
  high : Double,
) -> Double {
  let absolute = (high - low).abs() / 2.0
  if nominal == 0.0 {
    absolute
  } else {
    absolute / nominal.abs() * nominal.abs()
  }
}

///|
pub fn uncertainty_contains(
  value : Double,
  envelope : UncertaintyEnvelope,
) -> Bool {
  value >= envelope.low && value <= envelope.high
}

///|
pub fn duty_sign_is_robust(envelope : UncertaintyEnvelope) -> Bool {
  (envelope.low > 0.0 && envelope.high > 0.0) ||
  (envelope.low < 0.0 && envelope.high < 0.0) ||
  envelope.nominal == 0.0
}