use crate::Thermodynamics::ChemEquilibrium::equilibrium_log_moles::Phase;
use crate::Thermodynamics::ChemEquilibrium::equilibrium_nonlinear::ReactionExtentError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PhaseActivityModel {
IdealGas,
IdealSolution,
}
impl PhaseActivityModel {
pub fn log_phase_offset(
self,
pressure: f64,
reference_pressure: f64,
) -> Result<f64, ReactionExtentError> {
for (parameter, value) in [
("pressure", pressure),
("reference_pressure", reference_pressure),
] {
if !value.is_finite() || value <= 0.0 {
return Err(ReactionExtentError::InvalidConditions { parameter, value });
}
}
Ok(match self {
Self::IdealGas => (pressure / reference_pressure).ln(),
Self::IdealSolution => 0.0,
})
}
pub fn log_activity(
self,
species_moles: f64,
phase_moles: f64,
pressure: f64,
reference_pressure: f64,
) -> Result<f64, ReactionExtentError> {
if !species_moles.is_finite() || species_moles <= 0.0 {
return Err(ReactionExtentError::InvalidCandidate {
field: "activity_species_moles",
message: format!(
"species mole number must be finite and positive, got {species_moles}"
),
});
}
if !phase_moles.is_finite() || phase_moles <= 0.0 {
return Err(ReactionExtentError::InvalidCandidate {
field: "activity_phase_moles",
message: format!(
"phase mole number must be finite and positive, got {phase_moles}"
),
});
}
Ok((species_moles / phase_moles).ln()
+ self.log_phase_offset(pressure, reference_pressure)?)
}
pub fn d_log_activity_d_temperature(
self,
pressure: f64,
reference_pressure: f64,
) -> Result<f64, ReactionExtentError> {
self.log_phase_offset(pressure, reference_pressure)?;
Ok(match self {
Self::IdealGas | Self::IdealSolution => 0.0,
})
}
}
pub fn phase_activity_models(phases: &[Phase]) -> Vec<PhaseActivityModel> {
phases.iter().map(|phase| phase.kind).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gas_and_solution_activity_offsets_follow_their_contracts() {
assert!(
(PhaseActivityModel::IdealGas
.log_phase_offset(202_650.0, 101_325.0)
.unwrap()
- 2.0_f64.ln())
.abs()
< 1e-12
);
assert_eq!(
PhaseActivityModel::IdealSolution
.log_phase_offset(202_650.0, 101_325.0)
.unwrap(),
0.0
);
}
#[test]
fn pure_condensed_phase_has_unit_activity() {
assert_eq!(
PhaseActivityModel::IdealSolution
.log_activity(3.0, 3.0, 101_325.0, 101_325.0)
.unwrap(),
0.0
);
}
#[test]
fn supported_ideal_activities_have_no_explicit_temperature_derivative() {
for model in [
PhaseActivityModel::IdealGas,
PhaseActivityModel::IdealSolution,
] {
assert_eq!(
model
.d_log_activity_d_temperature(101_325.0, 101_325.0)
.unwrap(),
0.0
);
}
assert!(
PhaseActivityModel::IdealGas
.d_log_activity_d_temperature(0.0, 101_325.0)
.is_err()
);
}
}