use super::SimpleReactorBVP::ReactorError;
use nalgebra::DMatrix;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub enum InitialTemplate {
Linear { start: f64, end: f64 },
DecreasingExp { start: f64, decay: f64 },
IncreasingExp { end: f64, growth: f64 },
Constant { value: f64 },
Sigmoid {
start: f64,
end: f64,
steepness: f64,
center: f64,
},
}
impl InitialTemplate {
pub fn generate(&self, n_steps: usize) -> Vec<f64> {
let mut values = Vec::with_capacity(n_steps);
for i in 0..n_steps {
let z = i as f64 / (n_steps - 1) as f64;
let value = match self {
InitialTemplate::Linear { start, end } => start + (end - start) * z,
InitialTemplate::DecreasingExp { start, decay } => start * (-decay * z).exp(),
InitialTemplate::IncreasingExp { end, growth } => end * (1.0 - (-growth * z).exp()),
InitialTemplate::Constant { value } => *value,
InitialTemplate::Sigmoid {
start,
end,
steepness,
center,
} => start + (end - start) / (1.0 + (-steepness * (z - center)).exp()),
};
values.push(value);
}
values
}
}
#[derive(Debug, Clone)]
pub struct InitialConfig {
pub templates: HashMap<String, InitialTemplate>,
}
impl InitialConfig {
pub fn new() -> Self {
Self {
templates: HashMap::new(),
}
}
pub fn set_template(&mut self, var_type: &str, template: InitialTemplate) {
self.templates.insert(var_type.to_string(), template);
}
pub fn generate_initial_guess(
&self,
unknowns: &[String],
n_steps: usize,
) -> Result<DMatrix<f64>, ReactorError> {
let n_vars = unknowns.len();
let mut data = Vec::with_capacity(n_vars * n_steps);
for unknown in unknowns {
let var_type = self.get_variable_type(unknown)?;
let template = self.templates.get(&var_type).ok_or_else(|| {
ReactorError::MissingData(format!(
"No template found for variable type: {}",
var_type
))
})?;
let values = template.generate(n_steps);
data.extend(values);
}
Ok(DMatrix::from_vec(n_vars, n_steps, data))
}
fn get_variable_type(&self, unknown: &str) -> Result<String, ReactorError> {
if unknown == "Teta" {
Ok("Teta".to_string())
} else if unknown == "q" {
Ok("q".to_string())
} else if unknown.starts_with('C') {
Ok("C".to_string())
} else if unknown.starts_with('J') {
Ok("J".to_string())
} else {
Err(ReactorError::InvalidConfiguration(format!(
"Unknown variable type for: {}",
unknown
)))
}
}
pub fn all_const_initial(
&self,
map_of_values: HashMap<String, f64>,
n_steps: usize,
) -> Result<DMatrix<f64>, ReactorError> {
let unknowns: Vec<String> = map_of_values
.iter()
.map(|(unknown, _)| unknown.clone())
.collect();
let mut templates = HashMap::new();
for (unknown, value) in map_of_values.iter() {
let template = InitialTemplate::Constant { value: *value };
templates.insert(unknown.to_string(), template);
}
let mut inconfg = InitialConfig::new();
inconfg.templates = templates;
let initial_guess = inconfg.generate_initial_guess(&unknowns, n_steps);
initial_guess
}
pub fn only_one_value_for_all_initial(
&self,
value: f64,
n_steps: usize,
n_values: usize,
) -> Result<DMatrix<f64>, ReactorError> {
let ig = vec![value; n_steps * n_values];
let initial_guess = DMatrix::from_vec(n_values, n_steps, ig);
Ok(initial_guess)
}
}
impl Default for InitialConfig {
fn default() -> Self {
let mut config = Self::new();
config.set_template(
"C",
InitialTemplate::Linear {
start: 0.5,
end: 0.1,
},
);
config.set_template("J", InitialTemplate::Constant { value: 0.0 });
config.set_template(
"Teta",
InitialTemplate::Linear {
start: 0.0,
end: 1.0,
},
);
config.set_template("q", InitialTemplate::Constant { value: 0.0 });
config
}
}
#[derive(Debug, Clone)]
pub struct ToleranceConfig {
pub C: f64,
pub J: f64,
pub Teta: f64,
pub q: f64,
}
impl Default for ToleranceConfig {
fn default() -> Self {
Self {
C: 1e-4,
J: 1e-4,
Teta: 1e-5,
q: 1e-4,
}
}
}
impl ToleranceConfig {
pub fn new(C: f64, J: f64, Teta: f64, q: f64) -> Self {
Self { C, J, Teta, q }
}
pub fn to_full_tolerance_map(&self, substances: &[String]) -> HashMap<String, f64> {
let mut tolerance_map = HashMap::new();
tolerance_map.insert("Teta".to_string(), self.Teta);
tolerance_map.insert("q".to_string(), self.q);
for (i, _) in substances.iter().enumerate() {
tolerance_map.insert(format!("C{}", i), self.C);
tolerance_map.insert(format!("J{}", i), self.J);
}
tolerance_map
}
}
pub fn create_tolerance_map(
tolerance_config: HashMap<String, f64>,
substances: &[String],
) -> HashMap<String, f64> {
let mut full_tolerance_map = HashMap::new();
let default_C = tolerance_config.get("C").copied().unwrap_or(1e-4);
let default_J = tolerance_config.get("J").copied().unwrap_or(1e-4);
let default_Teta = tolerance_config.get("Teta").copied().unwrap_or(1e-5);
let default_q = tolerance_config.get("q").copied().unwrap_or(1e-4);
full_tolerance_map.insert("Teta".to_string(), default_Teta);
full_tolerance_map.insert("q".to_string(), default_q);
for (i, _) in substances.iter().enumerate() {
full_tolerance_map.insert(format!("C{}", i), default_C);
full_tolerance_map.insert(format!("J{}", i), default_J);
}
full_tolerance_map
}
#[derive(Debug, Clone)]
pub struct BoundsConfig {
pub C: (f64, f64),
pub J: (f64, f64),
pub Teta: (f64, f64),
pub q: (f64, f64),
}
impl Default for BoundsConfig {
fn default() -> Self {
Self {
C: (0.0, 1.0),
J: (-1e20, 1e20),
Teta: (-10.0, 10.0),
q: (-1e20, 1e20),
}
}
}
impl BoundsConfig {
pub fn new(C: (f64, f64), J: (f64, f64), Teta: (f64, f64), q: (f64, f64)) -> Self {
Self { C, J, Teta, q }
}
pub fn to_full_bounds_map(&self, substances: &[String]) -> HashMap<String, (f64, f64)> {
let mut bounds_map = HashMap::new();
bounds_map.insert("Teta".to_string(), self.Teta);
bounds_map.insert("q".to_string(), self.q);
for (i, _) in substances.iter().enumerate() {
bounds_map.insert(format!("C{}", i), self.C);
bounds_map.insert(format!("J{}", i), self.J);
}
bounds_map
}
}
pub fn create_bounds_map(
bounds_config: HashMap<String, (f64, f64)>,
substances: &[String],
) -> HashMap<String, (f64, f64)> {
let mut full_bounds_map = HashMap::new();
let default_C = bounds_config.get("C").copied().unwrap_or((0.0, 1.0));
let default_J = bounds_config.get("J").copied().unwrap_or((-1e20, 1e20));
let default_Teta = bounds_config.get("Teta").copied().unwrap_or((-10.0, 10.0));
let default_q = bounds_config.get("q").copied().unwrap_or((-1e20, 1e20));
full_bounds_map.insert("Teta".to_string(), default_Teta);
full_bounds_map.insert("q".to_string(), default_q);
for (i, _) in substances.iter().enumerate() {
full_bounds_map.insert(format!("C{}", i), default_C);
full_bounds_map.insert(format!("J{}", i), default_J);
}
full_bounds_map
}
#[derive(Debug, Clone)]
pub struct ScalingConfig {
pub dT: f64,
pub L: f64,
pub T_scale: f64,
}
impl Default for ScalingConfig {
fn default() -> Self {
Self {
dT: 100.0, L: 0.01, T_scale: 100.0,
}
}
}
impl ScalingConfig {
pub fn new(dT: f64, L: f64, T_scale: f64) -> Self {
Self { dT, L, T_scale }
}
pub fn validate(&self) -> Result<(), ReactorError> {
if self.dT <= 0.0 {
return Err(ReactorError::InvalidConfiguration(
"Temperature scaling dT must be positive".to_string(),
));
}
if self.L <= 0.0 {
return Err(ReactorError::InvalidConfiguration(
"Length scaling L must be positive".to_string(),
));
}
Ok(())
}
pub fn to_hashmap(&self) -> HashMap<String, f64> {
let mut map = HashMap::new();
map.insert("dT".to_string(), self.dT);
map.insert("L".to_string(), self.L);
map
}
pub fn from_hashmap(map: &HashMap<String, f64>) -> Result<Self, ReactorError> {
let dT = map.get("dT").copied().ok_or_else(|| {
ReactorError::MissingData("Missing dT in scaling parameters".to_string())
})?;
let L = map.get("L").copied().ok_or_else(|| {
ReactorError::MissingData("Missing L in scaling parameters".to_string())
})?;
let T_scale = map.get("T_scale").copied().ok_or_else(|| {
ReactorError::MissingData("Missing L in scaling parameters".to_string())
})?;
let config = Self::new(dT, L, T_scale);
config.validate()?;
Ok(config)
}
}