///|
pub(all) enum ProcessIssueCode {
EmptyProcess
DuplicateStreamId
NegativeFlowValue
InvalidTemperatureValue
EmptyComponentList
InvalidComposition
} derive(Debug, Eq)
///|
pub struct ProcessIssue {
code : ProcessIssueCode
stream_id : String
message : String
} derive(Debug)
///|
pub struct ProcessValidationReport {
valid : Bool
issue_count : Int
issues : Array[ProcessIssue]
} derive(Debug)
///|
pub fn ProcessValidationReport::has_code(
self : ProcessValidationReport,
code : ProcessIssueCode,
) -> Bool {
for issue in self.issues {
if issue.code == code {
return true
}
}
false
}
///|
fn validation_id_seen(ids : Array[String], id : String) -> Bool {
for current in ids {
if current == id {
return true
}
}
false
}
///|
fn stream_component_sum(stream : Stream) -> Double {
let mut total = 0.0
for component in stream.components {
total += component.molar_flow
}
total
}
///|
pub fn validate_process(streams : Array[Stream]) -> ProcessValidationReport {
let issues : Array[ProcessIssue] = []
let ids : Array[String] = []
if streams.length() == 0 {
issues.push({
code: EmptyProcess,
stream_id: "",
message: "the process contains no streams",
})
}
for stream in streams {
if validation_id_seen(ids, stream.id) {
issues.push({
code: DuplicateStreamId,
stream_id: stream.id,
message: "stream id is repeated",
})
} else {
ids.push(stream.id)
}
if stream.components.length() == 0 {
issues.push({
code: EmptyComponentList,
stream_id: stream.id,
message: "stream has no components",
})
}
if stream.temperature <= 0.0 {
issues.push({
code: InvalidTemperatureValue,
stream_id: stream.id,
message: "temperature must be above absolute zero",
})
}
for component in stream.components {
if component.molar_flow < 0.0 {
issues.push({
code: NegativeFlowValue,
stream_id: stream.id,
message: "component flow cannot be negative",
})
}
}
let total = stream_component_sum(stream)
if total > 0.0 {
let mut fraction_sum = 0.0
for component in stream.components {
fraction_sum += component.molar_flow / total
}
if (fraction_sum - 1.0).abs() > 1.0e-9 {
issues.push({
code: InvalidComposition,
stream_id: stream.id,
message: "component fractions do not close to one",
})
}
}
}
{ valid: issues.length() == 0, issue_count: issues.length(), issues }
}
///|
pub fn validation_summary(report : ProcessValidationReport) -> String {
if report.valid {
"process validation: valid"
} else {
"process validation: {report.issue_count} issue(s)"
}
}
///|
pub fn validate_temperature_range(
temperatures : Array[Double],
lower_k : Double,
upper_k : Double,
) -> Int {
let mut errors = 0
for temperature in temperatures {
if temperature < lower_k || temperature > upper_k {
errors += 1
}
}
errors
}
///|
pub fn validate_positive_values(values : Array[Double]) -> Int {
let mut errors = 0
for value in values {
if value <= 0.0 {
errors += 1
}
}
errors
}
///|
pub fn validate_monotonic(values : Array[Double], increasing : Bool) -> Bool {
if values.length() < 2 {
true
} else {
let mut valid = true
for i in 0..<(values.length() - 1) {
if increasing && values[i + 1] < values[i] {
valid = false
}
if !increasing && values[i + 1] > values[i] {
valid = false
}
}
valid
}
}
///|
pub fn validation_issue_count(reports : Array[ProcessValidationReport]) -> Int {
let mut total = 0
for report in reports {
total += report.issue_count
}
total
}
///|
pub fn all_processes_valid(reports : Array[ProcessValidationReport]) -> Bool {
for report in reports {
if !report.valid {
return false
}
}
true
}