use RustedSciThe::symbolic::symbolic_engine::Expr;
use enum_dispatch::enum_dispatch;
use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub enum TransportError {
SerdeError(serde_json::Error),
InvalidUnit(String),
InvalidTemperature(f64),
InvalidTemperatureRange,
InvalidPressure(f64),
InvalidMolarMass(f64),
InvalidDensity(f64),
InvalidHeatCapacity(f64),
InvalidCollisionDiameter(f64),
NonFiniteOutput(String),
CalculationError(String),
MissingCoefficients(String),
MissingData(String),
ParseError(String),
SymbolicError(String),
FittingError(String),
}
impl fmt::Display for TransportError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TransportError::SerdeError(e) => write!(f, "Serde error: {}", e),
TransportError::InvalidUnit(unit) => write!(f, "Invalid unit: {}", unit),
TransportError::InvalidTemperature(t) => write!(f, "Invalid temperature: {}", t),
TransportError::InvalidTemperatureRange => write!(f, "Invalid temperature range"),
TransportError::InvalidPressure(p) => write!(f, "Invalid pressure: {}", p),
TransportError::InvalidMolarMass(m) => write!(f, "Invalid molar mass: {}", m),
TransportError::InvalidDensity(d) => write!(f, "Invalid density: {}", d),
TransportError::InvalidHeatCapacity(c) => write!(f, "Invalid heat capacity: {}", c),
TransportError::InvalidCollisionDiameter(d) => {
write!(f, "Invalid collision diameter: {}", d)
}
TransportError::NonFiniteOutput(field) => {
write!(f, "Non-finite output for {}", field)
}
TransportError::CalculationError(msg) => write!(f, "Calculation error: {}", msg),
TransportError::MissingCoefficients(msg) => write!(f, "Missing coefficients: {}", msg),
TransportError::MissingData(field) => write!(f, "Missing required data: {}", field),
TransportError::ParseError(field) => write!(f, "Missing required data: {}", field),
TransportError::SymbolicError(msg) => write!(f, "Symbolic error: {}", msg),
TransportError::FittingError(msg) => write!(f, "Fitting error: {}", msg),
}
}
}
impl Error for TransportError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
TransportError::SerdeError(err) => Some(err),
_ => None,
}
}
}
impl From<super::TRANSPORTdata::TransportError> for TransportError {
fn from(err: super::TRANSPORTdata::TransportError) -> Self {
match err {
super::TRANSPORTdata::TransportError::InvalidUnit(unit) => {
TransportError::InvalidUnit(unit)
}
super::TRANSPORTdata::TransportError::SerdeError(e) => TransportError::SerdeError(e),
super::TRANSPORTdata::TransportError::InvalidTemperature(t) => {
TransportError::InvalidTemperature(t)
}
super::TRANSPORTdata::TransportError::InvalidPressure(p) => {
TransportError::InvalidPressure(p)
}
super::TRANSPORTdata::TransportError::InvalidMolarMass(m) => {
TransportError::InvalidMolarMass(m)
}
super::TRANSPORTdata::TransportError::InvalidDensity(d) => {
TransportError::InvalidDensity(d)
}
super::TRANSPORTdata::TransportError::InvalidHeatCapacity(c) => {
TransportError::InvalidHeatCapacity(c)
}
super::TRANSPORTdata::TransportError::InvalidCollisionDiameter(d) => {
TransportError::InvalidCollisionDiameter(d)
}
super::TRANSPORTdata::TransportError::NonFiniteOutput(field) => {
TransportError::NonFiniteOutput(field)
}
super::TRANSPORTdata::TransportError::CalculationError(msg) => {
TransportError::CalculationError(msg)
}
super::TRANSPORTdata::TransportError::SymbolicError(msg) => {
TransportError::SymbolicError(msg)
}
super::TRANSPORTdata::TransportError::InvalidTemperatureRange => {
TransportError::InvalidTemperatureRange
}
}
}
}
impl From<super::CEAdata::CEAError> for TransportError {
fn from(err: super::CEAdata::CEAError) -> Self {
match err {
super::CEAdata::CEAError::InvalidUnit(unit) => TransportError::InvalidUnit(unit),
super::CEAdata::CEAError::SerdeError(e) => TransportError::SerdeError(e),
super::CEAdata::CEAError::InvalidTemperature(t) => {
TransportError::InvalidTemperature(t)
}
super::CEAdata::CEAError::MissingCoefficients(p) => {
TransportError::MissingCoefficients(p)
}
super::CEAdata::CEAError::ParseError(m) => TransportError::ParseError(m),
super::CEAdata::CEAError::MissingData(d) => TransportError::MissingData(d),
super::CEAdata::CEAError::InvalidTemperatureRange => {
TransportError::InvalidTemperatureRange
}
super::CEAdata::CEAError::SymbolicError(msg) => TransportError::SymbolicError(msg),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LambdaUnit {
WPerMK,
MWPerMK,
MKWPerMK,
MKWPerSMK,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ViscosityUnit {
KgPerMS,
PaS,
MKPaS,
}
#[enum_dispatch]
pub trait TransportCalculator {
fn extract_coefficients(&mut self, t: f64) -> Result<(), TransportError>;
fn calculate_lambda(
&mut self,
C: Option<f64>,
ro: Option<f64>,
t: f64,
) -> Result<f64, TransportError>;
fn calculate_viscosity(&mut self, t: f64) -> Result<f64, TransportError>;
fn set_lambda_unit(&mut self, unit: Option<LambdaUnit>) -> Result<(), TransportError>;
fn set_viscosity_unit(&mut self, unit: Option<ViscosityUnit>) -> Result<(), TransportError>;
fn create_lambda_closure(
&mut self,
C: Option<f64>,
ro: Option<f64>,
) -> Result<Box<dyn Fn(f64) -> f64 + Send + Sync>, TransportError>;
fn create_viscosity_closure(
&mut self,
) -> Result<Box<dyn Fn(f64) -> f64 + Send + Sync>, TransportError>;
fn create_symbolic_lambda(
&mut self,
C: Option<Expr>,
ro: Option<Expr>,
) -> Result<(), TransportError>;
fn create_symbolic_viscosity(&mut self) -> Result<(), TransportError>;
fn taylor_series_lambda(
&mut self,
C: Option<Expr>,
ro: Option<Expr>,
t0: f64,
n: usize,
) -> Result<Expr, TransportError>;
fn from_serde(&mut self, data: serde_json::Value) -> Result<(), TransportError>;
fn set_M(&mut self, M: f64, M_unit: Option<String>) -> Result<(), TransportError>;
fn set_P(&mut self, P: f64, P_unit: Option<String>) -> Result<(), TransportError>;
fn print_instance(&self) -> Result<(), TransportError>;
fn get_lambda_sym(&self) -> Result<Expr, TransportError>;
fn get_viscosity_sym(&self) -> Result<Expr, TransportError>;
fn get_lambda_fun(&self) -> Result<Box<dyn Fn(f64) -> f64 + Send + Sync>, TransportError>;
fn get_viscosity_fun(&self) -> Result<Box<dyn Fn(f64) -> f64 + Send + Sync>, TransportError>;
fn fitting_coeffs_for_T_interval(&mut self) -> Result<(), TransportError>;
fn integr_mean(&mut self) -> Result<(), TransportError>;
fn set_T_interval(&mut self, T_min: f64, T_max: f64) -> Result<(), TransportError>;
}
pub fn lambda_dimension(unit: LambdaUnit) -> String {
match unit {
LambdaUnit::WPerMK => "W/m/K".to_owned(),
LambdaUnit::MWPerMK => "mW/m/K".to_owned(),
LambdaUnit::MKWPerMK => "mkW/m/K".to_owned(),
LambdaUnit::MKWPerSMK => "mkW/sm/K".to_owned(),
}
}
pub fn viscosity_dimension(unit: ViscosityUnit) -> String {
match unit {
ViscosityUnit::KgPerMS => "kg/m/s".to_owned(),
ViscosityUnit::PaS => "Pa*s".to_owned(),
ViscosityUnit::MKPaS => "mkPa*s".to_owned(),
}
}
pub fn validate_temperature(t: f64) -> Result<(), TransportError> {
if !t.is_finite() || t <= 0.0 {
Err(TransportError::InvalidTemperature(t))
} else {
Ok(())
}
}
pub fn validate_pressure(p: f64) -> Result<(), TransportError> {
if !p.is_finite() || p <= 0.0 {
Err(TransportError::InvalidPressure(p))
} else {
Ok(())
}
}
pub fn validate_molar_mass(m: f64) -> Result<(), TransportError> {
if !m.is_finite() || m <= 0.0 {
Err(TransportError::InvalidMolarMass(m))
} else {
Ok(())
}
}
pub fn validate_density(d: f64) -> Result<(), TransportError> {
if !d.is_finite() || d <= 0.0 {
Err(TransportError::InvalidDensity(d))
} else {
Ok(())
}
}
pub fn validate_heat_capacity(c: f64) -> Result<(), TransportError> {
if !c.is_finite() || c <= 0.0 {
Err(TransportError::InvalidHeatCapacity(c))
} else {
Ok(())
}
}
pub fn validate_collision_diameter(d: f64) -> Result<(), TransportError> {
if !d.is_finite() || d <= 0.0 {
Err(TransportError::InvalidCollisionDiameter(d))
} else {
Ok(())
}
}
#[derive(Debug, Clone)]
#[enum_dispatch(TransportCalculator)]
pub enum TransportEnum {
CEA(super::CEAdata::CEAdata),
Collision(super::TRANSPORTdata::TransportData),
}
pub enum TransportType {
CEA,
Collision,
}
pub fn create_transport_calculator(calc_type: TransportType) -> TransportEnum {
match calc_type {
TransportType::CEA => TransportEnum::CEA(super::CEAdata::CEAdata::new()),
TransportType::Collision => {
TransportEnum::Collision(super::TRANSPORTdata::TransportData::new())
}
}
}
pub fn create_transport_calculator_by_name(
calc_name: &str,
) -> Result<TransportEnum, TransportError> {
match calc_name {
"CEA" => Ok(TransportEnum::CEA(super::CEAdata::CEAdata::new())),
"Aramco_transport" => Ok(TransportEnum::Collision(
super::TRANSPORTdata::TransportData::new(),
)),
_ => Err(TransportError::MissingData(format!(
"transport library '{}'",
calc_name
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Thermodynamics::thermo_lib_api::ThermoData;
#[test]
fn test_unit_conversion() {}
#[test]
fn test_validation() {
assert!(validate_temperature(300.0).is_ok());
assert!(validate_temperature(-1.0).is_err());
assert!(validate_temperature(0.0).is_err());
assert!(validate_temperature(f64::NAN).is_err());
assert!(validate_temperature(f64::INFINITY).is_err());
assert!(validate_pressure(101325.0).is_ok());
assert!(validate_pressure(-1.0).is_err());
assert!(validate_pressure(0.0).is_err());
assert!(validate_pressure(f64::NAN).is_err());
assert!(validate_pressure(f64::INFINITY).is_err());
assert!(validate_molar_mass(28.0).is_ok());
assert!(validate_molar_mass(-1.0).is_err());
assert!(validate_molar_mass(0.0).is_err());
assert!(validate_molar_mass(f64::NAN).is_err());
assert!(validate_molar_mass(f64::INFINITY).is_err());
assert!(validate_density(1.225).is_ok());
assert!(validate_density(-1.0).is_err());
assert!(validate_density(0.0).is_err());
assert!(validate_density(f64::NAN).is_err());
assert!(validate_density(f64::INFINITY).is_err());
}
#[test]
fn test_validation_table_for_scalar_helpers() {
let invalid_values = [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 0.0, -1.0];
for value in invalid_values {
assert!(
validate_temperature(value).is_err(),
"temperature {:?}",
value
);
assert!(validate_pressure(value).is_err(), "pressure {:?}", value);
assert!(
validate_molar_mass(value).is_err(),
"molar mass {:?}",
value
);
assert!(validate_density(value).is_err(), "density {:?}", value);
assert!(
validate_heat_capacity(value).is_err(),
"heat capacity {:?}",
value
);
assert!(
validate_collision_diameter(value).is_err(),
"collision diameter {:?}",
value
);
}
let valid_values = [1.0, 300.0, 101325.0];
for value in valid_values {
assert!(
validate_temperature(value).is_ok(),
"temperature {:?}",
value
);
assert!(validate_pressure(value).is_ok(), "pressure {:?}", value);
assert!(validate_molar_mass(value).is_ok(), "molar mass {:?}", value);
assert!(validate_density(value).is_ok(), "density {:?}", value);
assert!(
validate_heat_capacity(value).is_ok(),
"heat capacity {:?}",
value
);
assert!(
validate_collision_diameter(value).is_ok(),
"collision diameter {:?}",
value
);
}
}
#[test]
fn test_calculator_creation() {
let thermo_data = ThermoData::new();
let sublib = thermo_data.LibThermoData.get("Aramco_transport").unwrap();
let _CO_data = sublib.get("CO").unwrap();
}
#[test]
fn test_factory_method_CEA() {
let thermo_data = ThermoData::new();
let mut cea_calc = create_transport_calculator(TransportType::CEA);
let sublib = thermo_data.LibThermoData.get("CEA").unwrap();
let co_data = sublib.get("CO").unwrap();
cea_calc.from_serde(co_data.clone()).unwrap();
cea_calc.set_lambda_unit(Some(LambdaUnit::MWPerMK)).unwrap();
cea_calc
.set_viscosity_unit(Some(ViscosityUnit::MKPaS))
.unwrap();
cea_calc.extract_coefficients(500.0).unwrap();
let lambda_cea = cea_calc.calculate_lambda(None, None, 500.0).unwrap();
let visc_cea = cea_calc.calculate_viscosity(500.0).unwrap();
assert!(lambda_cea > 0.0);
assert!(visc_cea > 0.0);
}
#[test]
fn test_CEA2() {
let mut transport_calc = create_transport_calculator_by_name("CEA").unwrap();
let thermo_data = ThermoData::new();
let sublib = thermo_data.LibThermoData.get("CEA").unwrap();
let substance_data = sublib.get("CO").unwrap();
let _ = transport_calc
.from_serde(substance_data.clone())
.map_err(|e| format!("Failed to load data: {}", e));
let _ = transport_calc
.extract_coefficients(400.9)
.map_err(|e| format!("Failed to extract coefficients: {}", e));
let lambda_cea = transport_calc.calculate_lambda(None, None, 500.0).unwrap();
let visc_cea = transport_calc.calculate_viscosity(500.0).unwrap();
println!("lambda_cea: {}", lambda_cea);
println!("visc_cea: {}", visc_cea);
}
#[test]
fn test_factory_method_Collision() {
let thermo_data = ThermoData::new();
let mut collision_calc = create_transport_calculator(TransportType::Collision);
let sublib = thermo_data.LibThermoData.get("Aramco_transport").unwrap();
let co_data = sublib.get("CO").unwrap();
let T = 500.0;
let _ = collision_calc.set_M(0.028, None);
let _ = collision_calc.set_P(101325.0, Some("Pa".to_owned()));
collision_calc.from_serde(co_data.clone()).unwrap();
collision_calc
.set_lambda_unit(Some(LambdaUnit::MWPerMK))
.unwrap();
collision_calc
.set_viscosity_unit(Some(ViscosityUnit::MKPaS))
.unwrap();
let _ = collision_calc.extract_coefficients(T);
let Cp = 29.15;
let lambda_collision = collision_calc
.calculate_lambda(Some(Cp), None, 500.0)
.unwrap();
let visc_collision = collision_calc.calculate_viscosity(T).unwrap();
assert!(lambda_collision > 0.0);
assert!(visc_collision > 0.0);
}
}