///|
pub(all) struct ComponentProperty {
name : String
molecular_weight : Double
density : Double
boiling_temperature : Double
heat_capacity : Double
viscosity : Double
} derive(Debug, Eq, ToJson)
///|
pub(all) struct MixtureProperty {
temperature : Double
pressure : Double
composition : Array[Double]
molecular_weight : Double
density : Double
heat_capacity : Double
viscosity : Double
boiling_temperature : Double
} derive(Debug, Eq, ToJson)
///|
pub(all) struct PropertySensitivity {
component : String
composition_change : Double
density_change : Double
viscosity_change : Double
boiling_change : Double
} derive(Debug, Eq, ToJson)
///|
pub fn validate_composition(composition : ArrayView[Double]) -> Bool {
if composition.length() == 0 {
return false
}
let mut total = 0.0
for value in composition {
if value < 0.0 {
return false
}
total += value
}
(total - 1.0).abs() <= 1.0e-8
}
///|
pub fn mixture_normalize_composition(
composition : ArrayView[Double],
) -> Array[Double] raise @domain.ExtractionError {
if composition.length() == 0 {
raise @domain.ExtractionError::InsufficientData(required=1, actual=0)
}
let mut total = 0.0
for value in composition {
if value < 0.0 {
raise @domain.ExtractionError::InvalidFraction(name="composition", value~)
}
total += value
}
if total == 0.0 {
raise @domain.ExtractionError::InvalidComposition(total~)
}
[
for value in composition => value / total
]
}
///|
pub fn mixture_molecular_weight(
components : ArrayView[ComponentProperty],
composition : ArrayView[Double],
) -> Double raise @domain.ExtractionError {
if components.length() != composition.length() || components.length() == 0 {
raise @domain.ExtractionError::InvalidArgument(
message="component composition dimensions differ",
)
}
let normalized = mixture_normalize_composition(composition)
let mut result = 0.0
for index in 0.. Double raise @domain.ExtractionError {
if components.length() != composition.length() || components.length() == 0 {
raise @domain.ExtractionError::InvalidArgument(
message="component composition dimensions differ",
)
}
let normalized = mixture_normalize_composition(composition)
let mut reciprocal = 0.0
for index in 0.. Double raise @domain.ExtractionError {
if components.length() != composition.length() || components.length() == 0 {
raise @domain.ExtractionError::InvalidArgument(
message="component composition dimensions differ",
)
}
let normalized = mixture_normalize_composition(composition)
let mut result = 0.0
for index in 0.. Double raise @domain.ExtractionError {
if components.length() != composition.length() || components.length() == 0 {
raise @domain.ExtractionError::InvalidArgument(
message="component composition dimensions differ",
)
}
let normalized = mixture_normalize_composition(composition)
let mut logarithm = 0.0
for index in 0.. Double raise @domain.ExtractionError {
if components.length() != composition.length() || components.length() == 0 {
return 0.0
}
let normalized = mixture_normalize_composition(composition)
let mut result = 0.0
for index in 0.. MixtureProperty raise @domain.ExtractionError {
let normalized = mixture_normalize_composition(composition)
{
temperature,
pressure,
composition: normalized,
molecular_weight: mixture_molecular_weight(components, composition),
density: mixture_density(components, composition),
heat_capacity: mixture_heat_capacity(components, composition),
viscosity: mixture_viscosity(components, composition),
boiling_temperature: mixture_boiling_temperature(components, composition),
}
}
///|
pub fn component_contribution(
property : ComponentProperty,
composition : Double,
) -> (Double, Double, Double, Double) {
(
property.density * composition,
property.viscosity * composition,
property.heat_capacity * composition,
property.boiling_temperature * composition,
)
}
///|
pub fn mixture_excess_density(
mixture : MixtureProperty,
ideal_density : Double,
) -> Double {
mixture.density - ideal_density
}
///|
pub fn mixture_density_factor(
mixture : MixtureProperty,
reference_density : Double,
) -> Double {
if reference_density == 0.0 {
0.0
} else {
mixture.density / reference_density
}
}
///|
pub fn mixture_viscosity_factor(
mixture : MixtureProperty,
reference_viscosity : Double,
) -> Double {
if reference_viscosity == 0.0 {
0.0
} else {
mixture.viscosity / reference_viscosity
}
}
///|
pub fn mixture_temperature_shift(
mixture : MixtureProperty,
target_temperature : Double,
thermal_expansion : Double,
) -> MixtureProperty {
let shift = target_temperature - mixture.temperature
{
temperature: target_temperature,
pressure: mixture.pressure,
composition: mixture.composition,
molecular_weight: mixture.molecular_weight,
density: mixture.density * (1.0 - thermal_expansion * shift).max(0.01),
heat_capacity: mixture.heat_capacity,
viscosity: mixture.viscosity * (1.0 - 0.01 * shift).max(0.05),
boiling_temperature: mixture.boiling_temperature,
}
}
///|
pub fn mixture_pressure_shift(
mixture : MixtureProperty,
target_pressure : Double,
compressibility : Double,
) -> MixtureProperty {
let ratio = if mixture.pressure == 0.0 {
1.0
} else {
target_pressure / mixture.pressure
}
{
temperature: mixture.temperature,
pressure: target_pressure,
composition: mixture.composition,
molecular_weight: mixture.molecular_weight,
density: mixture.density * (1.0 + compressibility * (ratio - 1.0)),
heat_capacity: mixture.heat_capacity,
viscosity: mixture.viscosity,
boiling_temperature: mixture.boiling_temperature,
}
}
///|
pub fn property_sensitivity(
baseline : MixtureProperty,
perturbed : MixtureProperty,
component : String,
composition_change : Double,
) -> PropertySensitivity {
{
component,
composition_change,
density_change: perturbed.density - baseline.density,
viscosity_change: perturbed.viscosity - baseline.viscosity,
boiling_change: perturbed.boiling_temperature - baseline.boiling_temperature,
}
}
///|
pub fn mixture_component_count(mixture : MixtureProperty) -> Int {
mixture.composition.length()
}
///|
pub fn mixture_dominant_component(mixture : MixtureProperty) -> Int {
if mixture.composition.length() == 0 {
return -1
}
let mut selected = 0
for index in 1.. mixture.composition[selected] {
selected = index
}
}
selected
}
///|
pub fn mixture_minor_fraction(mixture : MixtureProperty) -> Double {
if mixture.composition.length() == 0 {
return 0.0
}
let dominant = mixture.composition[mixture_dominant_component(mixture)]
1.0 - dominant
}
///|
pub fn mixture_composition_distance(
first : MixtureProperty,
second : MixtureProperty,
) -> Double {
let count = first.composition.length().min(second.composition.length())
let mut total = 0.0
for index in 0.. String {
let lines = [
"temperature,pressure,molecular_weight,density,heat_capacity,viscosity,boiling_temperature",
]
for property in properties {
lines.push(
property.temperature.to_string() +
"," +
property.pressure.to_string() +
"," +
property.molecular_weight.to_string() +
"," +
property.density.to_string() +
"," +
property.heat_capacity.to_string() +
"," +
property.viscosity.to_string() +
"," +
property.boiling_temperature.to_string(),
)
}
lines.join("\n")
}
///|
pub fn mixture_property_report(property : MixtureProperty) -> String {
"T=" +
property.temperature.to_string() +
",P=" +
property.pressure.to_string() +
",MW=" +
property.molecular_weight.to_string() +
",density=" +
property.density.to_string() +
",viscosity=" +
property.viscosity.to_string() +
",dominant_component=" +
mixture_dominant_component(property).to_string()
}
///|
fn mixture_approximate_log(value : Double) -> Double {
if value <= 0.0 {
return -30.0
}
let scaled = if value > 1.0 { value } else { 1.0 / value }
let mut exponent = 0
let mut current = scaled
while current >= 2.0 && exponent < 30 {
current /= 2.0
exponent += 1
}
while current < 1.0 && exponent > -30 {
current *= 2.0
exponent -= 1
}
let z = (current - 1.0) / (current + 1.0)
let z2 = z * z
let series = 2.0 * (z + z * z2 / 3.0 + z * z2 * z2 / 5.0)
if value > 1.0 {
series + exponent.to_double() * 0.69314718056
} else {
-series - exponent.to_double() * 0.69314718056
}
}
///|
fn mixture_approximate_exp(value : Double) -> Double {
let bounded = value.max(-30.0).min(30.0)
let mut result = 1.0
let mut term = 1.0
for index in 1..<18 {
term *= bounded / index.to_double()
result += term
}
result.max(0.0)
}