use crate::Thermodynamics::ChemEquilibrium::equilibrium_constant_problem::EquilibriumConstantProblem;
use crate::Thermodynamics::ChemEquilibrium::equilibrium_ids::ReactionId;
use crate::Thermodynamics::ChemEquilibrium::equilibrium_nonlinear::ReactionExtentError;
use crate::Thermodynamics::ChemEquilibrium::equilibrium_problem::EquilibriumConditions;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EquilibriumConstantValidationMode {
#[default]
Off,
WhenApplicable,
Required,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EquilibriumConstantValidationTolerances {
pub max_log_residual: f64,
}
impl Default for EquilibriumConstantValidationTolerances {
fn default() -> Self {
Self {
max_log_residual: 1e-8,
}
}
}
impl EquilibriumConstantValidationTolerances {
pub fn validate(self) -> Result<Self, ReactionExtentError> {
if !self.max_log_residual.is_finite() || self.max_log_residual <= 0.0 {
return Err(ReactionExtentError::InvalidProblem {
field: "equilibrium_constant_tolerances",
message: "max_log_residual must be finite and strictly positive".to_string(),
});
}
Ok(self)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EquilibriumConstantReactionReport {
pub reaction: ReactionId,
pub ln_q: f64,
pub ln_k: f64,
pub log_residual: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EquilibriumConstantValidationReport {
pub conditions: EquilibriumConditions,
pub reactions: Vec<EquilibriumConstantReactionReport>,
pub max_abs_log_residual: f64,
pub accepted: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EquilibriumConstantValidationRow {
pub section: &'static str,
pub label: String,
pub value: String,
}
impl fmt::Display for EquilibriumConstantValidationRow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {} = {}", self.section, self.label, self.value)
}
}
impl EquilibriumConstantValidationReport {
pub fn summary_rows(&self) -> Vec<EquilibriumConstantValidationRow> {
let mut rows = vec![
EquilibriumConstantValidationRow {
section: "validation",
label: "temperature".to_string(),
value: format!("{:.6}", self.conditions.temperature()),
},
EquilibriumConstantValidationRow {
section: "validation",
label: "pressure".to_string(),
value: format!("{:.6}", self.conditions.pressure()),
},
EquilibriumConstantValidationRow {
section: "validation",
label: "reaction_count".to_string(),
value: self.reactions.len().to_string(),
},
EquilibriumConstantValidationRow {
section: "validation",
label: "max_abs_log_residual".to_string(),
value: format!("{:.6e}", self.max_abs_log_residual),
},
EquilibriumConstantValidationRow {
section: "validation",
label: "accepted".to_string(),
value: self.accepted.to_string(),
},
];
for reaction in &self.reactions {
rows.push(EquilibriumConstantValidationRow {
section: "reaction",
label: reaction.reaction.index().to_string(),
value: format!(
"lnQ={:.6e}, lnK={:.6e}, residual={:.6e}",
reaction.ln_q, reaction.ln_k, reaction.log_residual
),
});
}
rows
}
}
impl fmt::Display for EquilibriumConstantValidationReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for row in self.summary_rows() {
writeln!(f, "{row}")?;
}
Ok(())
}
}
pub fn validate_equilibrium_constants(
problem: &EquilibriumConstantProblem,
candidate_moles: &[f64],
tolerances: EquilibriumConstantValidationTolerances,
) -> Result<EquilibriumConstantValidationReport, ReactionExtentError> {
let tolerances = tolerances.validate()?;
let mut reactions = Vec::with_capacity(problem.basis().reaction_count());
let mut max_abs_log_residual = 0.0_f64;
for index in 0..problem.basis().reaction_count() {
let reaction = problem.basis().reaction_id(index)?;
let ln_q = problem.ln_reaction_quotient(reaction, candidate_moles)?;
let ln_k = problem.ln_equilibrium_constant(reaction)?;
let log_residual = ln_q - ln_k;
max_abs_log_residual = max_abs_log_residual.max(log_residual.abs());
reactions.push(EquilibriumConstantReactionReport {
reaction,
ln_q,
ln_k,
log_residual,
});
}
Ok(EquilibriumConstantValidationReport {
conditions: problem.conditions(),
reactions,
max_abs_log_residual,
accepted: max_abs_log_residual <= tolerances.max_log_residual,
})
}