///|
/// Temperature scales supported by process calculations.
pub(all) enum TemperatureUnit {
Celsius
Kelvin
Fahrenheit
} derive(Debug, Eq, ToJson)
///|
/// A non-negative absolute temperature with explicit units.
pub(all) struct Temperature {
value : Double
unit : TemperatureUnit
} derive(Debug, Eq, ToJson)
///|
pub fn Temperature::new(
value~ : Double,
unit~ : TemperatureUnit,
) -> Temperature raise @domain.ExtractionError {
@domain.require_finite("temperature", value)
let kelvin = match unit {
Celsius => value + 273.15
Kelvin => value
Fahrenheit => (value - 32.0) * 5.0 / 9.0 + 273.15
}
if kelvin <= 0.0 {
raise @domain.ExtractionError::InvalidArgument(
message="absolute temperature must be above zero",
)
}
{ value, unit }
}
///|
pub fn Temperature::value(self : Temperature) -> Double {
self.value
}
///|
pub fn Temperature::unit(self : Temperature) -> TemperatureUnit {
self.unit
}
///|
pub fn Temperature::kelvin(self : Temperature) -> Double {
match self.unit {
Celsius => self.value + 273.15
Kelvin => self.value
Fahrenheit => (self.value - 32.0) * 5.0 / 9.0 + 273.15
}
}
///|
pub fn Temperature::celsius(self : Temperature) -> Double {
self.kelvin() - 273.15
}
///|
pub fn Temperature::fahrenheit(self : Temperature) -> Double {
self.kelvin() * 9.0 / 5.0 - 459.67
}
///|
pub fn Temperature::convert(
self : Temperature,
unit : TemperatureUnit,
) -> Temperature {
let value = match unit {
Celsius => self.celsius()
Kelvin => self.kelvin()
Fahrenheit => self.fahrenheit()
}
{ value, unit }
}
///|
pub fn Temperature::delta(self : Temperature, other : Temperature) -> Double {
self.kelvin() - other.kelvin()
}
///|
pub fn Temperature::shift(
self : Temperature,
delta_kelvin : Double,
) -> Temperature {
{ value: self.kelvin() + delta_kelvin, unit: Kelvin }
}
///|
/// Pressure units used by equilibrium and equipment models.
pub(all) enum PressureUnit {
Pascal
KiloPascal
Bar
Atmosphere
MillimeterHg
} derive(Debug, Eq, ToJson)
///|
pub(all) struct Pressure {
value : Double
unit : PressureUnit
} derive(Debug, Eq, ToJson)
///|
pub fn Pressure::new(
value~ : Double,
unit~ : PressureUnit,
) -> Pressure raise @domain.ExtractionError {
@domain.require_finite("pressure", value)
@domain.require_positive("pressure", value)
{ value, unit }
}
///|
pub fn Pressure::value(self : Pressure) -> Double {
self.value
}
///|
pub fn Pressure::unit(self : Pressure) -> PressureUnit {
self.unit
}
///|
pub fn Pressure::pascal(self : Pressure) -> Double {
match self.unit {
Pascal => self.value
KiloPascal => self.value * 1000.0
Bar => self.value * 100000.0
Atmosphere => self.value * 101325.0
MillimeterHg => self.value * 133.322387415
}
}
///|
pub fn Pressure::kilopascal(self : Pressure) -> Double {
self.pascal() / 1000.0
}
///|
pub fn Pressure::bar(self : Pressure) -> Double {
self.pascal() / 100000.0
}
///|
pub fn Pressure::atmosphere(self : Pressure) -> Double {
self.pascal() / 101325.0
}
///|
pub fn Pressure::millimeter_hg(self : Pressure) -> Double {
self.pascal() / 133.322387415
}
///|
pub fn Pressure::convert(self : Pressure, unit : PressureUnit) -> Pressure {
let value = match unit {
Pascal => self.pascal()
KiloPascal => self.kilopascal()
Bar => self.bar()
Atmosphere => self.atmosphere()
MillimeterHg => self.millimeter_hg()
}
{ value, unit }
}
///|
pub fn Pressure::drop(self : Pressure, downstream : Pressure) -> Pressure {
let difference = self.pascal() - downstream.pascal()
{ value: difference.max(0.0), unit: Pascal }
}
///|
pub fn Pressure::ratio(self : Pressure, reference : Pressure) -> Double {
self.pascal() / reference.pascal()
}
///|
/// Volumetric units for solvent and equipment sizing.
pub(all) enum VolumeUnit {
CubicMeter
Liter
Milliliter
Gallon
} derive(Debug, Eq, ToJson)
///|
pub(all) struct Volume {
value : Double
unit : VolumeUnit
} derive(Debug, Eq, ToJson)
///|
pub fn Volume::new(
value~ : Double,
unit~ : VolumeUnit,
) -> Volume raise @domain.ExtractionError {
@domain.require_finite("volume", value)
@domain.require_non_negative("volume", value)
{ value, unit }
}
///|
pub fn Volume::value(self : Volume) -> Double {
self.value
}
///|
pub fn Volume::cubic_meter(self : Volume) -> Double {
match self.unit {
CubicMeter => self.value
Liter => self.value / 1000.0
Milliliter => self.value / 1000000.0
Gallon => self.value * 0.003785411784
}
}
///|
pub fn Volume::liter(self : Volume) -> Double {
self.cubic_meter() * 1000.0
}
///|
pub fn Volume::milliliter(self : Volume) -> Double {
self.cubic_meter() * 1000000.0
}
///|
pub fn Volume::gallon(self : Volume) -> Double {
self.cubic_meter() / 0.003785411784
}
///|
pub fn Volume::convert(self : Volume, unit : VolumeUnit) -> Volume {
let value = match unit {
CubicMeter => self.cubic_meter()
Liter => self.liter()
Milliliter => self.milliliter()
Gallon => self.gallon()
}
{ value, unit }
}
///|
pub fn Volume::add(self : Volume, other : Volume) -> Volume {
{ value: self.value + other.convert(self.unit).value, unit: self.unit }
}
///|
pub fn Volume::scale(
self : Volume,
factor : Double,
) -> Volume raise @domain.ExtractionError {
@domain.require_non_negative("volume scale", factor)
{ value: self.value * factor, unit: self.unit }
}
///|
/// Energy units for heating and cooling duty estimates.
pub(all) enum EnergyUnit {
Joule
KiloJoule
MegaJoule
KilowattHour
} derive(Debug, Eq, ToJson)
///|
pub(all) struct Energy {
value : Double
unit : EnergyUnit
} derive(Debug, Eq, ToJson)
///|
pub fn Energy::new(
value~ : Double,
unit~ : EnergyUnit,
) -> Energy raise @domain.ExtractionError {
@domain.require_finite("energy", value)
{ value, unit }
}
///|
pub fn Energy::joule(self : Energy) -> Double {
match self.unit {
Joule => self.value
KiloJoule => self.value * 1000.0
MegaJoule => self.value * 1000000.0
KilowattHour => self.value * 3600000.0
}
}
///|
pub fn Energy::kilojoule(self : Energy) -> Double {
self.joule() / 1000.0
}
///|
pub fn Energy::megajoule(self : Energy) -> Double {
self.joule() / 1000000.0
}
///|
pub fn Energy::kilowatt_hour(self : Energy) -> Double {
self.joule() / 3600000.0
}
///|
pub fn Energy::convert(self : Energy, unit : EnergyUnit) -> Energy {
let value = match unit {
Joule => self.joule()
KiloJoule => self.kilojoule()
MegaJoule => self.megajoule()
KilowattHour => self.kilowatt_hour()
}
{ value, unit }
}
///|
pub fn Energy::add(self : Energy, other : Energy) -> Energy {
{ value: self.value + other.convert(self.unit).value, unit: self.unit }
}
///|
pub fn Energy::scale(self : Energy, factor : Double) -> Energy {
{ value: self.value * factor, unit: self.unit }
}
///|
/// Power is energy delivered per unit time.
pub(all) struct Power {
energy : Energy
duration : Duration
} derive(Debug, Eq, ToJson)
///|
pub fn Power::new(energy~ : Energy, duration~ : Duration) -> Power {
{ energy, duration }
}
///|
pub fn Power::watt(self : Power) -> Double {
self.energy.joule() / self.duration.per_second()
}
///|
pub fn Power::kilowatt(self : Power) -> Double {
self.watt() / 1000.0
}
///|
pub fn Power::energy_for(self : Power, duration : Duration) -> Energy {
{ value: self.watt() * duration.per_second(), unit: Joule }
}
///|
/// A consistent operating point passed between process calculations.
pub(all) struct OperatingConditions {
temperature : Temperature
pressure : Pressure
residence_time : Duration
} derive(Debug, Eq, ToJson)
///|
pub fn OperatingConditions::new(
temperature~ : Temperature,
pressure~ : Pressure,
residence_time~ : Duration,
) -> OperatingConditions {
{ temperature, pressure, residence_time }
}
///|
pub fn OperatingConditions::temperature(
self : OperatingConditions,
) -> Temperature {
self.temperature
}
///|
pub fn OperatingConditions::pressure(self : OperatingConditions) -> Pressure {
self.pressure
}
///|
pub fn OperatingConditions::residence_time(
self : OperatingConditions,
) -> Duration {
self.residence_time
}
///|
pub fn OperatingConditions::with_temperature(
self : OperatingConditions,
temperature : Temperature,
) -> OperatingConditions {
{ temperature, pressure: self.pressure, residence_time: self.residence_time }
}
///|
pub fn OperatingConditions::with_pressure(
self : OperatingConditions,
pressure : Pressure,
) -> OperatingConditions {
{
temperature: self.temperature,
pressure,
residence_time: self.residence_time,
}
}
///|
pub fn OperatingConditions::with_residence_time(
self : OperatingConditions,
residence_time : Duration,
) -> OperatingConditions {
{ temperature: self.temperature, pressure: self.pressure, residence_time }
}
///|
/// Convert a volumetric flow into a residence time for a vessel.
pub fn residence_time_from_flow(
vessel_volume~ : Volume,
volumetric_flow~ : Volume,
duration~ : Duration,
) -> Duration raise @domain.ExtractionError {
@domain.require_positive("vessel volume", vessel_volume.cubic_meter())
@domain.require_positive("volumetric flow", volumetric_flow.cubic_meter())
let seconds = vessel_volume.cubic_meter() /
volumetric_flow.cubic_meter() *
duration.per_second()
Duration::new(value=seconds, unit=Second)
}
///|
/// Convert a mass flow to a concentration ratio using a common mass basis.
pub fn mass_flow_concentration(solute : MassFlow, carrier : MassFlow) -> Double {
solute.kg_per_second() / carrier.kg_per_second()
}
///|
pub fn pressure_drop_fraction(inlet : Pressure, outlet : Pressure) -> Double {
let inlet_pa = inlet.pascal()
if inlet_pa <= 0.0 {
0.0
} else {
(inlet_pa - outlet.pascal()).max(0.0) / inlet_pa
}
}
///|
pub fn temperature_rise(inlet : Temperature, outlet : Temperature) -> Double {
outlet.kelvin() - inlet.kelvin()
}
///|
pub fn energy_balance_residual(
inlet : Energy,
outlet : Energy,
duty : Energy,
) -> Double {
inlet.joule() + duty.joule() - outlet.joule()
}
///|
pub fn thermal_duty_from_heat_capacity(
mass_flow_kg_per_second~ : Double,
heat_capacity_kj_per_kg_k~ : Double,
inlet~ : Temperature,
outlet~ : Temperature,
) -> Energy raise @domain.ExtractionError {
@domain.require_non_negative("mass flow", mass_flow_kg_per_second)
@domain.require_non_negative("heat capacity", heat_capacity_kj_per_kg_k)
let duty = mass_flow_kg_per_second *
heat_capacity_kj_per_kg_k *
temperature_rise(inlet, outlet)
Energy::new(value=duty, unit=KiloJoule)
}