///|
fn validation_result(
error_count : Int,
warning_count : Int,
messages : Array[String],
) -> ValidationSummary {
ValidationSummary::{
valid: error_count == 0,
error_count,
warning_count,
messages,
}
}
///|
pub fn validate_thermo_inputs(
components~ : Array[Component],
composition~ : Array[Double],
temperature_k~ : Double,
pressure_bar~ : Double,
) -> ValidationSummary {
let messages : Array[String] = []
if components.length() == 0 {
messages.push("components cannot be empty")
}
if composition.length() == 0 {
messages.push("composition cannot be empty")
}
let length_error = if components.length() != composition.length() {
1
} else {
0
}
if length_error > 0 {
messages.push("component and composition lengths differ")
}
let negative_count = for i = 0, count = 0; i < composition.length(); {
if composition[i] < 0.0 {
messages.push("composition contains a negative fraction")
continue i + 1, count + 1
} else {
continue i + 1, count
}
} nobreak {
count
}
let total = for i = 0, value = 0.0; i < composition.length(); {
continue i + 1, value + composition[i]
} nobreak {
value
}
let sum_error = if composition.length() > 0 &&
abs_double(total - 1.0) > 1.0E-8 {
messages.push("composition is not normalized")
1
} else {
0
}
let temperature_error = if temperature_k <= 0.0 {
messages.push("temperature must be positive")
1
} else {
0
}
let pressure_error = if pressure_bar <= 0.0 {
messages.push("pressure must be positive")
1
} else {
0
}
let range_warnings = for i = 0, count = 0; i < components.length(); {
if !components[i].antoine.is_in_range(temperature_k) {
messages.push(
components[i].name + ": temperature is outside Antoine range",
)
continue i + 1, count + 1
} else {
continue i + 1, count
}
} nobreak {
count
}
validation_result(
length_error +
negative_count +
sum_error +
temperature_error +
pressure_error,
range_warnings,
messages,
)
}
///|
pub fn validate_composition_for_model(
composition : Array[Double],
expected_dimension : Int,
) -> ValidationSummary {
let messages : Array[String] = []
let dimension_error = if composition.length() != expected_dimension {
1
} else {
0
}
if dimension_error > 0 {
messages.push("composition dimension does not match model")
}
let negative_count = for i = 0, count = 0; i < composition.length(); {
if composition[i] < 0.0 {
messages.push("composition fraction is negative")
continue i + 1, count + 1
} else {
continue i + 1, count
}
} nobreak {
count
}
let total = for i = 0, value = 0.0; i < composition.length(); {
continue i + 1, value + composition[i]
} nobreak {
value
}
let sum_error = if composition.length() > 0 &&
abs_double(total - 1.0) > 1.0E-8 {
messages.push("composition fractions do not sum to one")
1
} else {
0
}
validation_result(dimension_error + negative_count + sum_error, 0, messages)
}
///|
pub fn validate_thermo_property(property : ThermoProperty) -> ValidationSummary {
let messages : Array[String] = []
let warning_count = 0
if property.molecular_weight_g_per_mol <= 0.0 {
messages.push(
property.component.name + ": molecular weight is not positive",
)
}
if property.latent_heat_j_per_mol <= 0.0 {
messages.push(property.component.name + ": latent heat is not positive")
}
let cp_sample_error = try {
ignore(
property.liquid_heat_capacity.evaluate(property.reference_temperature_k),
)
0
} catch {
_ => {
messages.push(
property.component.name + ": liquid heat capacity cannot be evaluated",
)
1
}
} noraise {
_ => 0
}
let positivity_error = if property.molecular_weight_g_per_mol <= 0.0 {
1
} else {
0
}
let latent_error = if property.latent_heat_j_per_mol <= 0.0 { 1 } else { 0 }
validation_result(
cp_sample_error + positivity_error + latent_error,
warning_count,
messages,
)
}
///|
pub fn validate_property_catalog(
properties : Array[ThermoProperty],
) -> ValidationSummary {
let messages : Array[String] = []
let errors = for i = 0, count = 0; i < properties.length(); {
let summary = validate_thermo_property(properties[i])
for message in summary.messages {
messages.push(message)
}
continue i + 1, count + summary.error_count
} nobreak {
count
}
validation_result(errors, 0, messages)
}
///|
pub fn merge_validation_summaries(
first : ValidationSummary,
second : ValidationSummary,
) -> ValidationSummary {
let messages : Array[String] = []
for message in first.messages {
messages.push(message)
}
for message in second.messages {
messages.push(message)
}
validation_result(
first.error_count + second.error_count,
first.warning_count + second.warning_count,
messages,
)
}
///|
pub fn validation_risk_score(summary : ValidationSummary) -> Double {
summary.error_count.to_double() * 10.0 + summary.warning_count.to_double()
}
///|
pub fn validation_is_strict(summary : ValidationSummary) -> Bool {
summary.valid && summary.warning_count == 0
}
///|
pub fn validation_message_count(summary : ValidationSummary) -> Int {
summary.messages.length()
}
///|
pub fn validate_phase_point(point : PhasePoint) -> ValidationSummary {
let composition = validate_composition_for_model(
point.composition,
point.composition.length(),
)
let messages : Array[String] = []
for message in composition.messages {
messages.push(message)
}
let temperature_error = if point.temperature_k <= 0.0 {
messages.push("phase-point temperature is not positive")
1
} else {
0
}
let pressure_error = if point.pressure_bar <= 0.0 {
messages.push("phase-point pressure is not positive")
1
} else {
0
}
validation_result(
composition.error_count + temperature_error + pressure_error,
composition.warning_count,
messages,
)
}
///|
pub fn validate_binary_parameters(
first : Double,
second : Double,
tolerance : Double,
) -> ValidationSummary {
let messages : Array[String] = []
let error_count = if tolerance <= 0.0 {
messages.push("parameter tolerance must be positive")
1
} else {
0
}
let warning_count = if abs_double(first) < tolerance ||
abs_double(second) < tolerance {
messages.push("one binary parameter is close to zero")
1
} else {
0
}
validation_result(error_count, warning_count, messages)
}
///|
pub fn validate_solver_options(options : SolverOptions) -> ValidationSummary {
let messages : Array[String] = []
let error_count = if options.tolerance <= 0.0 || options.max_iterations <= 0 {
messages.push("solver options are invalid")
1
} else {
0
}
validation_result(error_count, 0, messages)
}
///|
pub fn validate_temperature_range(
temperature_k : Double,
low_k : Double,
high_k : Double,
) -> ValidationSummary {
let messages : Array[String] = []
let invalid_range = low_k <= 0.0 || high_k <= low_k
let out_of_range = temperature_k < low_k || temperature_k > high_k
if invalid_range {
messages.push("temperature range is invalid")
}
if out_of_range {
messages.push("temperature is outside requested range")
}
validation_result(
(if invalid_range { 1 } else { 0 }) + (if out_of_range { 1 } else { 0 }),
0,
messages,
)
}
///|
pub fn validate_pressure_range(
pressure_bar : Double,
low_bar : Double,
high_bar : Double,
) -> ValidationSummary {
let messages : Array[String] = []
let invalid_range = low_bar <= 0.0 || high_bar <= low_bar
let out_of_range = pressure_bar < low_bar || pressure_bar > high_bar
if invalid_range {
messages.push("pressure range is invalid")
}
if out_of_range {
messages.push("pressure is outside requested range")
}
validation_result(
(if invalid_range { 1 } else { 0 }) + (if out_of_range { 1 } else { 0 }),
0,
messages,
)
}
///|
pub fn validate_source_metadata(
source_name : String,
source_description : String,
) -> ValidationSummary {
let messages : Array[String] = []
let missing_name = source_name.trim().length() == 0
let missing_description = source_description.trim().length() == 0
if missing_name {
messages.push("source name is missing")
}
if missing_description {
messages.push("source description is missing")
}
validation_result(
(if missing_name { 1 } else { 0 }) +
(if missing_description { 1 } else { 0 }),
0,
messages,
)
}
///|
pub fn validate_correlation_pair(
lower_temperature_k : Double,
upper_temperature_k : Double,
lower_pressure_bar : Double,
upper_pressure_bar : Double,
) -> ValidationSummary {
let messages : Array[String] = []
let temperature_error = if lower_temperature_k <= 0.0 ||
upper_temperature_k <= lower_temperature_k {
messages.push("correlation temperatures are invalid")
1
} else {
0
}
let pressure_error = if lower_pressure_bar <= 0.0 || upper_pressure_bar <= 0.0 {
messages.push("correlation pressures are invalid")
1
} else {
0
}
let monotonic_warning = if upper_temperature_k > lower_temperature_k &&
upper_pressure_bar <= lower_pressure_bar {
messages.push("pressure is not increasing with temperature")
1
} else {
0
}
validation_result(
temperature_error + pressure_error,
monotonic_warning,
messages,
)
}
///|
pub fn validate_pressure_prediction(
reference_pressure_bar : Double,
predicted_pressure_bar : Double,
tolerance_bar : Double,
) -> ValidationSummary {
let messages : Array[String] = []
let input_error = if reference_pressure_bar <= 0.0 ||
predicted_pressure_bar <= 0.0 ||
tolerance_bar < 0.0 {
messages.push("pressure prediction inputs are invalid")
1
} else {
0
}
let difference = abs_double(predicted_pressure_bar - reference_pressure_bar)
let warning = if input_error == 0 && difference > tolerance_bar {
messages.push("pressure prediction exceeds tolerance")
1
} else {
0
}
validation_result(input_error, warning, messages)
}
///|
pub fn validation_has_warning(summary : ValidationSummary) -> Bool {
summary.warning_count > 0
}
///|
pub fn validation_has_errors(summary : ValidationSummary) -> Bool {
summary.error_count > 0
}
///|
pub fn validation_summary_is_empty(summary : ValidationSummary) -> Bool {
summary.error_count == 0 &&
summary.warning_count == 0 &&
summary.messages.length() == 0
}
///|
pub fn validation_score_passes(
summary : ValidationSummary,
maximum_score : Double,
) -> Bool {
maximum_score >= 0.0 && validation_risk_score(summary) <= maximum_score
}
///|
pub fn validate_component_name(name : String) -> ValidationSummary {
let messages : Array[String] = []
let missing = name.trim().length() == 0
let too_long = name.length() > 128
if missing {
messages.push("component name is missing")
}
if too_long {
messages.push("component name is too long")
}
validation_result(
(if missing { 1 } else { 0 }) + (if too_long { 1 } else { 0 }),
0,
messages,
)
}
///|
pub fn validate_temperature_delta(
temperature_delta_k : Double,
maximum_delta_k : Double,
) -> ValidationSummary {
let messages : Array[String] = []
let invalid = temperature_delta_k <= 0.0 ||
maximum_delta_k <= 0.0 ||
temperature_delta_k > maximum_delta_k
if invalid {
messages.push("temperature delta is outside allowed bounds")
}
validation_result(if invalid { 1 } else { 0 }, 0, messages)
}
///|
pub fn validate_unit_name(unit : String) -> ValidationSummary {
let messages : Array[String] = []
let missing = unit.trim().length() == 0
let too_long = unit.length() > 32
if missing {
messages.push("unit name is missing")
}
if too_long {
messages.push("unit name is too long")
}
validation_result(
(if missing { 1 } else { 0 }) + (if too_long { 1 } else { 0 }),
0,
messages,
)
}
///|
pub fn validate_array_length(actual : Int, expected : Int) -> ValidationSummary {
let messages : Array[String] = []
let invalid = actual < 0 || expected < 0 || actual != expected
if invalid {
messages.push("array length does not match expected dimension")
}
validation_result(if invalid { 1 } else { 0 }, 0, messages)
}