use crate::Kinetics::kinetics_lib_api::KineticData;
use crate::Kinetics::mechfinder_api::{ parse_kinetic_data_vec, ReactionData, Mechanism_search, ReactionKinetics};
use crate::Kinetics::parsetask::{decipher_vector_of_shortcuts, decipher_vector_of_shortcuts_to_pairs};
use crate::Kinetics::stoichiometry_analyzer::StoichAnalyzer;
use std::fs::File;
use std::io::Write;
use serde_json::json;
use serde_json::Value;
use std::collections::HashMap;
use prettytable::{Table, Row, Cell, row};
use log::{ info, warn};
#[derive(Debug, Clone)]
pub struct KinData {
pub shortcut_reactions: Option<Vec<String>>, pub map_of_reactions: Option<HashMap<String, Vec<String>>>, pub vec_of_pairs: Option<Vec<(String, String)>>, pub vec_of_reaction_Values: Option<Vec<Value>>, pub vec_of_reaction_data: Option<Vec<ReactionData>>, pub vec_of_equations: Vec<String>, pub substances: Vec<String>, pub groups: Option<HashMap<String, HashMap<String, usize>>>, pub stecheodata: StoichAnalyzer, }
impl KinData {
pub fn new() -> Self {
Self {
shortcut_reactions: None,
map_of_reactions: None,
vec_of_pairs: None,
vec_of_reaction_Values: None,
vec_of_reaction_data: None,
vec_of_equations: Vec::new(),
substances: Vec::new(),
groups: None,
stecheodata: StoichAnalyzer::new(),
}
}
pub fn set_reactions_directly(
&mut self,
reactions: Vec<String>,
groups: Option<HashMap<String, HashMap<String, usize>>>,
) -> () {
self.shortcut_reactions = Some(reactions);
self.groups = groups;
}
pub fn set_reactions_from_shortcut_range(&mut self, shortcut_range: String) -> Vec<String> {
let parts: Vec<&str> = shortcut_range.split("..").collect();
if parts.len() != 2 {
panic!("Invalid shortcut range: {}", shortcut_range);
}
let prefix = parts[0]
.chars()
.take_while(|c| c.is_alphabetic())
.collect::<String>();
let start: usize = parts[0][prefix.len()..].parse().unwrap_or(0); let end: usize = if let Some(last_char) = parts[1].chars().last() { if last_char.is_numeric() { parts[1].chars().rev() .take_while(|c| c.is_numeric()) .collect::<String>() .chars().rev().collect::<String>() .parse().unwrap_or(0) } else {
panic!("last symbol must be numeric: {}", shortcut_range);
}
} else {
panic!("last symbol must be numeric: {}", shortcut_range);
};
let res:Vec<String> = (start..=end).map(|i| format!("{}_{}", prefix, i)).collect(); info!("task includes reactions as follows: {:#?}", &res);
self.shortcut_reactions = Some(res.clone());
return res;
}
pub fn get_reactions_from_shortcuts(&mut self) -> () {
if let Some(shortcut_reactions) = &self.shortcut_reactions {
let vec: Vec<&str> = shortcut_reactions.iter().map(|s| s.as_str()).collect();
self.map_of_reactions = Some(decipher_vector_of_shortcuts(vec.clone()));
let vec_of_pairs = decipher_vector_of_shortcuts_to_pairs(vec);
self.vec_of_pairs = Some(vec_of_pairs.clone());
let mut vec_of_reaction_values = Vec::new();
let mut kin_instance = KineticData::new();
for (lib, reaction_id) in vec_of_pairs.iter() {
kin_instance.open_json_files(lib);
let reaction_data_Value =
kin_instance.search_reactdata_by_reaction_id(&reaction_id);
vec_of_reaction_values.push(reaction_data_Value);
}
self.vec_of_reaction_Values = Some(vec_of_reaction_values);
} else {
warn!("KinData::create_map_of_reactions: shortcut_reactions is None");
}
}
pub fn construct_mechanism(&mut self, task_substances: Vec<String>, task_library: String,) {
let mut found_mech = Mechanism_search::new( task_substances,task_library.clone());
found_mech.mechfinder_api();
self.vec_of_equations = found_mech.vec_of_reactions;
self.vec_of_reaction_data = Some(found_mech.reactdata);
let reactions = found_mech.mechanism;
let mut full_addres = Vec::new();
for reaction in reactions.iter() {
let addres = format!("{}_{}", task_library, reaction);
full_addres.push(addres);
}
self.shortcut_reactions = Some(full_addres);
self.substances = found_mech.reactants;
}
pub fn append_reaction(&mut self, reactions:Vec<Value>) {
let mut old_reactions = self.vec_of_reaction_Values.as_mut().unwrap().clone();
old_reactions.extend(reactions);
self.vec_of_reaction_Values = Some(old_reactions);
}
pub fn remove_by_index(&mut self, index: usize) {
let mut vec_of_equations = self.vec_of_equations.clone();
vec_of_equations.remove(index);
self.vec_of_equations = vec_of_equations;
if let Some(vec_of_reaction_Values) = self.vec_of_reaction_Values.clone().as_mut() {
vec_of_reaction_Values.remove(index);
self.vec_of_reaction_Values = Some(vec_of_reaction_Values.clone());
}
if let Some(vec_of_reaction_data) = self.vec_of_reaction_data.clone().as_mut() {
vec_of_reaction_data.remove(index);
self.vec_of_reaction_data = Some(vec_of_reaction_data.clone());
}
if let Some(vec_of_pairs) = self.vec_of_pairs.clone().as_mut() {
vec_of_pairs.remove(index);
self.vec_of_pairs = Some(vec_of_pairs.clone());
}
}
pub fn remove_reaction_by_name(&mut self, reaction_name: &str) {
let i= &self.vec_of_equations.clone().iter().position(|eq_i| *eq_i == reaction_name);
if let Some(index) = i {
self.remove_by_index(*index);
}
}
pub fn reactdata_parsing(&mut self) -> () {
if let Some(vec_of_reaction_values ) = & self.vec_of_reaction_Values {
let (vec_ReactionData, vec_of_equations) =
parse_kinetic_data_vec( vec_of_reaction_values.clone());
self.vec_of_reaction_data = Some(vec_ReactionData);
self.vec_of_equations = vec_of_equations;
} else {
warn!("KinData::reactdata_from_shortcuts: map_of_reactions is None");
}
}
pub fn analyze_reactions(&mut self) -> () {
let mut StoichAnalyzer_instance = StoichAnalyzer::new();
StoichAnalyzer_instance.reactions = self.vec_of_equations.clone();
StoichAnalyzer_instance.groups = self.groups.clone();
if self.substances.is_empty()| (self.substances.len() == 0) { StoichAnalyzer_instance.search_substances();
self.substances = StoichAnalyzer_instance.substances.clone();
} else {StoichAnalyzer_instance.substances = self.substances.clone()};
StoichAnalyzer_instance.analyse_reactions();
StoichAnalyzer_instance.create_matrix_of_elements();
self.stecheodata = StoichAnalyzer_instance;
}
pub fn kinetic_main(&mut self) {
self.reactdata_parsing();
self.analyze_reactions();
}
pub fn print_raw_reactions(&self) -> Result<(), std::io::Error> {
if let Some(vec_of_reaction_Values) = & self.vec_of_reaction_Values {
let json_array = json!(vec_of_reaction_Values);
let mut file = File::create("raw_reactions.json")?;
file.write_all(serde_json::to_string_pretty(&json_array)?.as_bytes())?;
info!("Raw reactions have been written to raw_reactions.json");
Ok(())
} else {
warn!("KinData::reactdata_from_shortcuts: map_of_reactions is None");
Ok(())
}
}
pub fn pretty_print_substances_verbose(&self) -> Result<(), std::io::Error> {
let subs = &self.substances;
let reacts = &self.vec_of_equations;
let sd: &StoichAnalyzer = &self.stecheodata.clone();
let elem_matrix = sd.matrix_of_elements.clone().unwrap().clone();
let unique_vec_of_elems = sd.unique_vec_of_elems.clone().unwrap();
let sm = sd.stecheo_matrx.clone();
let n_react_from_matrix = sm.len();
assert_eq!(n_react_from_matrix, reacts.len());
let n_subs_from_matrix = sm[0].len();
assert_eq!(n_subs_from_matrix, subs.len());
let (nrows, ncols) = elem_matrix.shape();
assert_eq!(nrows, subs.len());
assert_eq!(ncols, unique_vec_of_elems.len());
let mut table_stecheo = Table::new();
let mut header_row = vec![Cell::new("Reactions/Substances")];
for sub in subs {
header_row.push(Cell::new(sub));
}
table_stecheo.add_row(Row::new(header_row));
for (i, react) in reacts.iter().enumerate() {
let mut row = vec![Cell::new(react)];
for j in 0..subs.len() {
row.push(Cell::new(&format!("{:.4}", sm[i][j])));
}
table_stecheo.add_row(Row::new(row));
}
table_stecheo.printstd();
let mut elem_table = Table::new();
let mut header_row = vec![Cell::new("Substances/Elements")];
for elems in unique_vec_of_elems.clone() {
header_row.push(Cell::new(&elems));
}
elem_table.add_row(Row::new(header_row));
for (i, sub) in subs.iter().enumerate() {
let mut row = vec![Cell::new(sub)];
for j in 0..unique_vec_of_elems.len() {
row.push(Cell::new(&format!("{:.4}", elem_matrix.get((i, j)).unwrap() )));
}
elem_table.add_row(Row::new(row));
}
elem_table.printstd();
Ok(())
}
pub fn pretty_print_reaction_data(&self) -> Result<(), std::io::Error> {
if let Some(vec_of_reaction_data) = &self.vec_of_reaction_data {
let mut table = Table::new();
table.add_row(row![
"Reaction number",
"Reaction Type",
"Equation",
"Reactants",
"Kinetics Data"
]);
for (i, reaction_data) in vec_of_reaction_data.iter().enumerate() {
let reaction_type = format!("{:?}", reaction_data.reaction_type);
let equation = &reaction_data.eq;
let reactants = match &reaction_data.react {
Some(react) => format!("{:?}", react),
None => "None".to_string(),
};
let kinetics_data = match &reaction_data.data {
ReactionKinetics::Elementary(data) => format!("{:?}", data),
ReactionKinetics::Falloff(data) => format!("{:?}", data),
ReactionKinetics::Pressure(data) => format!("{:?}", data),
ReactionKinetics::ThreeBody(data) => format!("{:?}", data),
};
table.add_row(Row::new(vec![
Cell::new(i.to_string().as_str()),
Cell::new(&reaction_type),
Cell::new(equation),
Cell::new(&reactants),
Cell::new(&kinetics_data),
]));
}
table.printstd();
} else {
warn!("No reaction data available.");
}
Ok(())
}
pub fn pretty_print_kindata(&self) {
let _ = self.pretty_print_reaction_data();
let _ = self.pretty_print_substances_verbose();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_user_reactions_new() {
let shortcut_reactions = Some(vec![
"C_1".to_string(),
"C_2".to_string(),
"C_3".to_string(),
]);
let mut user_reactions = KinData::new();
user_reactions.shortcut_reactions = shortcut_reactions.clone();
assert_eq!(
user_reactions.shortcut_reactions,
Some(vec![
"C_1".to_string(),
"C_2".to_string(),
"C_3".to_string()
])
);
let mut map_of_reactions = HashMap::new();
map_of_reactions.insert(
"Cantera".to_string(),
vec!["1".to_string(), "2".to_string(), "3".to_string()]
.into_iter()
.collect(),
);
user_reactions.get_reactions_from_shortcuts();
println!(
"map_of_reactions: {:?} \n \n ",
&user_reactions.map_of_reactions
);
assert_eq!(user_reactions.map_of_reactions, Some(map_of_reactions));
user_reactions.reactdata_parsing();
println!(
"map_of_reactions data: {:?} \n \n ",
&user_reactions.clone().vec_of_reaction_data
);
assert_eq!(
user_reactions
.clone()
.vec_of_reaction_data
.unwrap()
.is_empty(),
false
);
assert_eq!(user_reactions.vec_of_equations.is_empty(), false);
user_reactions.analyze_reactions();
let subs = user_reactions.substances;
assert_eq!(subs.is_empty(), false);
let S = user_reactions.stecheodata.stecheo_matrx;
let nunber_of_reactions = S.len();
let number_of_substances = S[0].len();
assert_eq!(S.is_empty(), false);
assert_eq!(nunber_of_reactions, shortcut_reactions.unwrap().len());
assert_eq!(number_of_substances, subs.len());
}
#[test]
fn test_generate_strings_with_single_character_prefix() {
let mut user_reactions = KinData::new();
let result = user_reactions.set_reactions_from_shortcut_range("A1..5".to_string());
let expected = vec!["A_1", "A_2", "A_3", "A_4", "A_5"];
assert_eq!(result, expected);
}
#[test]
fn test_generate_strings_with_multi_character_prefix() {
let mut user_reactions = KinData::new();
let result = user_reactions.set_reactions_from_shortcut_range("Cat5..8".to_string());
let expected = vec!["Cat_5", "Cat_6", "Cat_7", "Cat_8"];
assert_eq!(result, expected);
}
#[test]
fn test_generate_strings_with_large_numbers() {
let mut user_reactions = KinData::new();
let result = user_reactions.set_reactions_from_shortcut_range("Meow20..25".to_string());
let expected = vec!["Meow_20", "Meow_21", "Meow_22", "Meow_23", "Meow_24", "Meow_25"];
assert_eq!(result, expected);
}
}