///|
pub(all) struct CartesianPoint {
  x : Double
  y : Double
} derive(Debug, Eq, ToJson)

///|
/// Convert normalized ternary fractions to an equilateral triangle with
/// carrier=(0,0), solvent=(1,0), solute=(1/2,sqrt(3)/2).
pub fn to_cartesian(composition : @domain.TernaryComposition) -> CartesianPoint {
  {
    x: composition.solvent + 0.5 * composition.solute,
    y: 0.8660254037844386 * composition.solute,
  }
}

///|
pub fn from_cartesian(
  point : CartesianPoint,
  tolerance? : Double = 1.0e-9,
) -> @domain.TernaryComposition raise @domain.ExtractionError {
  let solute = point.y / 0.8660254037844386
  let solvent = point.x - 0.5 * solute
  let carrier = 1.0 - solute - solvent
  if carrier < -tolerance || solute < -tolerance || solvent < -tolerance {
    raise @domain.ExtractionError::InvalidArgument(
      message="Cartesian point lies outside the ternary triangle",
    )
  }
  @domain.TernaryComposition::from_amounts(
    carrier=carrier.max(0.0),
    solute=solute.max(0.0),
    solvent=solvent.max(0.0),
  )
}

///|
pub fn mix_point(
  first : @domain.TernaryComposition,
  second : @domain.TernaryComposition,
  first_amount~ : Double,
  second_amount~ : Double,
) -> @domain.TernaryComposition raise @domain.ExtractionError {
  first.mix(second, self_amount=first_amount, other_amount=second_amount)
}