From 872523449badd245332e4b559eb063824cdd5bdf Mon Sep 17 00:00:00 2001 From: Alex Selimov Date: Sun, 19 Apr 2026 06:25:31 -0400 Subject: [PATCH 1/3] Refactor gas mixture to separate the gasses and condensed species --- src/mixtures/gas_mixture.rs | 93 +++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 30 deletions(-) diff --git a/src/mixtures/gas_mixture.rs b/src/mixtures/gas_mixture.rs index d88eeb4..21990b0 100644 --- a/src/mixtures/gas_mixture.rs +++ b/src/mixtures/gas_mixture.rs @@ -6,10 +6,16 @@ use crate::{ properties::thermo_fit::{Phase, SpeciesThermoData}, }; +pub struct MixtureComponent { + //kg-moles component/kg_mixture + n: f64, + s: SpeciesThermoData, +} + pub struct GasMixture { - pub(crate) ns: Vec, pub(crate) nsum: f64, - pub(crate) species: Vec, + pub(crate) gasses: Vec, + pub(crate) condensed: Vec, pub(crate) coeffs: Matrix, pub(crate) elements: HashMap, pub(crate) binitial: Vec, @@ -52,10 +58,35 @@ impl GasMixture { let binitial = get_b_current(&elements, species, &ns); + // Now separate SpeciesThermoData in gas and condensed MixtureComponents + let gasses = ns + .iter() + .zip(species.iter()) + .filter_map(|(n, s)| match s.phase { + Phase::Gas => Some(MixtureComponent { + n: *n, + s: s.clone(), + }), + Phase::Condensed => None, + }) + .collect(); + + let condensed = ns + .iter() + .zip(species.iter()) + .filter_map(|(n, s)| match s.phase { + Phase::Gas => None, + Phase::Condensed => Some(MixtureComponent { + n: *n, + s: s.clone(), + }), + }) + .collect(); + GasMixture { - ns, nsum, - species: species.to_vec(), + gasses, + condensed, coeffs, elements, binitial, @@ -64,18 +95,18 @@ impl GasMixture { // Calculate the normalized chemical potential (μ/RT) for each component in the mixture. // Equations 2.11 from reference paper pub fn gas_chem_potentials_over_rt(&self, temp: f64, pressure: f64) -> Vec { - self.ns + self.gasses .iter() - .zip(self.species.iter()) - .map(|(n, s)| -> f64 { - match s.phase { + .chain(self.condensed.iter()) + .map(|c| -> f64 { + match c.s.phase { Phase::Gas => { - let p = s - .polynomial_at(temp) - .expect("Gas doesn't have a polynomial"); + let p = + c.s.polynomial_at(temp) + .expect("Gas doesn't have a polynomial"); p.h_over_rt(temp) - p.s_over_r(temp) + (pressure / P_REF).ln() - + (n / self.nsum).ln() + + (c.n / self.nsum).ln() } Phase::Condensed => todo!(), } @@ -87,16 +118,16 @@ impl GasMixture { // // Equations 2.17 from reference paper pub fn gas_entropies_over_rt(&self, temp: f64, pressure: f64) -> Vec { - self.ns + self.gasses .iter() - .zip(self.species.iter()) - .map(|(n, s)| -> f64 { - match s.phase { + .chain(self.condensed.iter()) + .map(|c| -> f64 { + match c.s.phase { Phase::Gas => { - let p = s - .polynomial_at(temp) - .expect("Gas doesn't have a polynomial"); - p.s_over_r(temp) - (n / self.nsum).ln() - (pressure / P_REF).ln() + let p = + c.s.polynomial_at(temp) + .expect("Gas doesn't have a polynomial"); + p.s_over_r(temp) - (c.n / self.nsum).ln() - (pressure / P_REF).ln() } Phase::Condensed => todo!(), } @@ -108,16 +139,16 @@ impl GasMixture { // Note that the enthalpy doesn't have a dependence on the pressure. // Equation 2.14 from the paper pub fn mixture_h_over_rt(&self, temp: f64) -> Vec { - self.ns + self.gasses .iter() - .zip(self.species.iter()) - .map(|(n, s)| -> f64 { - match s.phase { + .chain(self.condensed.iter()) + .map(|c| -> f64 { + match c.s.phase { Phase::Gas => { - let p = s - .polynomial_at(temp) - .expect("Gas doesn't have a polynomial"); - n * p.h_over_rt(temp) + let p = + c.s.polynomial_at(temp) + .expect("Gas doesn't have a polynomial"); + c.n * p.h_over_rt(temp) } Phase::Condensed => todo!(), } @@ -163,10 +194,12 @@ mod test { 0.01665842352342649, 0.02498763528513974, ]; - assert_vec_delta!(expected_ns, gas.ns, 1e-12); + let ns: Vec = gas.gasses.iter().map(|c| c.n).collect(); + + assert_vec_delta!(expected_ns, ns, 1e-12); assert_delta!(gas.nsum, 0.04997527057027948, 1e-12); - assert_delta!(gas.coeffs.get(0, 0).unwrap(), 2.0, 1e-12); + assert_delta!(gas.coefs.get(0, 0).unwrap(), 2.0, 1e-12); assert_delta!(gas.coeffs.get(0, 1).unwrap(), 0.0, 1e-12); assert_delta!(gas.coeffs.get(0, 2).unwrap(), 2.0, 1e-12); assert_delta!(gas.coeffs.get(1, 0).unwrap(), 0.0, 1e-12); From 9e156bc8e32cf882688b355136f0e42ff4fadea0 Mon Sep 17 00:00:00 2001 From: Alex Selimov Date: Sun, 19 Apr 2026 06:28:21 -0400 Subject: [PATCH 2/3] Fix typo in gas mixture --- src/mixtures/gas_mixture.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mixtures/gas_mixture.rs b/src/mixtures/gas_mixture.rs index 21990b0..9b9c26b 100644 --- a/src/mixtures/gas_mixture.rs +++ b/src/mixtures/gas_mixture.rs @@ -199,7 +199,7 @@ mod test { assert_vec_delta!(expected_ns, ns, 1e-12); assert_delta!(gas.nsum, 0.04997527057027948, 1e-12); - assert_delta!(gas.coefs.get(0, 0).unwrap(), 2.0, 1e-12); + assert_delta!(gas.coeffs.get(0, 0).unwrap(), 2.0, 1e-12); assert_delta!(gas.coeffs.get(0, 1).unwrap(), 0.0, 1e-12); assert_delta!(gas.coeffs.get(0, 2).unwrap(), 2.0, 1e-12); assert_delta!(gas.coeffs.get(1, 0).unwrap(), 0.0, 1e-12); From 0910872a5513d1f4c943240f6c36c39874808b3c Mon Sep 17 00:00:00 2001 From: Alex Selimov Date: Sun, 19 Apr 2026 06:46:35 -0400 Subject: [PATCH 3/3] Another gasmixture refactor to make the code less fortrany --- src/mixtures/gas_mixture.rs | 55 +++++++++++++------------------------ 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/src/mixtures/gas_mixture.rs b/src/mixtures/gas_mixture.rs index 9b9c26b..65085eb 100644 --- a/src/mixtures/gas_mixture.rs +++ b/src/mixtures/gas_mixture.rs @@ -9,6 +9,8 @@ use crate::{ pub struct MixtureComponent { //kg-moles component/kg_mixture n: f64, + // Coefficients + a: Vec, s: SpeciesThermoData, } @@ -16,7 +18,6 @@ pub struct GasMixture { pub(crate) nsum: f64, pub(crate) gasses: Vec, pub(crate) condensed: Vec, - pub(crate) coeffs: Matrix, pub(crate) elements: HashMap, pub(crate) binitial: Vec, } @@ -46,48 +47,30 @@ impl GasMixture { } } - // Now build the coefficients - let mut coeffs = Matrix::new(ei, species.len(), 0.0); - for (j, s) in species.iter().enumerate() { - s.elements.iter().for_each(|e| { - // Safe to unwrap because elements should always have e - let i = *elements.get(&e.element).unwrap(); - coeffs.set(i, j, e.count); - }); - } - let binitial = get_b_current(&elements, species, &ns); // Now separate SpeciesThermoData in gas and condensed MixtureComponents - let gasses = ns + let (gasses, condensed) = ns .iter() .zip(species.iter()) - .filter_map(|(n, s)| match s.phase { - Phase::Gas => Some(MixtureComponent { + .map(|(n, s)| { + let mut a = vec![0.0; elements.len()]; + s.elements.iter().for_each(|e| { + let i = elements.get(&e.element).unwrap(); + a[*i] += e.count; + }); + MixtureComponent { n: *n, + a, s: s.clone(), - }), - Phase::Condensed => None, + } }) - .collect(); - - let condensed = ns - .iter() - .zip(species.iter()) - .filter_map(|(n, s)| match s.phase { - Phase::Gas => None, - Phase::Condensed => Some(MixtureComponent { - n: *n, - s: s.clone(), - }), - }) - .collect(); + .partition(|c| matches!(c.s.phase, Phase::Gas)); GasMixture { nsum, gasses, condensed, - coeffs, elements, binitial, } @@ -199,12 +182,12 @@ mod test { assert_vec_delta!(expected_ns, ns, 1e-12); assert_delta!(gas.nsum, 0.04997527057027948, 1e-12); - assert_delta!(gas.coeffs.get(0, 0).unwrap(), 2.0, 1e-12); - assert_delta!(gas.coeffs.get(0, 1).unwrap(), 0.0, 1e-12); - assert_delta!(gas.coeffs.get(0, 2).unwrap(), 2.0, 1e-12); - assert_delta!(gas.coeffs.get(1, 0).unwrap(), 0.0, 1e-12); - assert_delta!(gas.coeffs.get(1, 1).unwrap(), 2.0, 1e-12); - assert_delta!(gas.coeffs.get(1, 2).unwrap(), 1.0, 1e-12); + assert_delta!(gas.gasses[0].a[0], 2.0, 1e-12); + assert_delta!(gas.gasses[1].a[0], 0.0, 1e-12); + assert_delta!(gas.gasses[2].a[0], 2.0, 1e-12); + assert_delta!(gas.gasses[0].a[1], 0.0, 1e-12); + assert_delta!(gas.gasses[1].a[1], 2.0, 1e-12); + assert_delta!(gas.gasses[2].a[1], 1.0, 1e-12); let expected_b = [0.06663369409370597, 0.05830448233199272]; assert_vec_delta!(gas.binitial, expected_b, 1e-12);