pub mod fast_charging;
pub use fast_charging::{
CcStage, ChargingProtocol, ChargingState, FastChargingConfig, FastChargingResult,
FastChargingSimulator, FcBatteryParams, ProtocolComparison,
};
pub mod advanced_bms;
pub mod degradation_model;
pub use degradation_model::{
AgingConditions, AgingMode, CapacityFade, CycleCounterDoD, DegradationChemistry,
DegradationMechanism, DegradationModel, ResistanceGrowth,
};
pub mod aging;
pub mod aging_map;
pub mod bms;
pub mod charging;
pub mod ecm;
pub mod electrothermal;
pub mod fault_detection;
pub mod grid_services;
pub mod marketplace;
pub mod p2d;
pub mod pack;
pub mod safety;
pub mod scheduler;
pub mod second_life;
pub mod soc;
pub mod soh_prediction;
pub mod sop;
pub mod state_estimation;
pub mod thermal;
pub use electrothermal::{
CurrentStep, DriveCycle, ElectrothermalCell, ElectrothermalPack, ElectrothermalSimulator,
ElectrothermalState, EtSimConfig, EtSimResult, PackEtResult, ThermalManagement,
};
pub mod thermal_management;
pub mod v2g_second_life;
use crate::units::{Current, Energy, StateOfCharge, Temperature, Voltage};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct BatteryState {
pub voltage: Voltage,
pub soc: StateOfCharge,
pub temperature: Temperature,
pub internal_resistance: f64, pub capacity_remaining: Energy,
pub current: Current,
}
pub trait BatteryModel {
fn terminal_voltage(&self, soc: StateOfCharge, current: Current, temp: Temperature) -> Voltage;
fn step(&mut self, current: Current, dt: f64, temp: Temperature) -> BatteryState;
fn state(&self) -> BatteryState;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OcvSocCurve {
points: Vec<(f64, f64)>,
}
impl OcvSocCurve {
pub fn new(mut points: Vec<(f64, f64)>) -> Self {
points.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
Self { points }
}
pub fn lfp_default() -> Self {
Self::new(vec![
(0.00, 3.000),
(0.05, 3.100),
(0.10, 3.180),
(0.20, 3.250),
(0.30, 3.280),
(0.40, 3.300),
(0.50, 3.320),
(0.60, 3.340),
(0.70, 3.350),
(0.80, 3.360),
(0.90, 3.380),
(0.95, 3.400),
(1.00, 3.650),
])
}
pub fn nmc_default() -> Self {
Self::new(vec![
(0.00, 3.000),
(0.05, 3.400),
(0.10, 3.520),
(0.20, 3.620),
(0.30, 3.680),
(0.40, 3.720),
(0.50, 3.760),
(0.60, 3.810),
(0.70, 3.860),
(0.80, 3.920),
(0.90, 3.980),
(0.95, 4.060),
(1.00, 4.200),
])
}
pub fn ocv(&self, soc: f64) -> f64 {
let soc = soc.clamp(0.0, 1.0);
let pts = &self.points;
if pts.is_empty() {
return 0.0;
}
if soc <= pts[0].0 {
return pts[0].1;
}
if soc >= pts[pts.len() - 1].0 {
return pts[pts.len() - 1].1;
}
let pos = pts.partition_point(|&(s, _)| s <= soc);
let (s0, v0) = pts[pos - 1];
let (s1, v1) = pts[pos];
let alpha = (soc - s0) / (s1 - s0);
v0 + alpha * (v1 - v0)
}
pub fn docv_dsoc(&self, soc: f64) -> f64 {
let eps = 1e-4;
let soc = soc.clamp(eps, 1.0 - eps);
(self.ocv(soc + eps) - self.ocv(soc - eps)) / (2.0 * eps)
}
}
pub use advanced_bms::{
AdvancedBms, AdvancedBmsConfig, BalancingStrategy, BmsChemistry, BmsError, BmsFault,
BmsFaultType, BmsResult, CellState as AdvancedCellState, FaultSeverity, PackState,
};
pub use second_life::{
BatteryChemistry, PackAssemblyResult, PortfolioAllocation, SecondLifeApplication,
SecondLifeEconomics, SecondLifeEconomicsResult, SecondLifePackAssembler, SecondLifePortfolio,
SohAssessment, SohGrade, SohResult,
};
pub use soh_prediction::{
ChargeStrategy, SohError, SohForecast, SohPredictionConfig, SohPredictor, UsagePattern,
};
pub use state_estimation::{
BatteryChemistryType, BatteryPackEstimator, BatteryStateEstimatorConfig, EcmParameters,
PackStateEstimate,
};
pub use v2g_second_life::{
EvBatteryProfile, SecondLifeAssessment2, V2gComparison, V2gDegradationConfig,
V2gSecondLifeAnalyzer, V2gSecondLifeEconomics,
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ocv_interpolation() {
let curve = OcvSocCurve::lfp_default();
assert!((curve.ocv(0.0) - 3.0).abs() < 1e-6);
assert!((curve.ocv(1.0) - 3.65).abs() < 1e-6);
let v50 = curve.ocv(0.50);
let v90 = curve.ocv(0.90);
assert!(v90 > v50);
}
#[test]
fn test_ocv_clamp() {
let curve = OcvSocCurve::nmc_default();
let v_low = curve.ocv(-0.1);
let v_high = curve.ocv(1.1);
assert_eq!(v_low, curve.ocv(0.0));
assert_eq!(v_high, curve.ocv(1.0));
}
}