///|
/// One equilibrium datum: raffinate concentration `x` and extract
/// concentration `y`, expressed on a consistent solute-free basis.
pub(all) struct EquilibriumPoint {
  x : Double
  y : Double
} derive(Debug, Eq, ToJson)

///|
pub fn EquilibriumPoint::new(
  x~ : Double,
  y~ : Double,
) -> EquilibriumPoint raise ExtractionError {
  require_non_negative("raffinate concentration", x)
  require_non_negative("extract concentration", y)
  { x, y }
}

///|
/// Endpoints of a measured tie line in a ternary phase diagram.
pub(all) struct TieLine {
  raffinate : TernaryComposition
  extract : TernaryComposition
} derive(Debug, Eq, ToJson)

///|
pub fn TieLine::new(
  raffinate~ : TernaryComposition,
  extract~ : TernaryComposition,
) -> TieLine raise ExtractionError {
  if raffinate.distance_squared(extract) <= 1.0e-24 {
    raise ExtractionError::InvalidArgument(
      message="tie-line endpoints must be distinct",
    )
  }
  { raffinate, extract }
}

///|
/// Result from splitting an overall mixture into two equilibrium phases.
pub(all) struct PhaseSplit {
  raffinate_amount : Double
  extract_amount : Double
  raffinate : TernaryComposition
  extract : TernaryComposition
  residual : Double
} derive(Debug, Eq, ToJson)