///|
/// Explicit engineering quantities prevent accidental mixing of kg/h and kg/s.
pub(all) enum MassUnit {
Kilogram
Gram
Pound
} derive(Debug, Eq, ToJson)
///|
pub(all) enum TimeUnit {
Second
Minute
Hour
Day
} derive(Debug, Eq, ToJson)
///|
pub(all) struct Mass {
value : Double
unit : MassUnit
} derive(Debug, Eq, ToJson)
///|
pub fn Mass::new(
value~ : Double,
unit~ : MassUnit,
) -> Mass raise @domain.ExtractionError {
@domain.require_finite("mass", value)
@domain.require_non_negative("mass", value)
{ value, unit }
}
///|
pub fn Mass::value(self : Mass) -> Double {
self.value
}
///|
pub fn Mass::unit(self : Mass) -> MassUnit {
self.unit
}
///|
fn mass_to_kg(unit : MassUnit, value : Double) -> Double {
match unit {
Kilogram => value
Gram => value / 1000.0
Pound => value * 0.45359237
}
}
///|
fn kg_to_mass(unit : MassUnit, value : Double) -> Double {
match unit {
Kilogram => value
Gram => value * 1000.0
Pound => value / 0.45359237
}
}
///|
pub fn Mass::convert(self : Mass, target : MassUnit) -> Mass {
{ value: kg_to_mass(target, mass_to_kg(self.unit, self.value)), unit: target }
}
///|
pub fn Mass::add(self : Mass, other : Mass) -> Mass {
{ value: self.value + other.convert(self.unit).value, unit: self.unit }
}
///|
pub(all) struct Duration {
value : Double
unit : TimeUnit
} derive(Debug, Eq, ToJson)
///|
pub fn Duration::new(
value~ : Double,
unit~ : TimeUnit,
) -> Duration raise @domain.ExtractionError {
@domain.require_finite("duration", value)
@domain.require_positive("duration", value)
{ value, unit }
}
///|
fn duration_to_seconds(unit : TimeUnit, value : Double) -> Double {
match unit {
Second => value
Minute => value * 60.0
Hour => value * 3600.0
Day => value * 86400.0
}
}
///|
pub fn Duration::per_second(self : Duration) -> Double {
duration_to_seconds(self.unit, self.value)
}
///|
pub(all) struct MassFlow {
mass : Mass
duration : Duration
} derive(Debug, Eq, ToJson)
///|
pub fn MassFlow::new(mass~ : Mass, duration~ : Duration) -> MassFlow {
{ mass, duration }
}
///|
pub fn MassFlow::kg_per_second(self : MassFlow) -> Double {
mass_to_kg(self.mass.unit, self.mass.value) / self.duration.per_second()
}
///|
pub fn MassFlow::kg_per_hour(self : MassFlow) -> Double {
self.kg_per_second() * 3600.0
}
///|
pub fn MassFlow::scale(
self : MassFlow,
factor : Double,
) -> MassFlow raise @domain.ExtractionError {
@domain.require_non_negative("flow scale", factor)
{
mass: Mass::{ value: self.mass.value * factor, unit: self.mass.unit },
duration: self.duration,
}
}
///|
pub(all) struct Concentration {
solute_mass : Mass
carrier_mass : Mass
} derive(Debug, Eq, ToJson)
///|
pub fn Concentration::new(
solute_mass~ : Mass,
carrier_mass~ : Mass,
) -> Concentration raise @domain.ExtractionError {
if carrier_mass.value() <= 0.0 {
raise @domain.ExtractionError::InvalidArgument(
message="carrier mass must be positive",
)
}
{ solute_mass, carrier_mass }
}
///|
pub fn Concentration::ratio(self : Concentration) -> Double {
mass_to_kg(self.solute_mass.unit, self.solute_mass.value) /
mass_to_kg(self.carrier_mass.unit, self.carrier_mass.value)
}
///|
pub fn Concentration::mass_fraction(self : Concentration) -> Double {
let solute = mass_to_kg(self.solute_mass.unit, self.solute_mass.value)
let carrier = mass_to_kg(self.carrier_mass.unit, self.carrier_mass.value)
solute / (solute + carrier)
}
///|
pub fn concentration_from_fraction(
fraction : Double,
carrier_mass : Mass,
) -> Concentration raise @domain.ExtractionError {
if fraction < 0.0 || fraction >= 1.0 {
raise @domain.ExtractionError::InvalidArgument(
message="mass fraction must be in [0,1)",
)
}
let carrier_kg = mass_to_kg(carrier_mass.unit, carrier_mass.value)
let solute_kg = carrier_kg * fraction / (1.0 - fraction)
Concentration::new(
solute_mass=Mass::new(
value=kg_to_mass(carrier_mass.unit, solute_kg),
unit=carrier_mass.unit,
),
carrier_mass~,
)
}