#![allow(warnings)]
pub mod kinetics;
mod mechfinder;
use crate::Kinetics::error::{KineticsError, KineticsResult};
use RustedSciThe::symbolic::symbolic_engine::Expr;
use kinetics::{ElementaryStruct, FalloffStruct, PressureStruct, ThreeBodyStruct};
use log::{error, info, warn};
use serde::de::{self, Deserializer, MapAccess, Visitor};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Number, Value};
use std::collections::{HashMap, HashSet};
use std::f64;
use std::fmt;
#[derive(Debug, PartialEq, Serialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum ReactionType {
Elem,
Falloff,
#[serde(rename = "pres")]
Pressure,
#[serde(rename = "three-body")]
ThreeBody,
Empirical,
}
impl<'de> Deserialize<'de> for ReactionType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.as_str() {
"elem" => Ok(ReactionType::Elem),
"falloff" => Ok(ReactionType::Falloff),
"pressure" | "pres" => Ok(ReactionType::Pressure),
"three-body" | "threebody" => Ok(ReactionType::ThreeBody),
"empirical" => Ok(ReactionType::Empirical),
_ => Err(serde::de::Error::custom(format!(
"Unknown reaction type: {}",
s
))),
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ReactionData {
#[serde(rename = "type")]
pub reaction_type: ReactionType,
pub eq: String,
pub react: Option<HashMap<String, f64>>,
#[serde(flatten)]
pub data: ReactionKinetics,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum ReactionKinetics {
Falloff(FalloffStruct),
Pressure(PressureStruct),
ThreeBody(ThreeBodyStruct),
Elementary(ElementaryStruct),
}
impl ReactionData {
fn missing_concentration_error(reaction_kind: &str) -> KineticsError {
KineticsError::InvalidReactionData(format!(
"{} reaction requires concentration data",
reaction_kind
))
}
fn missing_pressure_error(reaction_kind: &str) -> KineticsError {
KineticsError::InvalidReactionData(format!(
"{} reaction requires pressure data",
reaction_kind
))
}
pub fn new_elementary(
eq: String,
arrenius: Vec<f64>,
react: Option<HashMap<String, f64>>,
) -> Self {
Self {
reaction_type: ReactionType::Elem,
eq,
react: react,
data: ReactionKinetics::Elementary(ElementaryStruct::new(arrenius)),
}
}
pub fn new_falloff(
eq: String,
low_rate: Vec<f64>,
high_rate: Vec<f64>,
eff: Option<HashMap<String, f64>>,
troe: Option<Vec<f64>>,
react: Option<HashMap<String, f64>>,
) -> Self {
Self {
reaction_type: ReactionType::Falloff,
eq,
react: react,
data: ReactionKinetics::Falloff(FalloffStruct::new(low_rate, high_rate, eff, troe)),
}
}
pub fn new_three_body(
eq: String,
arrenius: Vec<f64>,
eff: HashMap<String, f64>,
react: Option<HashMap<String, f64>>,
) -> Self {
Self {
reaction_type: ReactionType::ThreeBody,
eq,
react: react,
data: ReactionKinetics::ThreeBody(ThreeBodyStruct::new(arrenius, eff)),
}
}
pub fn new_pressure(
eq: String,
arrenius: HashMap<String, Vec<f64>>,
react: Option<HashMap<String, f64>>,
) -> Self {
Self {
reaction_type: ReactionType::Pressure,
eq,
react: react,
data: ReactionKinetics::Pressure(PressureStruct::new(arrenius)),
}
}
pub fn validate_reaction_type(&self) -> KineticsResult<()> {
match (&self.reaction_type, &self.data) {
(ReactionType::Elem, ReactionKinetics::Elementary(_)) => Ok(()),
(ReactionType::Falloff, ReactionKinetics::Falloff(_)) => Ok(()),
(ReactionType::Pressure, ReactionKinetics::Pressure(_)) => Ok(()),
(ReactionType::ThreeBody, ReactionKinetics::ThreeBody(_)) => Ok(()),
_ => Err(KineticsError::InvalidReactionData(format!(
"Mismatch between reaction_type ({:?}) and data variant ({:?})",
self.reaction_type,
std::mem::discriminant(&self.data)
))),
}
}
pub fn K_const(
&self,
temp: f64,
pres: Option<f64>,
concentrations: Option<HashMap<String, f64>>,
) -> KineticsResult<f64> {
match self.data.clone() {
ReactionKinetics::Elementary(data) => Ok(data.K_const(temp)),
ReactionKinetics::Falloff(data) => Ok(data.K_const(
temp,
concentrations.ok_or_else(|| Self::missing_concentration_error("falloff"))?,
)),
ReactionKinetics::Pressure(data) => Ok(data.K_const(
temp,
pres.ok_or_else(|| Self::missing_pressure_error("pressure"))?,
)),
ReactionKinetics::ThreeBody(data) => Ok(data.K_const(
temp,
concentrations.ok_or_else(|| Self::missing_concentration_error("three-body"))?,
)),
}
}
pub fn K_const_for_T_range(
&self,
T0: f64,
Tend: f64,
n: usize,
pres: Option<f64>,
concentrations: Option<HashMap<String, f64>>,
) -> KineticsResult<Vec<f64>> {
let mut K_const_values = Vec::new();
let T = Self::create_uniform_vector(T0, Tend, n)?;
for Ti in T {
let k = self.K_const(Ti, pres, concentrations.clone())?;
K_const_values.push(k);
}
Ok(K_const_values)
}
pub fn create_uniform_vector(T0: f64, Tend: f64, n: usize) -> KineticsResult<Vec<f64>> {
if n < 2 {
return Err(KineticsError::InvalidReactionData(
"n must be at least 2 to include both boundary values".to_string(),
));
}
let step = (Tend - T0) / (n - 1) as f64;
Ok((0..n).map(|i| T0 + i as f64 * step).collect())
}
pub fn K_sym(
&self,
pres: Option<f64>,
concentrations: Option<HashMap<String, Expr>>,
) -> KineticsResult<Expr> {
match self.data.clone() {
ReactionKinetics::Elementary(data) => Ok(data.K_expr()),
ReactionKinetics::Falloff(data) => Ok(data.K_expr(
concentrations.ok_or_else(|| Self::missing_concentration_error("falloff"))?,
)),
ReactionKinetics::Pressure(data) => {
Ok(data.K_expr(pres.ok_or_else(|| Self::missing_pressure_error("pressure"))?))
}
ReactionKinetics::ThreeBody(data) => Ok(data.K_expr(
concentrations.ok_or_else(|| Self::missing_concentration_error("three-body"))?,
)),
}
}
pub fn K_sym_with_scaled_T(
&self,
pres: Option<f64>,
concentrations: Option<HashMap<String, Expr>>,
scaling_T: Option<Expr>,
) -> KineticsResult<Expr> {
let mut K_sym = self.K_sym(pres, concentrations)?;
if let Some(scaling_T) = scaling_T {
K_sym = K_sym.substitute_variable("T", &scaling_T);
}
Ok(K_sym)
}
pub fn to_serde_value(&self) -> KineticsResult<Value> {
Ok(serde_json::to_value(self)?)
}
}
pub fn parse_kinetic_data(
big_mech: &str,
vec_of_reactions: &[String],
vec_of_reaction_value: Vec<Value>,
) -> KineticsResult<(Map<String, Value>, Vec<String>)> {
let mut reaction_data_hash = Map::new();
let mut equations = Vec::new();
info!("______________PARCING REACTION DATA INTO STRUCTS________");
for (reaction_record, reaction_id) in vec_of_reaction_value.iter().zip(vec_of_reactions) {
let react_code = format!("{}_{}", big_mech, reaction_id);
if let Ok(mut reactiondata) =
serde_json::from_value::<ReactionData>(reaction_record.clone())
{
equations.push(reactiondata.eq.clone());
reactiondata.validate_reaction_type()?; let value = serde_json::to_value(&reactiondata)?;
reaction_data_hash.insert(react_code, value);
} else {
error!("Error parsing reaction: {}", reaction_record);
return Err(KineticsError::InvalidReactionData(format!(
"Error parsing reaction: {}",
reaction_record
)));
}
}
info!("______________PARCING REACTION DATA INTO STRUCTS ENDED________");
Ok((reaction_data_hash, equations))
}
pub fn parse_kinetic_data_vec(
vec_of_reaction_value: Vec<Value>,
) -> KineticsResult<(Vec<ReactionData>, Vec<String>)> {
let mut reaction_dat = Vec::new();
let mut equations = Vec::new();
for reaction_record in vec_of_reaction_value.into_iter() {
match serde_json::from_value::<ReactionData>(reaction_record) {
Ok(mut reactiondata) => {
equations.push(reactiondata.eq.clone());
reactiondata.validate_reaction_type()?; reaction_dat.push(reactiondata);
}
Err(_) => {
return Err(KineticsError::InvalidReactionData(
"Error parsing reaction".to_string(),
));
}
}
}
Ok((reaction_dat, equations))
}
#[derive(Debug)]
pub struct Mechanism_search {
pub task_substances: Vec<String>,
pub task_library: String,
pub mechanism: Vec<String>,
pub reactants: Vec<String>,
pub vec_of_reactions: Vec<String>,
pub reactdata: Vec<ReactionData>,
}
impl Mechanism_search {
pub fn new(task_substances: Vec<String>, task_library: String) -> Self {
Self {
task_substances: task_substances,
task_library: task_library,
mechanism: Vec::new(),
reactants: Vec::new(),
vec_of_reactions: Vec::new(),
reactdata: Vec::new(),
}
}
pub fn default() -> Self {
Self {
task_substances: Vec::new(),
task_library: String::new(),
mechanism: Vec::new(),
reactants: Vec::new(),
vec_of_reactions: Vec::new(),
reactdata: Vec::new(),
}
}
pub fn mechfinder_api(&mut self) -> KineticsResult<(Vec<String>, Vec<String>, Vec<String>)> {
let vec: Vec<&str> = self.task_substances.iter().map(|s| s.as_str()).collect();
let big_mech = self.task_library.clone();
info!(
"задание {:?}, библиотека {:?}",
&big_mech, &vec
);
let (mechanism, reactants, vec_of_reactions, vec_of_reaction_value) =
mechfinder::mechfinder(&big_mech, vec)?;
let (reactdata, vec_of_equations) = parse_kinetic_data_vec(vec_of_reaction_value)?;
self.mechanism = mechanism;
self.reactants = reactants;
self.vec_of_reactions = vec_of_reactions;
self.reactdata = reactdata;
Ok((
self.mechanism.to_owned(),
self.reactants.to_owned(),
self.vec_of_reactions.to_owned(),
))
}
}
const ELEM_TESTING_JSON: &str = r#"{"type": "elem",
"eq": "NAPH+C2H3<=>NAPHV+C2H4",
"Arrenius": [0.408, 4.02, 36822.949]}"#;
const FALOFF_TESTING_JSON: &str = r#" {"type": "falloff",
"eq": "C4H71-3+CH3(+M)<=>C5H10-2(+M)",
"low_rate": [3.91e+60, -12.81, 26143.75],
"high_rate": [100000000000000.0, -0.32, -1097.2009],
"eff": {"H2": 2.0, "H2O": 6.0, "CH4": 2.0, "CO": 1.5, "CO2": 2.0, "C2H6": 3.0, "AR": 0.7},
"troe": [0.104, 1606.0, 60000.0, 6118.0]} "#;
const PRES_TESTING_JSON: &str = r#"{"type": "pres", "eq": "SC4H9<=>C3H6+CH3",
'Arrenius': {"0.001": [2.89e+40, -9.76, 140552.983],
"0.01": [1.8e+44, -10.5, 154800.281],
"0.1": [2.51e+46, -10.73, 168311.37099999998],
"1.0": [4.74e+44, -9.85, 175020.903],
"10.0": [3.79e+37, -7.44, 169846.532],
"100.0": [4.79e+26, -4.01, 154344.334] }}"#;
const THREE_BODY_TESTING_JSON: &str = r#"{"type": "threebody",
"eq": "H2+M<=>H+H+M",
"Arrenius": [4.577e+19, -1.4, 436705.19999999995],
"eff": {"H2": 2.5, "H2O": 12.0, "CO": 1.9, "CO2": 3.8, "HE": 0.83, "CH4": 2.0, "C2H6": 3.0} }"#;
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_mechfinder_api() {
let mut mech_search = Mechanism_search::new(
vec!["O".to_string(), "NH3".to_string(), "NO".to_string()],
"NUIG".to_string(),
);
let mech_result = mech_search.mechfinder_api();
assert!(mech_result.is_ok());
let (mechanism, reactants, vec_of_reactions) = mech_result.unwrap();
assert!(!mechanism.is_empty());
assert!(!reactants.is_empty());
assert!(!vec_of_reactions.is_empty());
}
#[test]
fn test_default_values() {
let mech_search = Mechanism_search::default();
assert!(mech_search.task_substances.is_empty());
assert!(mech_search.task_library.is_empty());
assert!(mech_search.mechanism.is_empty());
assert!(mech_search.reactants.is_empty());
assert!(mech_search.vec_of_reactions.is_empty());
}
#[test]
fn test_ELEM_parse_kinetic_data() {
let big_mech: &str = "NUIG";
let vec_of_reactions = vec!["1".to_string()];
let reaction = ELEM_TESTING_JSON;
let vec_of_reaction_value: Vec<Value> = vec![serde_json::from_str(reaction).unwrap()];
let parsed = parse_kinetic_data(big_mech, &vec_of_reactions, vec_of_reaction_value);
assert!(parsed.is_ok());
let (ReactionDataHash, _) = parsed.unwrap();
assert!(!ReactionDataHash.is_empty());
let key = format!("{}_{}", big_mech, &vec_of_reactions[0]);
let elem_react_testing_instance: ElementaryStruct =
serde_json::from_value::<ElementaryStruct>(ReactionDataHash[&key].clone()).unwrap();
assert!(elem_react_testing_instance.K_const(298.15) > 0.0);
}
#[test]
fn test_THREEBODY_parse_kinetic_data() {
let ThreeBodyStruct_test_data: &str = r#"{"Arrenius": [4.577e+19, -1.4, 436705.19999999995],
"eff": {"H2": 2.5, "H2O": 12.0, "CO": 1.9, "CO2": 3.8, "HE": 0.83, "CH4": 2.0, "C2H6": 3.0} }"#;
let big_mech: &str = "NUIG";
let vec_of_reactions = vec!["2".to_string()];
let reaction = THREE_BODY_TESTING_JSON;
let vec_of_reaction_value: Vec<Value> = vec![serde_json::from_str(reaction).unwrap()];
let parsed = parse_kinetic_data(big_mech, &vec_of_reactions, vec_of_reaction_value);
assert!(parsed.is_ok());
let (ReactionDataHash, _) = parsed.unwrap();
assert!(!ReactionDataHash.is_empty());
println!("ReactionDataHash: {:?} \n \n", ReactionDataHash);
let key = format!("{}_{}", big_mech, &vec_of_reactions[0]);
let threebody_react_testing_instance: ThreeBodyStruct =
serde_json::from_value::<ThreeBodyStruct>(
serde_json::from_str(ThreeBodyStruct_test_data).unwrap(),
)
.unwrap();
println!(
"threebody_react_testing_instance {:?}",
threebody_react_testing_instance
);
let mut Concentrations: HashMap<String, f64> = HashMap::new();
Concentrations.insert("H".to_string(), 0.5);
Concentrations.insert("O".to_string(), 0.5);
assert!(threebody_react_testing_instance.K_const(298.15, Concentrations) > 0.0);
}
#[test]
fn test_THREEBODY_from_lib() {
use crate::Kinetics::User_reactions::KinData;
let mut kinetics = KinData::new();
let C1_react = Some(vec!["C1".to_string()]);
kinetics.shortcut_reactions = C1_react.clone();
kinetics.get_reactions_from_shortcuts();
kinetics.reactdata_parsing();
assert!(kinetics.vec_of_reaction_data.iter().len() > 0);
}
#[test]
fn test_FALOFF_parse_kinetic_data() {
let big_mech: &str = "NUIG";
let vec_of_reactions = vec!["3".to_string()];
let reaction = FALOFF_TESTING_JSON;
let vec_of_reaction_value: Vec<Value> = vec![serde_json::from_str(reaction).unwrap()];
let parsed = parse_kinetic_data(big_mech, &vec_of_reactions, vec_of_reaction_value);
assert!(parsed.is_ok());
let (ReactionDataHash, _) = parsed.unwrap();
println!("ReactionDataHash: {:#?}", ReactionDataHash);
assert!(!ReactionDataHash.is_empty());
let key = format!("{}_{}", big_mech, &vec_of_reactions[0]);
let falloff_react_testing_instance: FalloffStruct =
serde_json::from_value::<FalloffStruct>(ReactionDataHash[&key].clone()).unwrap();
let mut Concentrations: HashMap<String, f64> = HashMap::new();
Concentrations.insert("H".to_string(), 0.5);
Concentrations.insert("O".to_string(), 0.5);
assert!(falloff_react_testing_instance.K_const(298.15, Concentrations) > 0.0);
}
#[test]
fn test_three_body_deserialization() {
let three_body_json = json!({
"type": "three-body",
"eq": "H2+M<=>H+H+M",
"Arrenius": [4.577e+19, -1.4, 436705.19999999995],
"eff": {
"H2": 2.5,
"H2O": 12.0,
"CO": 1.9,
"CO2": 3.8,
"HE": 0.83,
"CH4": 2.0,
"C2H6": 3.0
}
});
let reaction_data: ReactionData = serde_json::from_value(three_body_json).unwrap();
println!("reaction_data: {:#?}", reaction_data);
assert_eq!(
reaction_data.reaction_type,
ReactionType::ThreeBody,
"wrong reaction type!"
);
if let ReactionKinetics::ThreeBody(_) = reaction_data.data {
} else {
panic!("Expected ThreeBody variant");
}
}
#[test]
fn test_parse_kinetic_data_vec_rejects_missing_equation() {
let payload = json!({
"type": "elem",
"Arrenius": [1.0, 0.0, 0.0]
});
let err = parse_kinetic_data_vec(vec![payload]).unwrap_err();
assert!(matches!(
err,
KineticsError::InvalidReactionData(message)
if message.contains("Error parsing reaction")
));
}
#[test]
fn test_reaction_data_deserialization_rejects_unknown_reaction_type() {
let payload = json!({
"type": "not-a-real-type",
"eq": "A=>B",
"Arrenius": [1.0, 0.0, 0.0]
});
let err = serde_json::from_value::<ReactionData>(payload).unwrap_err();
assert!(err.to_string().contains("Unknown reaction type"));
}
#[test]
fn test_parse_kinetic_data_vec_rejects_missing_rate_parameters() {
let payload = json!({
"type": "elem",
"eq": "A=>B"
});
let err = parse_kinetic_data_vec(vec![payload]).unwrap_err();
assert!(matches!(
err,
KineticsError::InvalidReactionData(message)
if message.contains("Error parsing reaction")
));
}
#[test]
fn test_pres_deserialization() {
let pres_json = json!({
"type": "pres",
"eq": "SC4H9<=>C3H6+CH3",
"Arrenius": {
"0.01": [2.89e+40, -9.76, 140552.983],
"0.1": [1.8e+44, -10.5, 154800.281]
}
});
let reaction_data: ReactionData = serde_json::from_value(pres_json).unwrap();
println!("reaction_data: {:#?}", reaction_data);
assert_eq!(
reaction_data.reaction_type,
ReactionType::Pressure,
"wrong reaction type!"
);
if let ReactionKinetics::Pressure(_) = reaction_data.data {
} else {
panic!("Expected Pressure variant");
}
}
#[test]
fn test_pres_data_deserialization() {
const PRES_TESTING_JSON: &str = r#"{
"Arrenius":{"0.01": [2.89e+40, -9.76, 140552.983],
"0.1": [1.8e+44, -10.5, 154800.281]
}}"#;
let pres_val =
serde_json::from_str(PRES_TESTING_JSON).expect("Error parsing JSON: {err:?}");
println!("val: {:#?}", pres_val);
let pres_data = serde_json::from_value::<PressureStruct>(pres_val);
if let Ok(pres) = pres_data {
println!("pres_data: {:#?}", pres);
} else {
panic!("Expected Pressure variant");
}
}
}