use crate::Kinetics::error::{KineticsError, KineticsResult};
use crate::Kinetics::molmass::{
calculate_molar_mass_of_vector_of_subs, create_elem_composition_matrix,
};
use nalgebra::DMatrix;
use regex::Regex;
use std::collections::HashMap;
fn clean_off_artifacts(item: &mut String) -> &mut String {
#[allow(unused_variables)]
let items_to_clean = [" (", "(", "M)", "M)", ">", "S)", "s)"];
if item.contains("M)")
|| item.contains("M)")
|| item.contains(" (")
|| item.contains("(")
|| item.contains(">")
|| item.contains("S)")
|| item.contains("s)")
{
*item = item
.replace("M)", "")
.replace("M)", "")
.replace(" (", "")
.replace("(", "")
.replace(">", "")
.replace("S)", "")
.replace("s)", "");
return item;
} else {
item
}
}
pub fn analyse_substances(
half_reaction: &str,
) -> KineticsResult<(Vec<String>, Vec<f64>, Vec<f64>)> {
let mut s_list = Vec::new();
let mut g_list = Vec::new();
let mut subs: Vec<String> = Vec::new();
let re_coeff = Regex::new(r"(\d+(\.\d*)?)\*?").map_err(|e| {
KineticsError::InvalidReactionData(format!("invalid coefficient regex: {e}"))
})?;
let re_power = Regex::new(r"\^(\d+(\.\d*)?)")
.map_err(|e| KineticsError::InvalidReactionData(format!("invalid power regex: {e}")))?;
for s in half_reaction.split('+') {
let mut s = s.trim();
let mut stec_coeff = 1.0;
let _end_of_stoichiometric: usize = 0;
if let Some(captures) = re_coeff.captures(s) {
let regmatch = captures.get(0).ok_or_else(|| {
KineticsError::InvalidReactionData(format!(
"invalid stoichiometric prefix in `{}`",
half_reaction
))
})?;
let start_of_stoichiometric = regmatch.start();
let end_of_stoichiometric = regmatch.end();
if start_of_stoichiometric == 0 {
s = &s[end_of_stoichiometric..];
stec_coeff = captures
.get(1)
.ok_or_else(|| {
KineticsError::InvalidReactionData(format!(
"missing stoichiometric coefficient in `{}`",
half_reaction
))
})?
.as_str()
.parse()
.map_err(|_| {
KineticsError::InvalidReactionData(format!(
"invalid stoichiometric coefficient in `{}`",
half_reaction
))
})?;
}
}
let mut power_coeff = stec_coeff;
if let Some(captures) = re_power.captures(s) {
let regmatch = captures.get(0).ok_or_else(|| {
KineticsError::InvalidReactionData(format!(
"invalid kinetic power in `{}`",
half_reaction
))
})?;
power_coeff = captures
.get(1)
.ok_or_else(|| {
KineticsError::InvalidReactionData(format!(
"missing kinetic power in `{}`",
half_reaction
))
})?
.as_str()
.parse()
.map_err(|_| {
KineticsError::InvalidReactionData(format!(
"invalid kinetic power in `{}`",
half_reaction
))
})?;
let start = regmatch.start();
#[allow(unused_variables)]
let end = regmatch.end();
s = &s[..start];
}
s = s.trim();
if (s == "M") || (s == "(M)") {
continue;
} let s_to_filter: &mut String = &mut s.to_string();
let s_filtered = clean_off_artifacts(s_to_filter);
let s: String = s_filtered.to_string();
s_list.push(stec_coeff);
g_list.push(power_coeff);
subs.push(s);
}
Ok((subs, s_list, g_list))
}
pub fn clean_off_DUP(item: &mut String) -> &String {
if item.ends_with("_dup") || item.ends_with("_DUP") {
*item = item.replace("_dup", "").replace("_DUP", "");
return item;
} else {
item
}
}
#[derive(Debug, Clone)]
pub struct StoichAnalyzer {
pub reactions: Vec<String>, pub groups: Option<HashMap<String, HashMap<String, usize>>>, pub substances: Vec<String>, pub stecheo_matrx: Vec<Vec<f64>>, pub stecheo_reags: Vec<Vec<f64>>, pub stecheo_prods: Vec<Vec<f64>>, pub G_matrix_reag: Vec<Vec<f64>>, pub G_matrix_prod: Vec<Vec<f64>>, pub matrix_of_elements: Option<DMatrix<f64>>, pub vec_of_molmasses: Option<Vec<f64>>, pub unique_vec_of_elems: Option<Vec<String>>,
}
impl StoichAnalyzer {
pub fn new() -> Self {
StoichAnalyzer {
reactions: Vec::new(),
groups: None,
substances: Vec::new(),
stecheo_matrx: Vec::new(),
stecheo_reags: Vec::new(),
stecheo_prods: Vec::new(),
G_matrix_reag: Vec::new(),
G_matrix_prod: Vec::new(),
matrix_of_elements: None,
vec_of_molmasses: None,
unique_vec_of_elems: None,
}
}
pub fn analyse_reactions(&mut self) -> KineticsResult<()> {
let reactions_trimmed = self
.reactions
.iter()
.map(|s| s.replace("**", "^").trim().to_string())
.collect();
self.reactions = reactions_trimmed;
self.stecheo_matrx
.resize(self.reactions.len(), vec![0.0; self.substances.len()]);
self.stecheo_reags
.resize(self.reactions.len(), vec![0.0; self.substances.len()]);
self.stecheo_prods
.resize(self.reactions.len(), vec![0.0; self.substances.len()]);
self.G_matrix_reag
.resize(self.reactions.len(), vec![0.0; self.substances.len()]);
self.G_matrix_prod
.resize(self.reactions.len(), vec![0.0; self.substances.len()]);
for mut reaction in &self.reactions {
if let Some(i) = self.reactions.iter().position(|s| s == reaction) {
let reaction_: &mut String = &mut reaction.to_string();
reaction = clean_off_DUP(reaction_);
let re = Regex::new(r"<= >|< =>|<=>|=>|->|=").map_err(|e| {
KineticsError::InvalidReactionData(format!("invalid reaction split regex: {e}"))
})?;
let sides: Vec<String> = re .split(reaction) .map(|s| s.trim())
.map(|s| s.to_string())
.collect();
let mut subs = Vec::new();
let mut subs_r = Vec::new();
let mut s_list = Vec::new();
let mut g_list = Vec::new();
let mut s_list_r = Vec::new();
let mut g_list_r: Vec<f64> = Vec::new();
let (left_subs, mut left_s_list, mut left_g_list) = analyse_substances(&sides[0])?;
let mut left_subs = left_subs.iter().map(|s| s.as_str()).collect();
subs.append(&mut left_subs);
s_list.append(&mut left_s_list);
g_list.append(&mut left_g_list);
for j in 0..subs.len() {
let subs_j = subs[j];
if let Some(k) = self.substances.iter().position(|s| s == subs_j) {
self.stecheo_matrx[i][k] -= s_list[j];
self.stecheo_reags[i][k] = s_list[j];
self.G_matrix_reag[i][k] = g_list[j];
}
}
let (right_subs, mut right_s_list, mut right_g_list) =
analyse_substances(&sides[1])?;
let mut right_subs = right_subs.iter().map(|s| s.as_str()).collect();
subs_r.append(&mut right_subs);
s_list_r.append(&mut right_s_list);
g_list_r.append(&mut right_g_list);
for j in 0..subs_r.len() {
let subs_j = subs_r[j];
if let Some(k) = self.substances.iter().position(|s| s == subs_j) {
self.stecheo_matrx[i][k] += s_list_r[j];
self.stecheo_prods[i][k] = s_list_r[j];
self.G_matrix_prod[i][k] = g_list_r[j];
}
}
}
} Ok(())
}
pub fn search_substances(&mut self) -> KineticsResult<()> {
let reactions_trimmed = self
.reactions
.iter()
.map(|s| s.replace("**", "^").trim().to_string())
.collect();
self.reactions = reactions_trimmed;
let mut found_substances: Vec<String> = Vec::new();
for mut reaction in &self.reactions {
if let Some(_i) = self.reactions.iter().position(|s| s == reaction) {
let reaction_: &mut String = &mut reaction.to_string();
reaction = clean_off_DUP(reaction_);
let re = Regex::new(r"<= >|< =>|<=>|=>|->|=").map_err(|e| {
KineticsError::InvalidReactionData(format!("invalid reaction split regex: {e}"))
})?;
let sides: Vec<String> = re
.split(reaction) .map(|s| s.trim())
.map(|s| s.to_string())
.collect();
let mut subs = Vec::new();
let (left_subs, _, _) = analyse_substances(&sides[0])?;
let mut left_subs = left_subs.iter().map(|s| s.as_str()).collect();
subs.append(&mut left_subs);
let (right_subs, _, _) = analyse_substances(&sides[1])?;
let mut right_subs = right_subs.iter().map(|s| s.as_str()).collect();
subs.append(&mut right_subs);
let subs_mut = &mut subs.clone();
subs_mut.retain(|s| !found_substances.contains(&s.to_string())); found_substances.extend(subs_mut.iter().map(|s| s.to_string()).collect::<Vec<_>>());
} } self.substances = found_substances;
Ok(())
}
pub fn create_matrix_of_elements(&mut self) -> KineticsResult<()> {
if self.vec_of_molmasses.is_none() {
self.vec_of_molmasses = Some(calculate_molar_mass_of_vector_of_subs(
self.substances.iter().map(|s| s.as_str()).collect(),
self.groups.clone(),
)?);
}
let (matrix, vec_of_elems) = create_elem_composition_matrix(
self.substances.iter().map(|s| s.as_str()).collect(),
self.groups.clone(),
)?;
self.matrix_of_elements = Some(matrix);
self.unique_vec_of_elems = Some(vec_of_elems);
Ok(())
}
} #[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_analyse_substances() {
let half_reaction = "5H2O + 10O2";
let (subs, s_list, g_list) = analyse_substances(half_reaction).unwrap();
assert_eq!(subs, vec!["H2O".to_string(), "O2".to_string()]);
assert_eq!(s_list, vec![5.0, 10.0]);
assert_eq!(g_list, vec![5.0, 10.0]);
}
#[test]
fn test_analyse_substances_prefers_explicit_powers() {
let half_reaction = "2H2O^3 + 4O2";
let (subs, s_list, g_list) = analyse_substances(half_reaction).unwrap();
assert_eq!(subs, vec!["H2O".to_string(), "O2".to_string()]);
assert_eq!(s_list, vec![2.0, 4.0]);
assert_eq!(g_list, vec![3.0, 4.0]);
}
#[test]
fn test_analyse_substances_with_exotic_names() {
let mut ReactionAnalyzer_instance = StoichAnalyzer::new();
let reactions_: Vec<&str> = vec!["HMX=>7HMXprod"];
let reaction = reactions_.iter().map(|s| s.to_string()).collect();
ReactionAnalyzer_instance.reactions = reaction;
let substancses_ = vec!["HMX", "HMXprod"];
let substancses = substancses_.iter().map(|s| s.to_string()).collect();
ReactionAnalyzer_instance.substances = substancses;
ReactionAnalyzer_instance.analyse_reactions().unwrap();
let stecheo_matrx = ReactionAnalyzer_instance.stecheo_matrx;
let result = [[-1.0, 7.0]];
let result: Vec<Vec<f64>> = result.iter().map(|row| row.to_vec()).collect();
assert_eq!(stecheo_matrx, result);
}
#[test]
fn test_clean_off_artifacts_and_DUP() {
let mut item = "A=2BM)".to_string();
let cleaned_item = clean_off_artifacts(&mut item);
assert_eq!(cleaned_item, "A=2B");
let mut item = "B->A + 3C_DUP".to_string();
let cleaned_item = clean_off_DUP(&mut item);
assert_eq!(cleaned_item, "B->A + 3C");
}
#[test]
fn test_search_substances() {
let mut ReactionAnalyzer_instance = StoichAnalyzer::new();
let reactions_: Vec<&str> = vec!["A=2BM)", "B->A + 3C_DUP"];
let reaction = reactions_.iter().map(|s| s.to_string()).collect();
ReactionAnalyzer_instance.reactions = reaction;
ReactionAnalyzer_instance.search_substances().unwrap();
assert_eq!(
ReactionAnalyzer_instance.substances,
vec!["A".to_string(), "B".to_string(), "C".to_string()]
);
}
#[test]
fn test_reaction_analyzer() {
let mut ReactionAnalyzer_instance = StoichAnalyzer::new();
let reactions_: Vec<&str> = vec!["A=2BM)", "B=>A + 3C_DUP", "2B+A=D**0.5"];
let reaction = reactions_.iter().map(|s| s.to_string()).collect();
ReactionAnalyzer_instance.reactions = reaction;
let substancses_ = vec!["A", "B", "C", "D"];
let substancses = substancses_.iter().map(|s| s.to_string()).collect();
ReactionAnalyzer_instance.substances = substancses;
ReactionAnalyzer_instance.analyse_reactions().unwrap();
let stecheo_matrx = ReactionAnalyzer_instance.stecheo_matrx;
let result = [
[-1.0, 2.0, 0.0, 0.0],
[1.0, -1.0, 3.0, 0.0],
[-1.0, -2.0, 0.0, 1.0],
];
let result: Vec<Vec<f64>> = result.iter().map(|row| row.to_vec()).collect();
assert_eq!(stecheo_matrx, result);
}
}