use std::collections::HashMap;
use std::fmt::format;
use super::super::solid_state_kinetics_IVP::{IVPSolution, KineticModelIVP, KineticModelNames};
use super::kinetic_methods::integral_isoconversion::IntegralIsoconversionalSolver;
use super::kinetic_methods::{ConversionGridBuilder, ExperimentData, KineticDataView};
use super::one_experiment_dataset::TGADomainError;
use crate::Kinetics::experimental_kinetics::experiment_series_main::ExperimentMeta;
use RustedSciThe::numerical::ODE_api2::{SolverParam, SolverType, UniversalODESolver};
use std::time::Instant;
pub fn solution_to_experiment(sol: IVPSolution, heating_rate: f64, id: &str) -> ExperimentData {
ExperimentData {
meta: ExperimentMeta {
id: id.to_string(),
heating_rate: Some(heating_rate),
isothermal_temperature: None,
comment: None,
},
time: sol.time,
temperature: sol.temperature,
conversion: sol.conversion,
conversion_rate: sol.conversion_rate,
mass: None,
mass_rate: None,
}
}
pub fn simulate_tga_dataset(
model: KineticModelNames,
e: f64,
a: f64,
t0: f64,
betas: &[f64],
t_end: f64,
solvertype: SolverType,
params: Vec<f64>,
id: &str,
) -> Result<KineticDataView, TGADomainError> {
let mut experiments = Vec::with_capacity(betas.len());
for &beta in betas {
let mut ivp = KineticModelIVP::new(solvertype.clone());
let step = (t_end / 2000.0).max(1e-4).min(1.0);
let max_iter = 500;
let mut solver_params: HashMap<String, SolverParam> = HashMap::new();
solver_params.insert("step_size".to_string(), SolverParam::Float(step));
solver_params.insert("max_step".to_string(), SolverParam::Float(step));
solver_params.insert("max_iterations".to_string(), SolverParam::Int(max_iter));
solver_params.insert("tolerance".to_string(), SolverParam::Float(1e-3));
solver_params.insert("rtol".to_string(), SolverParam::Float(1e-3));
solver_params.insert("atol".to_string(), SolverParam::Float(1e-3));
solver_params.insert("parallel".to_string(), SolverParam::Bool(true));
ivp.set_solver_params(solver_params);
let _ = ivp.set_model(model.clone(), params.clone());
let _ = ivp.set_arrhenius(e, a);
let _ = ivp.set_heating_program(t0, beta);
let _ = ivp.set_time(t_end);
let _ = ivp
.solve()
.map_err(|s| TGADomainError::InvalidOperation(format!("error solving ivp {}", s)));
let sol: IVPSolution = ivp.get_solution().unwrap();
let exp = solution_to_experiment(sol, beta, id);
println!("problem for beta = {} solved", beta);
experiments.push(exp);
}
Ok(KineticDataView { experiments })
}
#[test]
fn test_conversion_grid_build() {
fn synthetic_experiment(id: &str, beta: f64) -> ExperimentData {
let mut time = Vec::with_capacity(101);
let mut temperature = Vec::with_capacity(101);
let mut conversion = Vec::with_capacity(101);
let mut conversion_rate = Vec::with_capacity(101);
for i in 0..=100 {
let t = i as f64;
time.push(t);
temperature.push(420.0 + beta * t);
conversion.push(i as f64 / 100.0);
conversion_rate.push(0.01);
}
ExperimentData {
meta: ExperimentMeta {
id: id.to_string(),
heating_rate: Some(beta),
isothermal_temperature: None,
comment: None,
},
time,
temperature,
conversion,
conversion_rate,
mass: None,
mass_rate: None,
}
}
let data = KineticDataView {
experiments: vec![
synthetic_experiment("exp_a", 0.5),
synthetic_experiment("exp_b", 1.0),
synthetic_experiment("exp_c", 2.0),
],
};
let grid = ConversionGridBuilder::new()
.eta_range(0.05, 0.95)
.segments(80)
.build_nonisothermal(&data)
.unwrap();
assert_eq!(grid.temperature.nrows(), 3);
assert_eq!(grid.eta.len(), 80);
assert!(grid.temperature[[0, 10]] > 300.0);
}
#[test]
fn test_kas_activation_energy() {
let true_e = 50_000.0;
let now = Instant::now();
let data = simulate_tga_dataset(
KineticModelNames::F1,
true_e,
1e6,
0.0,
&[2.0, 5.0, 10.0, 20.0],
50.0,
SolverType::BDF,
vec![],
"",
)
.unwrap();
println!("tga simulated {:?}", now.elapsed());
let grid = ConversionGridBuilder::new()
.segments(80)
.build_isothermal(&data)
.unwrap();
let solver = IntegralIsoconversionalSolver::kas();
grid.report();
let result = solver.solve(&grid).unwrap();
result.pretty_print_and_assert(0.05, 0.95, 1000, Some(0.99));
}