///|
pub fn celsius_to_kelvin(temperature_c : Double) -> Double {
temperature_c + 273.15
}
///|
pub fn kelvin_to_celsius(temperature_k : Double) -> Double {
temperature_k - 273.15
}
///|
pub fn bar_to_kpa(pressure_bar : Double) -> Double {
pressure_bar * 100.0
}
///|
pub fn kpa_to_bar(pressure_kpa : Double) -> Double {
pressure_kpa / 100.0
}
///|
pub fn bar_to_pascal(pressure_bar : Double) -> Double {
pressure_bar * 100000.0
}
///|
pub fn pascal_to_bar(pressure_pa : Double) -> Double {
pressure_pa / 100000.0
}
///|
pub fn mmhg_to_bar(pressure_mmhg : Double) -> Double {
pressure_mmhg * 0.001333223684
}
///|
pub fn bar_to_mmhg(pressure_bar : Double) -> Double {
pressure_bar / 0.001333223684
}
///|
pub fn atm_to_bar(pressure_atm : Double) -> Double {
pressure_atm * 1.01325
}
///|
pub fn bar_to_atm(pressure_bar : Double) -> Double {
pressure_bar / 1.01325
}
///|
pub fn joule_to_kilojoule(energy_j : Double) -> Double {
energy_j / 1000.0
}
///|
pub fn kilojoule_to_joule(energy_kj : Double) -> Double {
energy_kj * 1000.0
}
///|
pub fn watt_to_joule_per_second(power_w : Double) -> Double {
power_w
}
///|
pub fn validate_molecular_weights(
weights : Array[Double],
) -> Unit raise VleError {
if weights.length() == 0 {
raise VleError::EmptyMixture
}
for weight in weights {
if weight <= 0.0 {
raise VleError::InvalidParameter("molecular weights must be positive")
}
}
}
///|
pub fn mixture_molecular_weight(
mole_fractions : Array[Double],
molecular_weights : Array[Double],
) -> Double raise VleError {
assert_same_length(mole_fractions.length(), molecular_weights.length())
validate_molecular_weights(molecular_weights)
let x = normalize(mole_fractions)
for i = 0, value = 0.0; i < x.length(); {
continue i + 1, value + x[i] * molecular_weights[i]
} nobreak {
value
}
}
///|
pub fn molar_to_mass_fractions(
mole_fractions : Array[Double],
molecular_weights : Array[Double],
) -> Array[Double] raise VleError {
assert_same_length(mole_fractions.length(), molecular_weights.length())
validate_molecular_weights(molecular_weights)
let x = normalize(mole_fractions)
normalize([ for i in 0.. x[i] * molecular_weights[i] ])
}
///|
pub fn mass_to_molar_fractions(
mass_fractions : Array[Double],
molecular_weights : Array[Double],
) -> Array[Double] raise VleError {
assert_same_length(mass_fractions.length(), molecular_weights.length())
validate_molecular_weights(molecular_weights)
let w = normalize(mass_fractions)
normalize([ for i in 0.. w[i] / molecular_weights[i] ])
}
///|
pub fn molar_flow_to_mass_flow(
molar_flow_mol_per_s : Double,
molecular_weight_g_per_mol : Double,
) -> Double raise VleError {
if molar_flow_mol_per_s < 0.0 || molecular_weight_g_per_mol <= 0.0 {
raise VleError::InvalidParameter("flow and molecular weight are invalid")
}
molar_flow_mol_per_s * molecular_weight_g_per_mol / 1000.0
}
///|
pub fn mass_flow_to_molar_flow(
mass_flow_kg_per_s : Double,
molecular_weight_g_per_mol : Double,
) -> Double raise VleError {
if mass_flow_kg_per_s < 0.0 || molecular_weight_g_per_mol <= 0.0 {
raise VleError::InvalidParameter("flow and molecular weight are invalid")
}
mass_flow_kg_per_s * 1000.0 / molecular_weight_g_per_mol
}
///|
pub fn mole_fractions_to_partial_pressures(
mole_fractions : Array[Double],
pressure_bar : Double,
) -> Array[Double] raise VleError {
assert_pressure(pressure_bar)
let x = normalize(mole_fractions)
[
for value in x => value * pressure_bar
]
}
///|
pub fn partial_pressures_to_mole_fractions(
partial_pressures_bar : Array[Double],
) -> Array[Double] raise VleError {
for pressure in partial_pressures_bar {
if pressure < 0.0 {
raise VleError::InvalidParameter("partial pressure cannot be negative")
}
}
normalize(partial_pressures_bar)
}
///|
pub fn composition_entropy(
composition : Array[Double],
) -> Double raise VleError {
let x = normalize(composition)
for i = 0, accumulator = 0.0; i < x.length(); {
let safe = clamp_positive(x[i])
continue i + 1, accumulator - safe * @math.ln(safe)
} nobreak {
accumulator
}
}
///|
pub fn composition_l1_distance(
first : Array[Double],
second : Array[Double],
) -> Double raise VleError {
assert_same_length(first.length(), second.length())
let a = normalize(first)
let b = normalize(second)
for i = 0, distance = 0.0; i < a.length(); {
continue i + 1, distance + abs_double(a[i] - b[i])
} nobreak {
distance
}
}
///|
pub fn composition_l2_distance(
first : Array[Double],
second : Array[Double],
) -> Double raise VleError {
vector_distance(first, second)
}
///|
pub fn argmax_component(values : Array[Double]) -> Int raise VleError {
if values.length() == 0 {
raise VleError::EmptyMixture
}
for i = 1, index = 0; i < values.length(); {
continue i + 1, if values[i] > values[index] { i } else { index }
} nobreak {
index
}
}
///|
pub fn argmin_component(values : Array[Double]) -> Int raise VleError {
if values.length() == 0 {
raise VleError::EmptyMixture
}
for i = 1, index = 0; i < values.length(); {
continue i + 1, if values[i] < values[index] { i } else { index }
} nobreak {
index
}
}
///|
pub fn composition_maximum(
composition : Array[Double],
) -> Double raise VleError {
let x = normalize(composition)
x[argmax_component(x)]
}
///|
pub fn composition_minimum(
composition : Array[Double],
) -> Double raise VleError {
let x = normalize(composition)
x[argmin_component(x)]
}
///|
pub fn blend_compositions(
first : Array[Double],
first_amount : Double,
second : Array[Double],
second_amount : Double,
) -> Array[Double] raise VleError {
if first_amount < 0.0 ||
second_amount < 0.0 ||
first_amount + second_amount <= 0.0 {
raise VleError::InvalidParameter(
"blend amounts must be nonnegative and nonzero",
)
}
assert_same_length(first.length(), second.length())
let x = normalize(first)
let y = normalize(second)
normalize(
[
for i in 0.. first_amount * x[i] + second_amount * y[i]
],
)
}
///|
pub fn scale_composition(
composition : Array[Double],
scale : Double,
) -> Array[Double] raise VleError {
if scale < 0.0 {
raise VleError::InvalidParameter("composition scale cannot be negative")
}
let x = normalize(composition)
[
for value in x => value * scale
]
}
///|
pub fn composition_is_valid(
composition : Array[Double],
tolerance : Double,
) -> Bool {
if composition.length() == 0 || tolerance <= 0.0 {
return false
}
for value in composition {
if value < 0.0 {
return false
}
}
let total = for i = 0, accumulator = 0.0; i < composition.length(); {
continue i + 1, accumulator + composition[i]
} nobreak {
accumulator
}
abs_double(total - 1.0) <= tolerance
}
///|
pub fn composition_normalization_error(composition : Array[Double]) -> Double {
abs_double(sum(composition) - 1.0)
}
///|
pub fn componentwise_product(
first : Array[Double],
second : Array[Double],
) -> Array[Double] raise VleError {
assert_same_length(first.length(), second.length())
[
for i in 0.. first[i] * second[i]
]
}
///|
pub fn componentwise_quotient(
numerator : Array[Double],
denominator : Array[Double],
) -> Array[Double] raise VleError {
assert_same_length(numerator.length(), denominator.length())
[
for i in 0.. {
if denominator[i] == 0.0 {
raise VleError::InvalidParameter("component denominator cannot be zero")
}
numerator[i] / denominator[i]
}
]
}
///|
pub fn weighted_average(
values : Array[Double],
weights : Array[Double],
) -> Double raise VleError {
assert_same_length(values.length(), weights.length())
let w = normalize(weights)
for i = 0, value = 0.0; i < values.length(); {
continue i + 1, value + values[i] * w[i]
} nobreak {
value
}
}