use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SeparatorParams {
pub thickness: f64,
pub porosity: f64,
pub bruggeman: f64,
pub t_max_k: f64,
}
impl SeparatorParams {
pub fn celgard_2500() -> Self {
Self {
thickness: 25e-6,
porosity: 0.55,
bruggeman: 1.5,
t_max_k: 403.15, }
}
pub fn ceramic_16um() -> Self {
Self {
thickness: 16e-6,
porosity: 0.40,
bruggeman: 1.5,
t_max_k: 473.15, }
}
pub fn effective_transport_factor(&self) -> f64 {
self.porosity.powf(self.bruggeman)
}
pub fn tortuosity(&self) -> f64 {
self.porosity.powf(1.0 - self.bruggeman)
}
pub fn resistance_area(&self, kappa_electrolyte: f64) -> f64 {
let kappa_eff = kappa_electrolyte * self.effective_transport_factor();
if kappa_eff < 1e-20 {
return f64::INFINITY;
}
self.thickness / kappa_eff
}
pub fn is_overtemp(&self, temp_k: f64) -> bool {
temp_k > self.t_max_k
}
}
pub struct SeparatorState {
pub params: SeparatorParams,
pub c_e: Vec<f64>,
pub phi_e: Vec<f64>,
pub n_nodes: usize,
#[allow(dead_code)]
dx: f64,
}
impl SeparatorState {
pub fn new(params: SeparatorParams, c_e_init: f64, n_nodes: usize) -> Self {
let dx = params.thickness / n_nodes as f64;
Self {
c_e: vec![c_e_init; n_nodes],
phi_e: vec![0.0; n_nodes],
n_nodes,
dx,
params,
}
}
pub fn set_boundary_concentrations(&mut self, c_left: f64, c_right: f64) {
for i in 0..self.n_nodes {
let alpha = (i as f64 + 0.5) / self.n_nodes as f64;
self.c_e[i] = c_left + alpha * (c_right - c_left);
}
}
pub fn c_avg(&self) -> f64 {
self.c_e.iter().sum::<f64>() / self.n_nodes as f64
}
pub fn ohmic_drop(&self, current_density_a_m2: f64, kappa_electrolyte: f64) -> f64 {
current_density_a_m2 * self.params.resistance_area(kappa_electrolyte)
}
pub fn reset(&mut self, c_e_init: f64) {
self.c_e.fill(c_e_init);
self.phi_e.fill(0.0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_celgard_thickness() {
let sep = SeparatorParams::celgard_2500();
assert!((sep.thickness - 25e-6).abs() < 1e-10);
}
#[test]
fn test_effective_transport_factor_lt_one() {
let sep = SeparatorParams::celgard_2500();
let eta = sep.effective_transport_factor();
assert!(eta > 0.0 && eta < 1.0, "Factor should be in (0,1): {}", eta);
}
#[test]
fn test_resistance_positive() {
let sep = SeparatorParams::celgard_2500();
let r = sep.resistance_area(1.1); assert!(r > 0.0, "Resistance should be positive: {}", r);
}
#[test]
fn test_overtemp_detection() {
let sep = SeparatorParams::celgard_2500();
assert!(!sep.is_overtemp(298.15));
assert!(sep.is_overtemp(420.0)); }
#[test]
fn test_separator_state_init() {
let params = SeparatorParams::celgard_2500();
let state = SeparatorState::new(params, 1000.0, 5);
assert_eq!(state.n_nodes, 5);
for &c in &state.c_e {
assert!((c - 1000.0).abs() < 1e-10);
}
}
#[test]
fn test_linear_concentration_profile() {
let params = SeparatorParams::celgard_2500();
let mut state = SeparatorState::new(params, 1000.0, 10);
state.set_boundary_concentrations(900.0, 1100.0);
for i in 1..state.n_nodes {
assert!(state.c_e[i] > state.c_e[i - 1]);
}
let mid = (900.0 + 1100.0) / 2.0;
assert!((state.c_avg() - mid).abs() < 10.0);
}
#[test]
fn test_ceramic_vs_celgard_resistance() {
let kappa = 1.1;
let sep1 = SeparatorParams::celgard_2500();
let sep2 = SeparatorParams::ceramic_16um();
let r1 = sep1.resistance_area(kappa);
let r2 = sep2.resistance_area(kappa);
assert!(r1 > 0.0 && r2 > 0.0);
}
}