#[cfg(test)]
mod tests {
use crate::Thermodynamics::DBhandlers::thermo_api::ThermoCalculator;
use crate::Thermodynamics::DBhandlers::transport_api::TransportCalculator;
use crate::Thermodynamics::User_substances::{
CalculatorType, DataType, LibraryPriority, Phases, PropertyKind, SearchResult, SubsData,
WhatIsFound,
};
use crate::Thermodynamics::thermo_lib_api::LibraryId;
use std::collections::HashMap;
#[test]
fn test_user_substances_with_calculators() {
let substances = vec!["CO".to_string(), "H2O".to_string()];
let mut user_subs = SubsData::new();
user_subs
.map_of_phases
.insert("CO".to_string(), Some(Phases::Gas));
user_subs
.map_of_phases
.insert("H2O".to_string(), Some(Phases::Gas));
user_subs.substances = substances.clone();
user_subs.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
user_subs.set_multiple_library_priorities(
vec!["Aramco_transport".to_string()],
LibraryPriority::Priority,
);
user_subs.search_substances().unwrap();
user_subs
.extract_thermal_coeffs(substances[0].as_str(), 400.0)
.unwrap();
user_subs
.extract_thermal_coeffs(substances[1].as_str(), 400.0)
.unwrap();
if let Ok((cp, dh, ds)) = user_subs.calculate_thermo_properties("CO", 400.0) {
println!("CO Thermo properties at 400K:");
println!("Cp: {}, dH: {}, dS: {}", cp, dh, ds);
assert!(cp > 0.0);
} else {
panic!("Failed to calculate CO properties");
}
let Cp = 33.8;
user_subs
.extract_transport_coeffs(substances[0].as_str(), 400.0)
.unwrap();
user_subs
.extract_transport_coeffs(substances[1].as_str(), 400.0)
.unwrap();
let _ = user_subs.set_M(
HashMap::from([("H2O".to_string(), 18.0), ("CO".to_string(), 32.0)]),
None,
);
let _ = user_subs.set_P(1e5, None);
if let Ok((lambda, viscosity)) =
user_subs.calculate_transport_properties("H2O", 400.0, Some(Cp), None)
{
println!("CO Transport properties at 400K:");
println!("Lambda: {}, Viscosity: {}", lambda, viscosity);
assert!(lambda > 0.0);
assert!(viscosity > 0.0);
} else {
panic!("Failed to calculate H2O properties");
}
user_subs.print_search_summary();
}
#[test]
fn physical_state_requirement_selects_the_nasa_condensed_liquid_record() {
let mut substances = SubsData::new();
substances.set_substances(vec!["Fe".to_string()]);
substances.set_substance_physical_state("Fe".to_string(), Phases::Liquid);
substances.set_library_priority("NASA_cond".to_string(), LibraryPriority::Priority);
substances.search_substances().unwrap();
let record = substances
.get_search_result("Fe", WhatIsFound::Thermo)
.expect("NASA condensed should contain a liquid iron record");
assert_eq!(record.library(), "NASA_cond");
assert_eq!(record.record_key(), "Fe(L)");
let summary = substances.search_summary_report();
assert!(
summary
.rows()
.iter()
.any(|row| row.substance() == "Fe" && row.record_key() == "Fe(L)")
);
}
#[test]
fn test_user_substances_with_calculators_own_functions() {
let substances = vec!["CO".to_string(), "H2O".to_string()];
let mut user_subs = SubsData::new();
user_subs
.map_of_phases
.insert("CO".to_string(), Some(Phases::Gas));
user_subs
.map_of_phases
.insert("H2O".to_string(), Some(Phases::Gas));
user_subs.substances = substances.clone();
user_subs.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
user_subs.set_multiple_library_priorities(
vec!["Aramco_transport".to_string()],
LibraryPriority::Priority,
);
user_subs.search_substances().unwrap();
user_subs
.extract_thermal_coeffs(substances[0].as_str(), 400.0)
.unwrap();
user_subs
.extract_thermal_coeffs(substances[1].as_str(), 400.0)
.unwrap();
user_subs.print_search_summary();
let datamap = user_subs.get_substance_result("CO").unwrap();
let Thermo = datamap.get(&WhatIsFound::Thermo).unwrap().as_ref().unwrap();
let Calculator = Thermo.calculator().unwrap();
let Cp;
match Calculator {
CalculatorType::Thermo(thermo) => {
let mut thermo = thermo.clone();
thermo.extract_model_coefficients(400.0).unwrap();
if let Ok(()) = thermo.calculate_Cp_dH_dS(400.0) {
let (cp, dh, ds) = (
thermo.get_Cp().unwrap(),
thermo.get_dh().unwrap(),
thermo.get_ds().unwrap(),
); println!("CO Thermo properties at 400K:");
println!("Cp: {}, dH: {}, dS: {}", cp, dh, ds);
Cp = cp;
assert!(cp > 0.0);
} else {
panic!("Failed to calculate CO properties");
}
}
_ => {
panic!("Failed to calculate CO properties");
}
}
let Transport = datamap
.get(&WhatIsFound::Transport)
.unwrap()
.as_ref()
.unwrap();
let Calculator = Transport.calculator().unwrap();
match Calculator {
CalculatorType::Transport(transport) => {
let mut transport = transport.clone();
let _ = transport.set_M(32.0, None);
let _ = transport.set_P(1e5, None);
transport.extract_coefficients(400.0).unwrap();
if let Ok(L) = transport.calculate_lambda(Some(Cp), None, 400.0) {
println!("CO Transport properties at 400K:");
println!("Lambda: {}", L);
assert!(L > 0.0);
} else {
panic!("Failed to calculate H2O properties");
}
}
_ => {
panic!("Failed to calculate CO properties");
}
}
}
#[test]
fn test_library_priority_setting() {
let mut user_subs = SubsData::new();
user_subs.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
assert_eq!(
user_subs.library_priorities.get("NASA_gas"),
Some(&LibraryPriority::Priority)
);
user_subs.set_multiple_library_priorities(
vec!["NIST".to_string(), "CEA".to_string()],
LibraryPriority::Permitted,
);
assert_eq!(
user_subs.library_priorities.get("NIST"),
Some(&LibraryPriority::Permitted)
);
assert_eq!(
user_subs.library_priorities.get("CEA"),
Some(&LibraryPriority::Permitted)
);
user_subs.set_library_priority("NIST".to_string(), LibraryPriority::Priority);
assert_eq!(
user_subs.library_priorities.get("NIST"),
Some(&LibraryPriority::Priority)
);
}
#[test]
fn test_typed_library_identifiers_and_aliases_are_canonicalized() {
let mut user_subs = SubsData::new();
user_subs.set_library_priority(
"Cantera_nasa_base_gas".to_string(),
LibraryPriority::Priority,
);
user_subs.set_library_priority("Aramco_transpot".to_string(), LibraryPriority::Permitted);
user_subs.set_library_priority_id(LibraryId::NasaCond, LibraryPriority::Priority);
user_subs.set_explicit_search_instruction("CO2".to_string(), LibraryId::AramcoTransport);
assert_eq!(
user_subs.library_priorities().get("NASA_gas"),
Some(&LibraryPriority::Priority)
);
assert_eq!(
user_subs.library_priorities().get("Aramco_transport"),
Some(&LibraryPriority::Permitted)
);
assert_eq!(
user_subs.library_priorities().get("NASA_cond"),
Some(&LibraryPriority::Priority)
);
assert_eq!(
user_subs
.explicit_search_instructions()
.get("CO2")
.map(String::as_str),
Some("Aramco_transport")
);
assert!(matches!(
user_subs
.create_calculator("Cantera_nasa_base_gas")
.unwrap(),
CalculatorType::Thermo(_)
));
assert!(matches!(
user_subs.create_calculator("Aramco_transpot").unwrap(),
CalculatorType::Transport(_)
));
}
#[test]
fn test_canonical_read_only_accessors_expose_the_clean_api() {
let mut user_subs = SubsData::new();
user_subs.set_explicit_search_instruction("H2O".to_string(), LibraryId::NasaGas);
user_subs
.molar_mass_by_substance
.insert("H2O".to_string(), 18.015);
assert_eq!(
user_subs
.explicit_search_map()
.get("H2O")
.map(String::as_str),
Some("NASA_gas")
);
assert_eq!(user_subs.molar_mass_map().get("H2O"), Some(&18.015));
assert_eq!(user_subs.molar_mass_of("H2O"), Some(18.015));
assert_eq!(user_subs.molar_mass_of("CO2"), None);
}
#[test]
fn test_deprecated_explicit_search_alias_forwards_to_canonical_method() {
let mut canonical = SubsData::new();
canonical.set_explicit_search_instruction("H2O".to_string(), LibraryId::NasaGas);
let mut legacy = SubsData::new();
legacy.set_explicit_search_instructions(HashMap::from([(
"H2O".to_string(),
"NASA_gas".to_string(),
)]));
assert_eq!(
canonical.explicit_search_map(),
legacy.explicit_search_map()
);
assert_eq!(
legacy.explicit_search_map().get("H2O").map(String::as_str),
Some("NASA_gas")
);
}
#[test]
fn test_property_kind_separates_identity_from_representation() {
assert_eq!(PropertyKind::Cp.numeric_type(), DataType::Cp);
assert_eq!(PropertyKind::Cp.function_type(), DataType::Cp_fun);
assert_eq!(PropertyKind::Cp.symbolic_type(), DataType::Cp_sym);
assert_eq!(PropertyKind::Lambda.numeric_type(), DataType::Lambda);
assert_eq!(
PropertyKind::from_data_type(DataType::Visc_sym),
Some(PropertyKind::Visc)
);
let subs_data = SubsData::new();
assert!(
subs_data
.property_value_state("H2O", PropertyKind::Cp)
.is_not_calculated()
);
assert!(
subs_data
.property_function_state("H2O", PropertyKind::Lambda)
.is_not_calculated()
);
assert!(
subs_data
.property_symbolic_state("H2O", PropertyKind::Visc)
.is_not_calculated()
);
}
#[test]
fn test_substance_search() {
let mut user_subs = SubsData::new();
user_subs.substances = vec![
"H2O".to_string(),
"CO2".to_string(),
"NonExistentSubstance".to_string(),
];
user_subs.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
user_subs.set_multiple_library_priorities(
vec!["Aramco_transport".to_string()],
LibraryPriority::Permitted,
);
let _ = user_subs.search_substances().unwrap();
assert!(matches!(
user_subs
.get_substance_result("H2O")
.unwrap()
.get(&WhatIsFound::Thermo)
.unwrap(),
Some(SearchResult { .. })
));
let _data_to_expect: HashMap<WhatIsFound, Option<SearchResult>> =
HashMap::from([(WhatIsFound::NotFound, None)]);
assert!(matches!(
user_subs.get_substance_result("NonExistentSubstance"),
Some(_data_to_expect)
));
let not_found = user_subs.get_not_found_substances();
assert!(not_found.contains(&"NonExistentSubstance".to_string()));
let priority_found = user_subs.get_priority_found_substances();
let permitted_found = user_subs.get_permitted_found_substances();
println!(
"Priority found: {:?}, Permitted found: {:?}",
priority_found, permitted_found
);
assert!(
priority_found.contains(&"H2O".to_string())
|| permitted_found.contains(&"H2O".to_string())
);
}
#[test]
fn test_thermo_property_calculation() {
let mut user_subs = SubsData::new();
let substances = vec!["O2".to_string(), "CO".to_string()];
user_subs.substances = substances.clone();
user_subs.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
user_subs
.map_of_phases
.insert("CO".to_string(), Some(Phases::Gas));
user_subs
.map_of_phases
.insert("O2".to_string(), Some(Phases::Gas));
let _ = user_subs.search_substances().unwrap();
user_subs
.extract_thermal_coeffs(substances[0].as_str(), 400.0)
.unwrap();
user_subs
.extract_thermal_coeffs(substances[1].as_str(), 400.0)
.unwrap();
if let Ok((cp, dh, ds)) = user_subs.calculate_thermo_properties("O2", 400.0) {
println!(
"O2 Thermo properties at 400K: Cp={}, dH={}, dS={}",
cp, dh, ds
);
assert!(cp > 0.0);
assert!(ds > 0.0);
} else {
panic!("Failed to calculate O2 properties");
}
if let Ok((cp, dh, ds)) = user_subs.calculate_thermo_properties("CO", 400.0) {
println!(
"CO Thermo properties at 400K: Cp={}, dH={}, dS={}",
cp, dh, ds
);
assert!(cp > 0.0);
assert!(ds > 0.0);
} else {
panic!("Failed to calculate CO properties");
}
}
#[test]
fn test_transport_property_calculation() {
let mut user_subs = SubsData::new();
user_subs.substances = vec!["H2O".to_string(), "CO".to_string()];
user_subs
.map_of_phases
.insert("CO".to_string(), Some(Phases::Gas));
user_subs
.map_of_phases
.insert("H2O".to_string(), Some(Phases::Gas));
user_subs.set_multiple_library_priorities(
vec!["Aramco_transport".to_string()],
LibraryPriority::Priority,
);
let _ = user_subs.search_substances().unwrap();
let _ = user_subs.set_M(
HashMap::from([("H2O".to_string(), 18.0), ("CO".to_string(), 32.0)]),
None,
);
let _ = user_subs.set_P(1e5, None);
user_subs.extract_transport_coeffs("H2O", 400.0).unwrap();
user_subs.extract_transport_coeffs("CO", 400.0).unwrap();
for substance in &["H2O", "CO"] {
if let Ok((lambda, viscosity)) =
user_subs.calculate_transport_properties(substance, 400.0, Some(33.0), None)
{
println!(
"{} Transport properties at 400K: Lambda={}, Viscosity={}",
substance, lambda, viscosity
);
assert!(lambda > 0.0);
assert!(viscosity > 0.0);
}
}
}
#[test]
fn test_property_maps_calculation() {
let mut user_subs = SubsData::new();
let substances = vec!["H2O".to_string(), "CO".to_string()];
user_subs
.map_of_phases
.insert("CO".to_string(), Some(Phases::Gas));
user_subs
.map_of_phases
.insert("H2O".to_string(), Some(Phases::Gas));
user_subs.substances = substances.clone();
user_subs.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
let _ = user_subs.search_substances().unwrap();
user_subs
.extract_thermal_coeffs(substances[0].as_str(), 400.0)
.unwrap();
user_subs
.extract_thermal_coeffs(substances[1].as_str(), 400.0)
.unwrap();
user_subs
.calculate_therm_map_of_properties(500.0)
.expect("Failed to calculate property map");
for substance in &["H2O", "CO"] {
let property_map = user_subs.therm_map_of_properties_values.get(*substance);
assert!(property_map.is_some());
let property_map = property_map.unwrap();
assert!(property_map.get(&DataType::Cp).unwrap().is_some());
assert!(property_map.get(&DataType::dH).unwrap().is_some());
assert!(property_map.get(&DataType::dS).unwrap().is_some());
assert!(property_map.get(&DataType::Cp).unwrap().unwrap() > 0.0);
}
}
#[test]
fn test_search_by_elements() {
let mut user_subs = SubsData::new();
user_subs.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
let elements = vec!["C".to_string(), "O".to_string()];
let result = user_subs.search_by_elements(elements);
assert!(result.is_ok());
let found_substances = result.unwrap();
assert!(!found_substances.is_empty());
for substance in &found_substances {
let search_result = user_subs.get_substance_result(substance);
assert!(search_result.is_some());
let result_map = search_result.unwrap();
assert!(result_map.contains_key(&WhatIsFound::Thermo));
let typed_state = user_subs.get_substance_search_state(substance).unwrap();
assert!(matches!(
typed_state.thermo,
crate::Thermodynamics::User_substances::PropertySearchState::Found(_)
));
}
}
#[test]
fn test_search_by_elements_only() {
let mut user_subs = SubsData::new();
user_subs.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
let elements = vec!["H".to_string(), "O".to_string()];
let result = user_subs.search_by_elements_only(elements);
assert!(result.is_ok());
let found_substances = result.unwrap();
let _ = user_subs.calculate_therm_map_of_properties(400.0);
if !found_substances.is_empty() {
for substance in &found_substances {
let search_result = user_subs.get_substance_result(substance);
assert!(search_result.is_some());
let result_map = search_result.unwrap();
assert!(result_map.contains_key(&WhatIsFound::Thermo));
let typed_state = user_subs.get_substance_search_state(substance).unwrap();
assert!(matches!(
typed_state.thermo,
crate::Thermodynamics::User_substances::PropertySearchState::Found(_)
));
let property_map = user_subs.therm_map_of_properties_values.get(substance);
assert!(property_map.is_some());
let property_map = property_map.unwrap();
assert!(property_map.get(&DataType::Cp).unwrap().is_some());
assert!(property_map.get(&DataType::dH).unwrap().is_some());
assert!(property_map.get(&DataType::dS).unwrap().is_some());
}
}
}
#[test]
fn test_function_maps_calculation() {
let mut user_subs = SubsData::new();
user_subs.substances = vec!["H2O".to_string()];
user_subs
.map_of_phases
.insert("H2O".to_string(), Some(Phases::Gas));
user_subs.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
let _ = user_subs.search_substances().unwrap();
user_subs.extract_thermal_coeffs("H2O", 400.0).unwrap();
user_subs
.calculate_therm_map_of_fun()
.expect("Failed to calculate function map");
let cp_fun = user_subs.get_thermo_function("H2O", DataType::Cp_fun);
assert!(cp_fun.is_some());
let cp_fun = cp_fun.unwrap();
let cp_300 = cp_fun(300.0);
let cp_400 = cp_fun(400.0);
let cp_500 = cp_fun(500.0);
println!(
"H2O Cp values: 300K={}, 400K={}, 500K={}",
cp_300, cp_400, cp_500
);
assert!(cp_300 > 0.0);
assert!(cp_400 > 0.0);
assert!(cp_500 > 0.0);
assert!(cp_300 != cp_400); }
#[test]
fn test_symbolic_maps_calculation() {
let mut user_subs = SubsData::new();
user_subs.substances = vec!["H2O".to_string()];
user_subs
.map_of_phases
.insert("H2O".to_string(), Some(Phases::Gas));
user_subs.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
let _ = user_subs.search_substances().unwrap();
user_subs.extract_thermal_coeffs("H2O", 400.0).unwrap();
user_subs
.calculate_therm_map_of_sym()
.expect("Failed to calculate symbolic map");
let cp_sym = user_subs.get_thermo_symbolic("H2O", DataType::Cp_sym);
assert!(cp_sym.is_some());
let cp_sym = cp_sym.unwrap();
println!("H2O Cp symbolic expression: {}", cp_sym.to_string());
assert!(!cp_sym.to_string().is_empty());
}
#[test]
fn test_integration_all_features() {
let mut user_subs = SubsData::new();
let substances = vec!["H2O".to_string(), "CO".to_string(), "CH4".to_string()];
user_subs
.map_of_phases
.insert("H2O".to_string(), Some(Phases::Gas));
user_subs
.map_of_phases
.insert("CO".to_string(), Some(Phases::Gas));
user_subs
.map_of_phases
.insert("CH4".to_string(), Some(Phases::Gas));
user_subs.substances = substances.clone();
user_subs.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
user_subs.set_multiple_library_priorities(
vec!["NIST".to_string(), "Aramco_transport".to_string()],
LibraryPriority::Permitted,
);
let _ = user_subs.search_substances().unwrap();
user_subs
.extract_thermal_coeffs(substances[0].as_str(), 400.0)
.unwrap();
user_subs
.extract_thermal_coeffs(substances[1].as_str(), 400.0)
.unwrap();
user_subs
.extract_thermal_coeffs(substances[2].as_str(), 400.0)
.unwrap();
user_subs
.calculate_therm_map_of_properties(400.0)
.expect("Failed to calculate property map");
user_subs
.calculate_therm_map_of_fun()
.expect("Failed to calculate function map");
user_subs
.calculate_therm_map_of_sym()
.expect("Failed to calculate symbolic map");
user_subs.print_search_summary();
for substance in &["H2O", "CO", "CH4"] {
let substance = *substance;
let result = user_subs.get_substance_result(substance);
assert!(result.is_some());
if let Some(_map) = result {
let property_map = user_subs.therm_map_of_properties_values.get(substance);
assert!(property_map.is_some());
assert!(
user_subs
.therm_function_state(substance, DataType::Cp_fun)
.is_ready()
);
assert!(
user_subs
.therm_symbolic_state(substance, DataType::Cp_sym)
.is_ready()
);
}
}
}
}
#[cfg(test)]
mod tests2 {
use crate::Thermodynamics::User_substances::{
CalculatorType, DataType, DerivedValueState, LibraryPriority, Phases, PropertySearchState,
SearchResult, SubsData, WhatIsFound,
};
use crate::Thermodynamics::thermo_lib_api::ThermoData;
use approx::assert_relative_eq;
use core::panic;
use std::collections::HashMap;
fn create_test_subsdata() -> SubsData {
let mut user_subs = SubsData::new();
user_subs.substances = vec!["H2O".to_string(), "CO2".to_string(), "CH4".to_string()];
user_subs
.map_of_phases
.insert("H2O".to_string(), Some(Phases::Gas));
user_subs
.map_of_phases
.insert("CO2".to_string(), Some(Phases::Gas));
user_subs
.map_of_phases
.insert("CH4".to_string(), Some(Phases::Gas));
user_subs.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
user_subs.set_library_priority("NUIG_thermo".to_string(), LibraryPriority::Permitted);
let _ = user_subs.set_P(1.0, Some("atm".to_string()));
let molar_masses = HashMap::from([
("H2O".to_string(), 18.01528),
("CO2".to_string(), 44.01),
("CH4".to_string(), 16.04),
]);
let _ = user_subs.set_M(molar_masses, Some("g/mol".to_string()));
user_subs
}
#[test]
fn test_new() {
let user_subs = SubsData::new();
assert!(user_subs.substances.is_empty());
assert!(user_subs.library_priorities.is_empty());
assert!(user_subs.search_states.is_empty());
}
#[test]
fn test_search_state_view_tracks_found_and_missing() {
let mut user_subs = SubsData::new();
user_subs.store_search_result(
"H2O",
WhatIsFound::Thermo,
"NASA_gas".to_string(),
LibraryPriority::Priority,
serde_json::Value::String("payload".to_string()),
CalculatorType::Thermo(
crate::Thermodynamics::DBhandlers::thermo_api::ThermoEnum::NASA(
crate::Thermodynamics::DBhandlers::NASAdata::NASAdata::new(),
),
),
false,
);
let typed_state = user_subs.get_substance_search_state("H2O").unwrap();
assert!(matches!(typed_state.thermo, PropertySearchState::Found(_)));
assert!(matches!(
typed_state.transport,
PropertySearchState::NotSearched
));
let compat = user_subs.get_substance_result("H2O").unwrap();
assert!(compat.get(&WhatIsFound::Thermo).is_some());
user_subs.insert_not_found_if_absent("CO2");
let missing_state = user_subs.get_substance_search_state("CO2").unwrap();
assert!(matches!(missing_state.thermo, PropertySearchState::Missing));
assert!(matches!(
missing_state.transport,
PropertySearchState::Missing
));
}
#[test]
fn test_search_state_based_getters_ignore_stale_compat_view() {
let mut user_subs = SubsData::new();
user_subs.store_search_result(
"N2",
WhatIsFound::Thermo,
"NASA_gas".to_string(),
LibraryPriority::Priority,
serde_json::Value::String("payload".to_string()),
CalculatorType::Thermo(
crate::Thermodynamics::DBhandlers::thermo_api::ThermoEnum::NASA(
crate::Thermodynamics::DBhandlers::NASAdata::NASAdata::new(),
),
),
false,
);
user_subs.store_search_result(
"Ar",
WhatIsFound::Transport,
"Aramco_transport".to_string(),
LibraryPriority::Permitted,
serde_json::Value::String("payload".to_string()),
CalculatorType::Transport(
crate::Thermodynamics::DBhandlers::transport_api::TransportEnum::Collision(
crate::Thermodynamics::DBhandlers::TRANSPORTdata::TransportData::new(),
),
),
false,
);
user_subs.search_results.clear();
let compat = user_subs.get_substance_result("N2").unwrap();
assert!(compat.contains_key(&WhatIsFound::Thermo));
let priority_found = user_subs.get_priority_found_substances();
assert_eq!(priority_found, vec!["N2".to_string()]);
let permitted_found = user_subs.get_permitted_found_substances();
assert_eq!(permitted_found, vec!["Ar".to_string()]);
let not_found = user_subs.get_not_found_substances();
assert!(not_found.is_empty());
}
#[test]
fn test_set_substances_invalidates_dependent_state() {
let mut user_subs = create_test_subsdata();
user_subs.store_search_result(
"H2O",
WhatIsFound::Thermo,
"NASA_gas".to_string(),
LibraryPriority::Priority,
serde_json::Value::Null,
CalculatorType::Thermo(
crate::Thermodynamics::DBhandlers::thermo_api::ThermoEnum::NASA(
crate::Thermodynamics::DBhandlers::NASAdata::NASAdata::new(),
),
),
false,
);
user_subs.therm_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Cp, Some(42.0))]),
);
user_subs
.therm_map_of_fun
.insert("H2O".to_string(), HashMap::from([(DataType::Cp_fun, None)]));
user_subs
.therm_map_of_sym
.insert("H2O".to_string(), HashMap::from([(DataType::Cp_sym, None)]));
user_subs.transport_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda, Some(0.5))]),
);
user_subs.transport_map_of_fun.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda_fun, None)]),
);
user_subs.transport_map_of_sym.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda_sym, None)]),
);
user_subs.elem_composition_matrix = Some(nalgebra::DMatrix::from_element(1, 1, 1.0));
user_subs
.molar_mass_by_substance
.insert("H2O".to_string(), 18.0);
user_subs.diffusion_data = Some(
crate::Thermodynamics::DBhandlers::Diffusion::MultiSubstanceDiffusion::new(
300.0, 101325.0,
)
.expect("test diffusion state should be constructible"),
);
user_subs.unique_elements = vec!["H".to_string(), "O".to_string()];
user_subs.ro_map = Some(HashMap::from([("H2O".to_string(), 1.0)]));
user_subs
.map_of_phases
.insert("H2O".to_string(), Some(Phases::Gas));
user_subs.set_substances(vec!["CO2".to_string(), "CO".to_string()]);
assert!(user_subs.search_states.is_empty());
assert!(user_subs.therm_map_of_properties_values.is_empty());
assert!(user_subs.therm_map_of_fun.is_empty());
assert!(user_subs.therm_map_of_sym.is_empty());
assert!(user_subs.transport_map_of_properties_values.is_empty());
assert!(user_subs.transport_map_of_fun.is_empty());
assert!(user_subs.transport_map_of_sym.is_empty());
assert!(user_subs.elem_composition_matrix.is_none());
assert!(user_subs.molar_mass_by_substance.is_empty());
assert!(user_subs.diffusion_data.is_none());
assert!(user_subs.unique_elements.is_empty());
assert!(user_subs.ro_map.is_none());
assert!(user_subs.map_of_phases.is_empty());
assert_eq!(
user_subs.substances,
vec!["CO2".to_string(), "CO".to_string()]
);
}
#[test]
fn test_search_substances_rolls_back_on_failure() {
let mut user_subs = create_test_subsdata();
std::sync::Arc::make_mut(&mut user_subs.thermo_data.LibThermoData).insert(
"bad_lib".to_string(),
HashMap::from([("CH4".to_string(), serde_json::Value::Null)]),
);
let _ = user_subs.set_explicit_search_instructions(HashMap::from([(
"CH4".to_string(),
"bad_lib".to_string(),
)]));
user_subs.store_search_result(
"H2O",
WhatIsFound::Thermo,
"sentinel_library".to_string(),
LibraryPriority::Priority,
serde_json::Value::String("sentinel".to_string()),
CalculatorType::Thermo(
crate::Thermodynamics::DBhandlers::thermo_api::ThermoEnum::NASA(
crate::Thermodynamics::DBhandlers::NASAdata::NASAdata::new(),
),
),
false,
);
let err = user_subs.search_substances().unwrap_err();
assert!(
err.to_string().contains("bad_lib")
|| err.to_string().to_lowercase().contains("library")
);
let h2o_state = user_subs.get_substance_search_state("H2O").unwrap();
let thermo = h2o_state.result(WhatIsFound::Thermo).unwrap();
assert_eq!(thermo.library(), "sentinel_library");
let direct_thermo = user_subs
.get_search_result("H2O", WhatIsFound::Thermo)
.unwrap();
assert_eq!(direct_thermo.library(), "sentinel_library");
let h2o = user_subs.get_substance_result("H2O").unwrap();
let thermo = h2o.get(&WhatIsFound::Thermo).unwrap().as_ref().unwrap();
assert_eq!(thermo.library(), "sentinel_library");
assert_eq!(
thermo.data(),
&serde_json::Value::String("sentinel".to_string())
);
assert!(user_subs.get_substance_search_state("CO2").is_none());
assert!(user_subs.get_substance_search_state("CH4").is_none());
}
#[test]
fn test_lookup_mutations_invalidate_search_and_derived_state() {
let mut user_subs = create_test_subsdata();
user_subs.store_search_result(
"H2O",
WhatIsFound::Thermo,
"NASA_gas".to_string(),
LibraryPriority::Priority,
serde_json::Value::Null,
CalculatorType::Thermo(
crate::Thermodynamics::DBhandlers::thermo_api::ThermoEnum::NASA(
crate::Thermodynamics::DBhandlers::NASAdata::NASAdata::new(),
),
),
false,
);
user_subs.therm_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Cp, Some(42.0))]),
);
user_subs.transport_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda, Some(0.5))]),
);
user_subs
.therm_map_of_fun
.insert("H2O".to_string(), HashMap::from([(DataType::Cp_fun, None)]));
user_subs.transport_map_of_fun.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda_fun, None)]),
);
user_subs
.therm_map_of_sym
.insert("H2O".to_string(), HashMap::from([(DataType::Cp_sym, None)]));
user_subs.transport_map_of_sym.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda_sym, None)]),
);
user_subs.elem_composition_matrix = Some(nalgebra::DMatrix::from_element(1, 1, 1.0));
user_subs
.molar_mass_by_substance
.insert("H2O".to_string(), 18.0);
user_subs.unique_elements = vec!["H".to_string(), "O".to_string()];
user_subs.ro_map = Some(HashMap::from([("H2O".to_string(), 1.0)]));
user_subs.ro_map_sym = Some(HashMap::from([(
"H2O".to_string(),
Box::new(RustedSciThe::symbolic::symbolic_engine::Expr::Const(1.0)),
)]));
user_subs.diffusion_data = Some(
crate::Thermodynamics::DBhandlers::Diffusion::MultiSubstanceDiffusion::new(
300.0, 101325.0,
)
.expect("test diffusion state should be constructible"),
);
user_subs.set_library_priority("NASA_gas".to_string(), LibraryPriority::Permitted);
assert!(user_subs.search_states.is_empty());
assert!(user_subs.therm_map_of_properties_values.is_empty());
assert!(user_subs.transport_map_of_properties_values.is_empty());
assert!(user_subs.therm_map_of_fun.is_empty());
assert!(user_subs.transport_map_of_fun.is_empty());
assert!(user_subs.therm_map_of_sym.is_empty());
assert!(user_subs.transport_map_of_sym.is_empty());
assert!(user_subs.elem_composition_matrix.is_none());
assert!(user_subs.molar_mass_by_substance.is_empty());
assert!(user_subs.unique_elements.is_empty());
assert!(user_subs.ro_map.is_none());
assert!(user_subs.ro_map_sym.is_none());
assert!(user_subs.diffusion_data.is_none());
user_subs.store_search_result(
"CO",
WhatIsFound::Transport,
"Aramco_transport".to_string(),
LibraryPriority::Permitted,
serde_json::Value::Null,
CalculatorType::Transport(
crate::Thermodynamics::DBhandlers::transport_api::TransportEnum::Collision(
crate::Thermodynamics::DBhandlers::TRANSPORTdata::TransportData::new(),
),
),
false,
);
user_subs.transport_map_of_properties_values.insert(
"CO".to_string(),
HashMap::from([(DataType::Lambda, Some(0.2))]),
);
let _ = user_subs.set_explicit_search_instructions(HashMap::from([(
"CO".to_string(),
"NASA_gas".to_string(),
)]));
assert!(user_subs.search_states.is_empty());
assert!(user_subs.transport_map_of_properties_values.is_empty());
assert!(user_subs.transport_map_of_fun.is_empty());
assert!(user_subs.transport_map_of_sym.is_empty());
}
#[test]
fn test_temperature_pressure_and_molar_mass_setters_invalidate_only_dependent_state() {
let mut user_subs = create_test_subsdata();
user_subs.store_search_result(
"H2O",
WhatIsFound::Thermo,
"NASA_gas".to_string(),
LibraryPriority::Priority,
serde_json::Value::Null,
CalculatorType::Thermo(
crate::Thermodynamics::DBhandlers::thermo_api::ThermoEnum::NASA(
crate::Thermodynamics::DBhandlers::NASAdata::NASAdata::new(),
),
),
false,
);
user_subs.therm_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Cp, Some(42.0))]),
);
user_subs.transport_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda, Some(0.5))]),
);
user_subs.transport_map_of_fun.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda_fun, None)]),
);
user_subs.transport_map_of_sym.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda_sym, None)]),
);
user_subs.ro_map = Some(HashMap::from([("H2O".to_string(), 1.0)]));
user_subs.ro_map_sym = Some(HashMap::from([(
"H2O".to_string(),
Box::new(RustedSciThe::symbolic::symbolic_engine::Expr::Const(1.0)),
)]));
user_subs.diffusion_data = Some(
crate::Thermodynamics::DBhandlers::Diffusion::MultiSubstanceDiffusion::new(
300.0, 101325.0,
)
.expect("test diffusion state should be constructible"),
);
user_subs.set_T(500.0).unwrap();
assert!(user_subs.get_substance_search_state("H2O").is_some());
assert!(user_subs.therm_map_of_properties_values.is_empty());
assert!(user_subs.transport_map_of_properties_values.is_empty());
assert!(user_subs.transport_map_of_fun.is_empty());
assert!(user_subs.transport_map_of_sym.is_empty());
assert!(user_subs.ro_map.is_none());
assert!(user_subs.ro_map_sym.is_none());
assert!(user_subs.diffusion_data.is_none());
user_subs.transport_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda, Some(0.5))]),
);
user_subs.transport_map_of_fun.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda_fun, None)]),
);
user_subs.transport_map_of_sym.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda_sym, None)]),
);
user_subs.ro_map = Some(HashMap::from([("H2O".to_string(), 1.0)]));
user_subs.ro_map_sym = Some(HashMap::from([(
"H2O".to_string(),
Box::new(RustedSciThe::symbolic::symbolic_engine::Expr::Const(1.0)),
)]));
user_subs.diffusion_data = Some(
crate::Thermodynamics::DBhandlers::Diffusion::MultiSubstanceDiffusion::new(
300.0, 101325.0,
)
.expect("test diffusion state should be constructible"),
);
user_subs.set_P(2.0e5, None).unwrap();
assert!(user_subs.get_substance_search_state("H2O").is_some());
assert!(user_subs.transport_map_of_properties_values.is_empty());
assert!(user_subs.transport_map_of_fun.is_empty());
assert!(user_subs.transport_map_of_sym.is_empty());
assert!(user_subs.ro_map.is_none());
assert!(user_subs.ro_map_sym.is_none());
assert!(user_subs.diffusion_data.is_none());
let mut molar_masses = HashMap::new();
molar_masses.insert("H2O".to_string(), 18.01528);
user_subs
.set_M(molar_masses, Some("g/mol".to_string()))
.unwrap();
assert!(user_subs.get_substance_search_state("H2O").is_some());
assert!(user_subs.transport_map_of_properties_values.is_empty());
assert!(user_subs.transport_map_of_fun.is_empty());
assert!(user_subs.transport_map_of_sym.is_empty());
assert!(user_subs.ro_map.is_none());
assert!(user_subs.ro_map_sym.is_none());
assert!(user_subs.diffusion_data.is_none());
}
#[test]
fn test_invalidation_turns_calculated_entries_back_into_not_calculated_states() {
let mut user_subs = SubsData::new();
user_subs.therm_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Cp, Some(123.4)), (DataType::dH, Some(55.0))]),
);
user_subs.therm_map_of_fun.insert(
"H2O".to_string(),
HashMap::from([(
DataType::Cp_fun,
Some(Box::new(|t: f64| t + 1.0) as Box<dyn Fn(f64) -> f64 + Send + Sync>),
)]),
);
user_subs.therm_map_of_sym.insert(
"H2O".to_string(),
HashMap::from([(
DataType::Cp_sym,
Some(Box::new(
RustedSciThe::symbolic::symbolic_engine::Expr::Const(1.0),
)),
)]),
);
user_subs.transport_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda, Some(0.56))]),
);
user_subs.transport_map_of_fun.insert(
"H2O".to_string(),
HashMap::from([(
DataType::Lambda_fun,
Some(Box::new(|t: f64| t * 2.0) as Box<dyn Fn(f64) -> f64 + Send + Sync>),
)]),
);
user_subs.transport_map_of_sym.insert(
"H2O".to_string(),
HashMap::from([(
DataType::Lambda_sym,
Some(Box::new(
RustedSciThe::symbolic::symbolic_engine::Expr::Const(2.0),
)),
)]),
);
assert!(user_subs.therm_value_state("H2O", DataType::Cp).is_ready());
assert!(
user_subs
.therm_function_state("H2O", DataType::Cp_fun)
.is_ready()
);
assert!(
user_subs
.transport_value_state("H2O", DataType::Lambda)
.is_ready()
);
assert!(
user_subs
.transport_function_state("H2O", DataType::Lambda_fun)
.is_ready()
);
assert!(
user_subs
.transport_symbolic_state("H2O", DataType::Lambda_sym)
.is_ready()
);
user_subs.set_T(450.0).unwrap();
assert!(
user_subs
.therm_value_state("H2O", DataType::Cp)
.is_not_calculated()
);
assert!(
user_subs
.therm_value_state("H2O", DataType::dH)
.is_not_calculated()
);
assert!(
user_subs
.transport_value_state("H2O", DataType::Lambda)
.is_not_calculated()
);
assert!(
user_subs
.transport_function_state("H2O", DataType::Lambda_fun)
.is_not_calculated()
);
assert!(
user_subs
.transport_symbolic_state("H2O", DataType::Lambda_sym)
.is_not_calculated()
);
assert!(
user_subs
.therm_function_state("H2O", DataType::Cp_fun)
.is_ready()
);
assert!(
user_subs
.therm_symbolic_state("H2O", DataType::Cp_sym)
.is_ready()
);
}
#[test]
fn test_mutation_path_invariant_covers_current_input_setters() {
{
let mut user_subs = create_test_subsdata();
user_subs.store_search_result(
"H2O",
WhatIsFound::Thermo,
"NASA_gas".to_string(),
LibraryPriority::Priority,
serde_json::Value::Null,
CalculatorType::Thermo(
crate::Thermodynamics::DBhandlers::thermo_api::ThermoEnum::NASA(
crate::Thermodynamics::DBhandlers::NASAdata::NASAdata::new(),
),
),
false,
);
user_subs.therm_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Cp, Some(42.0))]),
);
user_subs.set_substances(vec!["CO2".to_string()]);
assert!(user_subs.search_states.is_empty());
assert!(user_subs.therm_map_of_properties_values.is_empty());
assert!(user_subs.transport_map_of_properties_values.is_empty());
assert!(user_subs.elem_composition_matrix.is_none());
assert!(user_subs.molar_mass_by_substance.is_empty());
assert!(user_subs.diffusion_data.is_none());
}
{
let mut user_subs = create_test_subsdata();
user_subs.store_search_result(
"H2O",
WhatIsFound::Thermo,
"NASA_gas".to_string(),
LibraryPriority::Priority,
serde_json::Value::Null,
CalculatorType::Thermo(
crate::Thermodynamics::DBhandlers::thermo_api::ThermoEnum::NASA(
crate::Thermodynamics::DBhandlers::NASAdata::NASAdata::new(),
),
),
false,
);
user_subs.therm_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Cp, Some(42.0))]),
);
user_subs.set_library_priority("NASA_gas".to_string(), LibraryPriority::Permitted);
assert!(user_subs.search_states.is_empty());
assert!(user_subs.therm_map_of_properties_values.is_empty());
user_subs.store_search_result(
"CO",
WhatIsFound::Transport,
"Aramco_transport".to_string(),
LibraryPriority::Permitted,
serde_json::Value::Null,
CalculatorType::Transport(
crate::Thermodynamics::DBhandlers::transport_api::TransportEnum::Collision(
crate::Thermodynamics::DBhandlers::TRANSPORTdata::TransportData::new(),
),
),
false,
);
user_subs.set_multiple_library_priorities(
vec!["NASA_gas".to_string(), "Aramco_transport".to_string()],
LibraryPriority::Priority,
);
assert!(user_subs.search_states.is_empty());
user_subs.store_search_result(
"CO",
WhatIsFound::Transport,
"Aramco_transport".to_string(),
LibraryPriority::Permitted,
serde_json::Value::Null,
CalculatorType::Transport(
crate::Thermodynamics::DBhandlers::transport_api::TransportEnum::Collision(
crate::Thermodynamics::DBhandlers::TRANSPORTdata::TransportData::new(),
),
),
false,
);
let _ = user_subs.set_explicit_search_instructions(HashMap::from([(
"CO".to_string(),
"NASA_gas".to_string(),
)]));
assert!(user_subs.search_states.is_empty());
}
{
let mut user_subs = create_test_subsdata();
user_subs.store_search_result(
"H2O",
WhatIsFound::Thermo,
"NASA_gas".to_string(),
LibraryPriority::Priority,
serde_json::Value::Null,
CalculatorType::Thermo(
crate::Thermodynamics::DBhandlers::thermo_api::ThermoEnum::NASA(
crate::Thermodynamics::DBhandlers::NASAdata::NASAdata::new(),
),
),
false,
);
user_subs.therm_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Cp, Some(42.0))]),
);
user_subs.transport_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda, Some(0.5))]),
);
user_subs.ro_map = Some(HashMap::from([("H2O".to_string(), 1.0)]));
user_subs.diffusion_data = Some(
crate::Thermodynamics::DBhandlers::Diffusion::MultiSubstanceDiffusion::new(
300.0, 101325.0,
)
.expect("test diffusion state should be constructible"),
);
user_subs.set_T(500.0).unwrap();
assert!(user_subs.get_substance_search_state("H2O").is_some());
assert!(user_subs.therm_map_of_properties_values.is_empty());
assert!(user_subs.transport_map_of_properties_values.is_empty());
assert!(user_subs.ro_map.is_none());
assert!(user_subs.diffusion_data.is_none());
}
{
let mut user_subs = create_test_subsdata();
user_subs.store_search_result(
"H2O",
WhatIsFound::Transport,
"Aramco_transport".to_string(),
LibraryPriority::Permitted,
serde_json::Value::Null,
CalculatorType::Transport(
crate::Thermodynamics::DBhandlers::transport_api::TransportEnum::Collision(
crate::Thermodynamics::DBhandlers::TRANSPORTdata::TransportData::new(),
),
),
false,
);
user_subs.transport_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda, Some(0.5))]),
);
user_subs.transport_map_of_fun.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda_fun, None)]),
);
user_subs.ro_map = Some(HashMap::from([("H2O".to_string(), 1.0)]));
user_subs.diffusion_data = Some(
crate::Thermodynamics::DBhandlers::Diffusion::MultiSubstanceDiffusion::new(
300.0, 101325.0,
)
.expect("test diffusion state should be constructible"),
);
user_subs.set_P(2.0e5, Some("Pa".to_string())).unwrap();
assert!(user_subs.get_substance_search_state("H2O").is_some());
assert_eq!(user_subs.P, Some(2.0e5));
assert_eq!(user_subs.P_unit.as_deref(), Some("Pa"));
assert!(user_subs.transport_map_of_properties_values.is_empty());
assert!(user_subs.transport_map_of_fun.is_empty());
assert!(user_subs.ro_map.is_none());
assert!(user_subs.diffusion_data.is_none());
let mut molar_masses = HashMap::new();
molar_masses.insert("H2O".to_string(), 18.01528);
user_subs
.set_M(molar_masses, Some("g/mol".to_string()))
.unwrap();
assert!(user_subs.get_substance_search_state("H2O").is_some());
assert_eq!(user_subs.Molar_mass_unit.as_deref(), Some("g/mol"));
assert!(user_subs.transport_map_of_properties_values.is_empty());
assert!(user_subs.transport_map_of_fun.is_empty());
assert!(user_subs.ro_map.is_none());
assert!(user_subs.diffusion_data.is_none());
}
}
#[test]
fn test_set_library_priority() {
let mut user_subs = SubsData::new();
user_subs.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
assert_eq!(user_subs.library_priorities.len(), 1);
assert_eq!(
*user_subs.library_priorities.get("NASA_gas").unwrap(),
LibraryPriority::Priority
);
user_subs.set_library_priority("NASA_gas".to_string(), LibraryPriority::Permitted);
assert_eq!(
*user_subs.library_priorities.get("NASA_gas").unwrap(),
LibraryPriority::Permitted
);
}
#[test]
fn test_set_multiple_library_priorities() {
let mut user_subs = SubsData::new();
let libraries = vec![
"NASA_gas".to_string(),
"NUIG_thermo".to_string(),
"Cantera_nasa_base_gas".to_string(),
];
user_subs.set_multiple_library_priorities(libraries.clone(), LibraryPriority::Priority);
for lib in libraries {
let canonical = ThermoData::canonical_library_name(&lib);
assert_eq!(
*user_subs.library_priorities.get(&canonical).unwrap(),
LibraryPriority::Priority
);
}
}
#[test]
fn test_search_substances() {
let mut user_subs = create_test_subsdata();
let _ = user_subs.search_substances().unwrap();
assert!(!user_subs.search_states.is_empty());
let not_found = user_subs.get_not_found_substances();
assert!(not_found.len() < user_subs.substances.len());
}
#[test]
fn test_explicit_search_keeps_explicit_provenance() {
let mut user_subs = create_test_subsdata();
let _ = user_subs.set_explicit_search_instructions(HashMap::from([(
"H2O".to_string(),
"NASA_gas".to_string(),
)]));
let _ = user_subs.search_substances().unwrap();
let result_map = user_subs
.get_substance_result("H2O")
.expect("H2O should be present after explicit lookup");
let explicit = result_map
.get(&WhatIsFound::Thermo)
.and_then(|result| result.as_ref())
.expect("Thermo result should exist");
assert_eq!(explicit.priority_type(), LibraryPriority::Explicit);
assert_eq!(explicit.library(), "NASA_gas");
}
#[test]
fn test_same_priority_libraries_resolve_in_documented_order() {
let mut user_subs = create_test_subsdata();
user_subs.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
user_subs.set_library_priority("NUIG_thermo".to_string(), LibraryPriority::Priority);
user_subs.search_substances().unwrap();
let result_map = user_subs
.get_substance_result("H2O")
.expect("H2O should be present after the search");
let thermo = result_map
.get(&WhatIsFound::Thermo)
.and_then(|result| result.as_ref())
.expect("Thermo result should exist for H2O");
assert_eq!(thermo.priority_type(), LibraryPriority::Priority);
assert_eq!(thermo.library(), "NASA_gas");
}
#[test]
fn test_search_substances_keeps_thermo_and_transport_separate() {
let mut user_subs = SubsData::new();
user_subs.substances = vec!["CO".to_string()];
user_subs
.map_of_phases
.insert("CO".to_string(), Some(Phases::Gas));
user_subs.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
user_subs.set_library_priority("Aramco_transport".to_string(), LibraryPriority::Permitted);
let _ = user_subs.search_substances().unwrap();
let result_map = user_subs
.get_substance_result("CO")
.expect("CO should be present after lookup");
let thermo = result_map
.get(&WhatIsFound::Thermo)
.and_then(|result| result.as_ref())
.expect("Thermo result should exist");
let transport = result_map
.get(&WhatIsFound::Transport)
.and_then(|result| result.as_ref())
.expect("Transport result should exist");
assert_eq!(thermo.priority_type(), LibraryPriority::Priority);
assert_eq!(transport.priority_type(), LibraryPriority::Permitted);
}
#[test]
fn test_get_substance_result() {
let mut user_subs = create_test_subsdata();
let _ = user_subs.search_substances().unwrap();
let result = user_subs.get_substance_result("H2O");
assert!(result.is_some());
assert!(
user_subs
.get_substance_search_state("H2O")
.is_some_and(|state| state.thermo().is_resolved())
);
}
#[test]
fn test_read_only_getters_expose_canonical_state() {
let mut user_subs = SubsData::new();
user_subs.set_substances(vec!["H2O".to_string(), "CO2".to_string()]);
user_subs.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
let _ = user_subs.set_explicit_search_instructions(HashMap::from([(
"CO2".to_string(),
"NASA_gas".to_string(),
)]));
assert_eq!(
user_subs.substances(),
&["H2O".to_string(), "CO2".to_string()]
);
assert_eq!(
user_subs.library_priorities().get("NASA_gas"),
Some(&LibraryPriority::Priority)
);
assert_eq!(
user_subs.explicit_search_instructions().get("CO2"),
Some(&"NASA_gas".to_string())
);
assert!(user_subs.temperature().is_none());
assert!(user_subs.pressure().is_none());
assert!(user_subs.phases().is_empty());
assert!(user_subs.search_states().is_empty());
assert!(user_subs.therm_values().is_empty());
assert!(user_subs.transport_values().is_empty());
assert!(user_subs.molar_masses().is_empty());
assert!(user_subs.unique_elements().is_empty());
assert!(user_subs.element_composition_matrix().is_none());
assert!(user_subs.diffusion_data().is_none());
}
#[test]
fn test_property_search_state_helpers_distinguish_pending_and_resolved() {
let pending = PropertySearchState::NotSearched;
assert!(pending.is_pending());
assert!(!pending.is_resolved());
assert!(!pending.is_missing());
assert!(!pending.is_failed());
let resolved = PropertySearchState::Found(SearchResult {
library: "sentinel_library".to_string(),
record_key: "sentinel_record".to_string(),
priority_type: LibraryPriority::Priority,
data: serde_json::Value::Null,
calculator: None,
});
assert!(!resolved.is_pending());
assert!(resolved.is_resolved());
assert!(!resolved.is_missing());
assert!(!resolved.is_failed());
let missing = PropertySearchState::Missing;
assert!(missing.is_missing());
assert!(!missing.is_pending());
let failed = PropertySearchState::Failed("boom".to_string());
assert!(failed.is_failed());
assert!(!failed.is_resolved());
}
#[test]
fn test_derived_value_state_reports_not_calculated_and_ready() {
let mut user_subs = SubsData::new();
assert!(
user_subs
.therm_value_state("H2O", DataType::Cp)
.is_not_calculated()
);
assert!(
user_subs
.transport_value_state("H2O", DataType::Lambda)
.is_not_calculated()
);
user_subs.therm_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Cp, Some(123.4))]),
);
user_subs.transport_map_of_properties_values.insert(
"H2O".to_string(),
HashMap::from([(DataType::Lambda, Some(0.56))]),
);
let therm_state = user_subs.therm_value_state("H2O", DataType::Cp);
assert!(therm_state.is_ready());
assert_eq!(therm_state.value(), Some(&123.4));
let transport_state = user_subs.transport_value_state("H2O", DataType::Lambda);
assert!(transport_state.is_ready());
assert_eq!(transport_state.value(), Some(&0.56));
}
#[test]
fn test_derived_function_and_symbolic_state_reports_not_calculated_and_ready() {
let mut user_subs = SubsData::new();
assert!(
user_subs
.therm_function_state("H2O", DataType::Cp_fun)
.is_not_calculated()
);
assert!(
user_subs
.therm_symbolic_state("H2O", DataType::Cp_sym)
.is_not_calculated()
);
user_subs.therm_map_of_fun.insert(
"H2O".to_string(),
HashMap::from([(
DataType::Cp_fun,
Some(Box::new(|t: f64| t + 1.0) as Box<dyn Fn(f64) -> f64 + Send + Sync>),
)]),
);
user_subs.therm_map_of_sym.insert(
"H2O".to_string(),
HashMap::from([(
DataType::Cp_sym,
Some(Box::new(
RustedSciThe::symbolic::symbolic_engine::Expr::Const(1.0),
)),
)]),
);
let fun_state = user_subs.therm_function_state("H2O", DataType::Cp_fun);
assert!(fun_state.is_ready());
assert_eq!(fun_state.value().map(|f| f(2.0)), Some(3.0));
let sym_state = user_subs.therm_symbolic_state("H2O", DataType::Cp_sym);
assert!(sym_state.is_ready());
assert!(matches!(sym_state.value(), Some(expr) if expr.to_string().contains('1')));
}
#[test]
fn test_get_not_found_substances() {
let mut user_subs = SubsData::new();
user_subs.substances = vec!["NonExistentSubstance123".to_string()];
user_subs.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
let _ = user_subs.search_substances().unwrap();
let not_found = user_subs.get_not_found_substances();
assert_eq!(not_found.len(), 1);
assert_eq!(not_found[0], "NonExistentSubstance123");
}
#[test]
fn test_extract_thermal_coeffs() {
let mut user_subs = create_test_subsdata();
let _ = user_subs.search_substances().unwrap();
let found_substances: Vec<String> = user_subs
.get_all_search_states()
.keys()
.filter(|&s| {
user_subs
.get_substance_search_state(s)
.is_some_and(|state| state.thermo().is_resolved())
})
.cloned()
.collect();
if !found_substances.is_empty() {
let substance = &found_substances[0];
let result = user_subs.extract_thermal_coeffs(substance, 298.15);
assert!(result.is_ok());
}
}
#[test]
fn test_calculate_thermo_properties() {
let mut user_subs = create_test_subsdata();
let _ = user_subs.search_substances().unwrap();
let found_substances: Vec<String> = user_subs
.get_all_search_states()
.keys()
.filter(|&s| {
user_subs
.get_substance_search_state(s)
.is_some_and(|state| state.thermo().is_resolved())
})
.cloned()
.collect();
if !found_substances.is_empty() {
let substance = &found_substances[0];
let _ = user_subs.extract_thermal_coeffs(substance, 298.15);
let result = user_subs.calculate_thermo_properties(substance, 298.15);
assert!(result.is_ok());
let (cp, _dh, _ds) = result.unwrap();
assert!(cp > 0.0); }
}
#[test]
fn test_calculate_therm_map_of_properties() {
let mut user_subs = create_test_subsdata();
let _ = user_subs.search_substances().unwrap();
let _ = user_subs.extract_all_thermal_coeffs(298.15);
let result = user_subs.calculate_therm_map_of_properties(298.15);
assert!(result.is_ok());
assert!(!user_subs.therm_map_of_properties_values.is_empty());
for substance in user_subs.get_priority_found_substances() {
let cp_state = user_subs.therm_value_state(&substance, DataType::Cp);
assert!(cp_state.is_ready(), "Cp should be ready for {}", substance);
if let Some(cp) = cp_state.value() {
assert!(*cp > 0.0); }
}
}
#[test]
fn test_calculate_therm_map_of_fun() {
let mut user_subs = create_test_subsdata();
let _ = user_subs.search_substances().unwrap();
user_subs.print_search_summary();
let _ = user_subs.extract_all_thermal_coeffs(298.15);
let result = user_subs.calculate_therm_map_of_fun();
assert!(result.is_ok());
assert!(!user_subs.therm_functions().is_empty());
for substance in user_subs.get_priority_found_substances() {
let cp_fun_state = user_subs.therm_function_state(&substance, DataType::Cp_fun);
assert!(
cp_fun_state.is_ready(),
"Cp function should be ready for {}",
substance
);
if let Some(cp_fun) = cp_fun_state.value() {
let cp_300 = cp_fun(300.0);
let cp_400 = cp_fun(400.0);
assert!(cp_300 > 0.0);
assert!(cp_400 > 0.0);
}
}
}
#[test]
fn test_one_phase_gibbs_builders_do_not_publish_working_copy_caches() {
let mut user_subs = create_test_subsdata();
let _ = user_subs.search_substances().unwrap();
let _ = user_subs.extract_all_thermal_coeffs(298.15);
assert!(user_subs.therm_map_of_properties_values.is_empty());
assert!(user_subs.therm_functions().is_empty());
assert!(user_subs.therm_symbolic().is_empty());
let gibbs_fun = user_subs.calculate_Gibbs_fun_one_phase(101_325.0, 298.15);
assert!(gibbs_fun.is_ok());
assert!(user_subs.therm_functions().is_empty());
assert!(user_subs.therm_symbolic().is_empty());
assert!(user_subs.therm_map_of_properties_values.is_empty());
let gibbs_sym = user_subs.calculate_Gibbs_sym_one_phase(298.15, None, None);
assert!(gibbs_sym.is_ok());
assert!(user_subs.therm_functions().is_empty());
assert!(user_subs.therm_symbolic().is_empty());
assert!(user_subs.therm_map_of_properties_values.is_empty());
}
#[test]
fn test_calculate_therm_map_of_sym() {
let mut user_subs = create_test_subsdata();
let _ = user_subs.search_substances().unwrap();
let _ = user_subs.extract_all_thermal_coeffs(298.15);
let result = user_subs.calculate_therm_map_of_sym();
assert!(result.is_ok());
assert!(!user_subs.therm_symbolic().is_empty());
for substance in user_subs.get_priority_found_substances() {
assert!(
user_subs
.therm_symbolic_state(&substance, DataType::Cp_sym)
.is_ready()
);
}
}
#[test]
fn test_transport_properties() {
let mut user_subs = create_test_subsdata();
user_subs.set_multiple_library_priorities(
vec!["Aramco_transport".to_string()],
LibraryPriority::Priority,
);
let _ = user_subs.search_substances().unwrap();
let _ = user_subs.extract_all_thermal_coeffs(298.15);
let _ = user_subs.extract_all_transport_coeffs(298.15);
let _ = user_subs.calculate_therm_map_of_properties(298.15);
let found_substances: Vec<String> = user_subs
.get_all_search_states()
.keys()
.filter(|&s| {
user_subs
.get_substance_search_state(s)
.is_some_and(|state| state.transport().is_resolved())
})
.cloned()
.collect();
if !found_substances.is_empty() {
let substance = &found_substances[0];
let cp = user_subs
.therm_map_of_properties_values
.get(substance)
.and_then(|props| props.get(&DataType::Cp))
.and_then(|opt_cp| *opt_cp);
if let Some(cp) = cp {
let result =
user_subs.calculate_transport_properties(substance, 298.15, Some(cp), None);
if result.is_ok() {
let (lambda, visc) = result.unwrap();
assert!(lambda > 0.0); assert!(visc > 0.0); }
}
}
}
#[test]
fn test_print_search_summary() {
let mut user_subs = create_test_subsdata();
let _ = user_subs.search_substances().unwrap();
let report = user_subs.search_summary_report();
assert_eq!(report.total_substances(), user_subs.substances.len());
assert_eq!(
report.priority_found() + report.permitted_found() + report.not_found(),
user_subs.substances.len()
);
assert!(!report.render_table().is_empty());
user_subs.print_search_summary();
}
#[test]
fn test_search_summary_render_does_not_mutate_state() {
let mut user_subs = create_test_subsdata();
let _ = user_subs.search_substances().unwrap();
let before_states = user_subs
.get_all_search_states()
.keys()
.cloned()
.collect::<Vec<_>>();
let mut before_states = before_states;
before_states.sort();
let before_count = user_subs.get_all_search_states().len();
let report = user_subs.search_summary_report();
let rendered = report.render_table();
assert!(!rendered.is_empty());
assert_eq!(before_count, user_subs.get_all_search_states().len());
let mut after_states = user_subs
.get_all_search_states()
.keys()
.cloned()
.collect::<Vec<_>>();
after_states.sort();
assert_eq!(before_states, after_states);
assert_eq!(report.total_substances(), before_count);
}
#[test]
fn test_get_priority_and_permitted_found_substances() {
let mut user_subs = create_test_subsdata();
user_subs.set_library_priority("NASA_cond".to_string(), LibraryPriority::Permitted);
user_subs.set_library_priority(
"Cantera_nasa_base_gas".to_string(),
LibraryPriority::Priority,
);
user_subs.search_substances().unwrap();
let priority_found = user_subs.get_priority_found_substances();
let permitted_found = user_subs.get_permitted_found_substances();
assert_eq!(
priority_found.len()
+ permitted_found.len()
+ user_subs.get_not_found_substances().len(),
user_subs.substances.len()
);
}
#[test]
fn test_clone_preserves_state_but_requires_explicit_function_rebuild() {
let mut user_subs = create_test_subsdata();
let _ = user_subs.search_substances().unwrap();
let _ = user_subs.extract_all_thermal_coeffs(298.15);
let _ = user_subs.calculate_therm_map_of_properties(298.15);
let _ = user_subs.calculate_therm_map_of_fun();
let thermo_source = user_subs
.therm_map_of_fun
.keys()
.next()
.cloned()
.expect("expected at least one thermodynamic function cache entry");
let mut cloned_subs = user_subs.clone();
assert_eq!(cloned_subs.substances, user_subs.substances);
assert_eq!(cloned_subs.library_priorities, user_subs.library_priorities);
assert_eq!(
cloned_subs.therm_map_of_properties_values.len(),
user_subs.therm_map_of_properties_values.len()
);
assert_eq!(
cloned_subs.therm_map_of_sym.len(),
user_subs.therm_map_of_sym.len()
);
assert_eq!(
cloned_subs.search_results.len(),
user_subs.search_results.len()
);
assert_eq!(
cloned_subs.therm_map_of_fun.keys().count(),
user_subs.therm_map_of_fun.keys().count()
);
assert_eq!(
cloned_subs.transport_map_of_fun.keys().count(),
user_subs.transport_map_of_fun.keys().count()
);
assert!(
cloned_subs
.therm_map_of_fun
.get(&thermo_source)
.and_then(|map| map.get(&DataType::Cp_fun))
.is_some_and(|entry| entry.is_none())
);
if let Some(transport_source) = cloned_subs.transport_map_of_fun.keys().next().cloned() {
assert!(matches!(
cloned_subs.transport_function_state(&transport_source, DataType::Lambda_fun),
DerivedValueState::NotCalculated
));
} else {
assert!(cloned_subs.transport_map_of_fun.is_empty());
}
cloned_subs.calculate_therm_map_of_fun().unwrap();
assert!(matches!(
cloned_subs.get_thermo_function(&thermo_source, DataType::Cp_fun),
Some(_)
));
}
#[test]
fn test_if_not_found_go_NIST() {
let mut user_subs = SubsData::new();
user_subs.substances = vec![
"H2O".to_string(),
"CO2".to_string(),
"CH4".to_string(),
"NH4ClO4".to_string(),
];
user_subs
.map_of_phases
.insert("H2O".to_string(), Some(Phases::Gas));
user_subs
.map_of_phases
.insert("CO2".to_string(), Some(Phases::Gas));
user_subs
.map_of_phases
.insert("CH4".to_string(), Some(Phases::Gas));
user_subs
.map_of_phases
.insert("NH4ClO4".to_string(), Some(Phases::Solid));
user_subs.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
user_subs.set_library_priority("NUIG_thermo".to_string(), LibraryPriority::Permitted);
let _ = user_subs.set_P(1.0, Some("atm".to_string()));
let _ = user_subs.search_substances().unwrap();
user_subs.if_not_found_go_NIST().unwrap();
println!(
"\n\n Search state: {:#?}",
user_subs.get_substance_search_state("NH4ClO4")
);
let nh4clo4_state = user_subs.get_substance_search_state("NH4ClO4");
assert!(nh4clo4_state.is_some());
assert!(matches!(
nh4clo4_state.unwrap().thermo,
PropertySearchState::Found(_)
));
assert!(
user_subs
.get_substance_search_state("NH4ClO4")
.is_some_and(|state| state.thermo().is_resolved())
);
let not_found = user_subs.get_not_found_substances();
println!("Not found: {:#?}", not_found);
}
#[test]
fn test_if_not_found_go_NIST_errors_when_no_thermo_fallback_is_enabled() {
let mut user_subs = SubsData::new();
user_subs.substances = vec!["Xe".to_string()];
user_subs.library_priorities.clear();
user_subs.set_library_priority("Aramco_transport".to_string(), LibraryPriority::Priority);
user_subs.insert_not_found_if_absent("Xe");
let result = user_subs.if_not_found_go_NIST();
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("NIST data"));
}
#[test]
fn test_calculate_elem_composition_and_molar_mass() {
let mut user_subs = create_test_subsdata();
user_subs.substances = vec!["H2O".to_string(), "CO2".to_string(), "CH4".to_string()];
let _ = user_subs.search_substances().unwrap();
let result = user_subs.calculate_elem_composition_and_molar_mass(None);
assert!(result.is_ok());
assert!(user_subs.elem_composition_matrix.is_some());
assert!(user_subs.molar_mass_by_substance.contains_key("H2O"));
assert!(user_subs.molar_mass_by_substance.contains_key("CO2"));
assert!(user_subs.molar_mass_by_substance.contains_key("CH4"));
let h2o_mass = user_subs.molar_mass_by_substance.get("H2O").unwrap();
let co2_mass = user_subs.molar_mass_by_substance.get("CO2").unwrap();
let ch4_mass = user_subs.molar_mass_by_substance.get("CH4").unwrap();
assert_relative_eq!(*h2o_mass, 18.0, epsilon = 0.1);
assert_relative_eq!(*co2_mass, 44.0, epsilon = 0.1);
assert_relative_eq!(*ch4_mass, 16.0, epsilon = 0.1);
let matrix = user_subs.elem_composition_matrix.as_ref().unwrap();
assert_eq!(matrix.nrows(), 3); assert!(matrix.ncols() >= 3);
let mut groups = HashMap::new();
let mut methyl_group = HashMap::new();
methyl_group.insert("C".to_string(), 1);
methyl_group.insert("H".to_string(), 3);
groups.insert("Me".to_string(), methyl_group);
let mut user_subs_with_groups = create_test_subsdata();
user_subs_with_groups.substances = vec!["MeOH".to_string()];
let _ = user_subs_with_groups.search_substances().unwrap();
let result = user_subs_with_groups.calculate_elem_composition_and_molar_mass(Some(groups));
assert!(result.is_ok());
if let Some(meoh_mass) = user_subs_with_groups.molar_mass_by_substance.get("MeOH") {
assert_relative_eq!(*meoh_mass, 32.0, epsilon = 0.1); } else {
panic!("Molar mass for MeOH not calculated");
}
}
#[test]
fn test_calculate_elem_composition_matrix_structure() {
let mut user_subs = create_test_subsdata();
user_subs.substances = vec!["H2".to_string(), "O2".to_string(), "H2O".to_string()];
user_subs.search_substances().unwrap();
let result = user_subs.calculate_elem_composition_and_molar_mass(None);
assert!(result.is_ok());
let matrix = user_subs.elem_composition_matrix.as_ref().unwrap();
println!("Matrix: {}", matrix);
assert_eq!(matrix.nrows(), 3); assert_eq!(matrix.ncols(), 2);
assert_eq!(matrix[(0, 0)], 2.0); assert_eq!(matrix[(0, 1)], 0.0);
assert_eq!(matrix[(1, 0)], 0.0); assert_eq!(matrix[(1, 1)], 2.0);
assert_eq!(matrix[(2, 0)], 2.0); assert_eq!(matrix[(2, 1)], 1.0); }
#[test]
fn test_calculate_elem_composition_with_complex_molecules() {
let mut user_subs = create_test_subsdata();
user_subs.substances = vec![
"C6H12O6".to_string(),
"C2H5OH".to_string(),
"NH3".to_string(),
];
let _ = user_subs.search_substances().unwrap();
let result = user_subs.calculate_elem_composition_and_molar_mass(None);
assert!(result.is_ok());
assert!(user_subs.elem_composition_matrix.is_some());
assert!(!user_subs.molar_mass_by_substance.is_empty());
let glucose_mass = user_subs.molar_mass_by_substance.get("C6H12O6").unwrap();
let ethanol_mass = user_subs.molar_mass_by_substance.get("C2H5OH").unwrap();
let ammonia_mass = user_subs.molar_mass_by_substance.get("NH3").unwrap();
assert_relative_eq!(*glucose_mass, 180.0, epsilon = 0.5); assert_relative_eq!(*ethanol_mass, 46.0, epsilon = 0.5); assert_relative_eq!(*ammonia_mass, 17.0, epsilon = 0.5);
let matrix = user_subs.elem_composition_matrix.as_ref().unwrap();
assert_eq!(matrix.nrows(), 3); assert!(matrix.ncols() >= 4); }
#[test]
fn test_calculate_transport_map_of_properties() {
let mut user_subs = SubsData::new();
let substances = vec!["H2O".to_string(), "CO".to_string()];
user_subs.substances = substances.clone();
user_subs
.map_of_phases
.insert("H2O".to_string(), Some(Phases::Gas));
user_subs
.map_of_phases
.insert("CO".to_string(), Some(Phases::Gas));
user_subs.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
user_subs.set_multiple_library_priorities(
vec!["Aramco_transport".to_string()],
LibraryPriority::Priority,
);
let _ = user_subs.search_substances().unwrap();
user_subs
.calculate_elem_composition_and_molar_mass(None)
.unwrap();
assert!(user_subs.elem_composition_matrix.is_some());
assert!(!user_subs.molar_mass_by_substance.is_empty());
println!("Molar masses: {:?}", user_subs.molar_mass_by_substance);
let _ = user_subs.set_P(1e5, None);
user_subs.extract_all_thermal_coeffs(400.0).unwrap();
user_subs.calculate_therm_map_of_properties(400.0).unwrap();
user_subs.extract_all_transport_coeffs(400.0).unwrap();
let result = user_subs.calculate_transport_map_of_properties(400.0);
assert!(result.is_ok());
for substance in &substances {
let lambda_state = user_subs.transport_value_state(substance, DataType::Lambda);
let visc_state = user_subs.transport_value_state(substance, DataType::Visc);
assert!(
lambda_state.is_ready(),
"Lambda should be ready for {}",
substance
);
assert!(
visc_state.is_ready(),
"Viscosity should be ready for {}",
substance
);
if let Some(lambda) = lambda_state.value() {
println!("{} Lambda: {}", substance, lambda);
assert!(*lambda > 0.0, "Lambda should be positive for {}", substance);
}
if let Some(visc) = visc_state.value() {
println!("{} Viscosity: {}", substance, visc);
assert!(
*visc > 0.0,
"Viscosity should be positive for {}",
substance
);
}
}
}
#[test]
fn test_calculate_transport_map_of_properties_CEA() {
let mut user_subs = SubsData::new();
let substances = vec!["H2O".to_string(), "CO".to_string()];
user_subs.substances = substances.clone();
user_subs
.map_of_phases
.insert("H2O".to_string(), Some(Phases::Gas));
user_subs
.map_of_phases
.insert("CO".to_string(), Some(Phases::Gas));
user_subs.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
user_subs
.set_multiple_library_priorities(vec!["CEA".to_string()], LibraryPriority::Priority);
let _ = user_subs.search_substances().unwrap();
user_subs
.calculate_elem_composition_and_molar_mass(None)
.unwrap();
assert!(user_subs.elem_composition_matrix.is_some());
assert!(!user_subs.molar_mass_by_substance.is_empty());
println!("Molar masses: {:?}", user_subs.molar_mass_by_substance);
let _ = user_subs.set_P(1e5, None);
let H2O_results = user_subs.get_substance_result("H2O");
assert!(H2O_results.is_some());
let H2O_results = H2O_results.unwrap();
assert!(H2O_results.get(&WhatIsFound::Thermo).is_some());
assert!(H2O_results.get(&WhatIsFound::Transport).is_some());
let H2O_thermo = H2O_results.get(&WhatIsFound::Thermo).unwrap();
assert!(H2O_thermo.is_some());
let H2O_transport = H2O_results.get(&WhatIsFound::Transport).unwrap();
assert!(H2O_transport.is_some());
user_subs.extract_all_thermal_coeffs(400.0).unwrap();
user_subs.calculate_therm_map_of_properties(400.0).unwrap();
user_subs.extract_all_transport_coeffs(400.0).unwrap();
let result = user_subs.calculate_transport_map_of_properties(400.0);
assert!(result.is_ok());
for substance in &substances {
let lambda_state = user_subs.transport_value_state(substance, DataType::Lambda);
let visc_state = user_subs.transport_value_state(substance, DataType::Visc);
assert!(
lambda_state.is_ready(),
"Lambda should be ready for {}",
substance
);
assert!(
visc_state.is_ready(),
"Viscosity should be ready for {}",
substance
);
if let Some(lambda) = lambda_state.value() {
println!("{} Lambda: {}", substance, lambda);
assert!(*lambda > 0.0, "Lambda should be positive for {}", substance);
}
if let Some(visc) = visc_state.value() {
println!("{} Viscosity: {}", substance, visc);
assert!(
*visc > 0.0,
"Viscosity should be positive for {}",
substance
);
}
}
}
}
#[cfg(test)]
mod additional_error_tests {
use crate::Thermodynamics::User_substances::{LibraryPriority, SubsData};
use crate::Thermodynamics::User_substances_error::SubsDataError;
#[test]
fn test_absurdly_high_temperatures() {
let mut subs_data = SubsData::new();
subs_data.substances = vec!["CO".to_string()];
subs_data.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
let _ = subs_data.search_substances().unwrap();
let extreme_temps = vec![1_000_000.0, 10_000_000.0, f64::MAX];
for temp in extreme_temps {
let result = subs_data.extract_thermal_coeffs("CO", temp);
if let Err(error) = result {
assert!(matches!(
error,
SubsDataError::InvalidTemperature(_)
| SubsDataError::CoefficientExtractionFailed { .. }
| SubsDataError::ThermoError(_)
));
}
}
}
#[test]
fn test_fictional_substances() {
let mut subs_data = SubsData::new();
let fictional_substances = vec![
"Unobtainium".to_string(),
"Vibranium".to_string(),
"Kryptonite".to_string(),
"Element115".to_string(),
];
subs_data.substances = fictional_substances.clone();
subs_data.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
let _ = subs_data.search_substances().unwrap();
for substance in &fictional_substances {
let result = subs_data.extract_thermal_coeffs(substance, 400.0);
println!("Result for {}: {:?}", substance, result);
assert!(matches!(
result,
Err(SubsDataError::SubstanceNotFound(_))
| Err(SubsDataError::CalculatorNotAvailable { .. })
));
let result = subs_data.calculate_thermo_properties(substance, 400.0);
assert!(matches!(
result,
Err(SubsDataError::SubstanceNotFound(_))
| Err(SubsDataError::CalculatorNotAvailable { .. })
));
}
}
#[test]
fn test_malformed_substance_names() {
let mut subs_data = SubsData::new();
let malformed_names = vec![
"".to_string(), " ".to_string(), "CO@#$%".to_string(), "123456".to_string(), "VeryLongSubstanceNameThatDoesNotExistAnywhere".to_string(), ];
subs_data.substances = malformed_names.clone();
subs_data.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
let _ = subs_data.search_substances().unwrap();
for substance in &malformed_names {
let result = subs_data.extract_thermal_coeffs(substance, 400.0);
assert!(matches!(result, Err(SubsDataError::SubstanceNotFound(_))));
}
}
#[test]
fn test_transport_without_prerequisites() {
let mut subs_data = SubsData::new();
subs_data.substances = vec!["CO".to_string()];
subs_data.set_multiple_library_priorities(
vec!["Aramco_transport".to_string()],
LibraryPriority::Priority,
);
subs_data.search_substances().unwrap();
let result = subs_data.calculate_transport_properties("CO", 400.0, Some(30.0), None);
assert!(matches!(result, Err(SubsDataError::PressureNotSet)));
let _ = subs_data.set_P(101325.0, None);
let result = subs_data.calculate_transport_properties("CO", 400.0, Some(30.0), None);
assert!(matches!(result, Err(SubsDataError::MolarMassNotFound(_))));
}
#[test]
fn test_batch_operations_with_mixed_results() {
let mut subs_data = SubsData::new();
subs_data.substances = vec![
"CO".to_string(),
"H2O".to_string(),
"NonExistent1".to_string(),
"NonExistent2".to_string(),
];
subs_data.set_multiple_library_priorities(
vec!["NASA_gas".to_string()],
LibraryPriority::Priority,
);
let _ = subs_data.search_substances().unwrap();
let result = subs_data.extract_all_thermal_coeffs(400.0);
assert!(matches!(result, Err(SubsDataError::SubstanceNotFound(_))));
assert!(subs_data.extract_thermal_coeffs("CO", 400.0).is_ok());
assert!(subs_data.extract_thermal_coeffs("H2O", 400.0).is_ok());
}
#[test]
fn test_error_message_content() {
let error = SubsDataError::CoefficientExtractionFailed {
substance: "TestSubstance".to_string(),
temperature: Some(5000.0),
};
let message = format!("{}", error);
assert!(message.contains("TestSubstance"));
assert!(message.contains("5000"));
assert!(message.contains("extract coefficients"));
let error = SubsDataError::FunctionCreationFailed {
substance: "CO2".to_string(),
function_type: "test function".to_string(),
};
let message = format!("{}", error);
assert!(message.contains("CO2"));
assert!(message.contains("test function"));
assert!(message.contains("Failed to create"));
let error = SubsDataError::SubstanceNotFound("UnknownSubstance".to_string());
let message = format!("{}", error);
assert!(message.contains("UnknownSubstance"));
assert!(message.contains("not found"));
let error = SubsDataError::InvalidTemperature(-100.0);
let message = format!("{}", error);
assert!(message.contains("-100"));
assert!(message.contains("Invalid temperature"));
}
}
#[cfg(test)]
mod logging_tests {
use crate::Thermodynamics::User_substances::{LibraryPriority, Phases, SubsData};
use crate::Thermodynamics::User_substances_error::ExceptionLogger;
use std::collections::HashMap;
#[test]
fn test_logging_non_existent_substances() {
use crate::Thermodynamics::User_substances_error::LogErrorType;
let mut subs_data = SubsData::new();
subs_data.substances = vec!["FakeSubstance123".to_string(), "Unobtainium".to_string()];
subs_data.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
let _ = subs_data.search_substances();
subs_data.clear_error_logs();
let _ = subs_data.extract_thermal_coeffs("FakeSubstance123", 400.0);
let _ = subs_data.calculate_thermo_properties("Unobtainium", 500.0);
let _ = subs_data.extract_transport_coeffs("FakeSubstance123", 300.0);
let logs = subs_data.logger.get_logs();
assert!(!logs.is_empty());
let substance_not_found_count = logs
.iter()
.filter(|log| matches!(log.error_type, LogErrorType::SubstanceNotFound(_)))
.count();
assert!(
substance_not_found_count > 0,
"Expected SubstanceNotFound errors"
);
println!("\n=== Non-existent Substances ===");
subs_data.logger.print_pretty_table();
}
#[test]
fn test_logging_invalid_temperatures() {
use crate::Thermodynamics::User_substances_error::LogErrorType;
let mut subs_data = SubsData::new();
subs_data.substances = vec!["CO".to_string()];
subs_data.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
let _ = subs_data.search_substances();
subs_data.clear_error_logs();
let invalid_temps = vec![-100.0, 0.0, -273.15];
for temp in invalid_temps {
let _ = subs_data.extract_thermal_coeffs("CO", temp);
let _ = subs_data.extract_transport_coeffs("CO", temp);
}
let logs = subs_data.logger.get_logs();
assert!(!logs.is_empty());
let invalid_temp_count = logs
.iter()
.filter(|log| matches!(log.error_type, LogErrorType::InvalidTemperature(_)))
.count();
assert!(invalid_temp_count > 0, "Expected InvalidTemperature errors");
println!("\n=== Invalid Temperatures ===");
subs_data.logger.print_pretty_table();
}
#[test]
fn test_logging_batch_operations() {
let mut subs_data = SubsData::new();
subs_data.substances = vec![
"CO".to_string(),
"NonExistent1".to_string(),
"CO2".to_string(),
];
subs_data.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
let _ = subs_data.search_substances();
subs_data.clear_error_logs();
let _ = subs_data.extract_all_thermal_coeffs(400.0);
let _ = subs_data.calculate_therm_map_of_properties(400.0);
let _ = subs_data.calculate_therm_map_of_fun();
let logs = subs_data.logger.get_logs();
assert!(!logs.is_empty());
println!("\n=== Batch Operations ===");
subs_data.logger.print_pretty_table();
}
#[test]
fn test_logging_transport_prerequisites() {
use crate::Thermodynamics::User_substances_error::LogErrorType;
let mut subs_data = SubsData::new();
subs_data.substances = vec!["CO".to_string()];
subs_data.set_library_priority("Aramco_transport".to_string(), LibraryPriority::Priority);
let _ = subs_data.search_substances();
subs_data.clear_error_logs();
let _ = subs_data.calculate_transport_properties("CO", 400.0, Some(30.0), None);
let _ = subs_data.set_P(101325.0, None);
let _ = subs_data.calculate_transport_properties("CO", 400.0, Some(30.0), None);
let logs = subs_data.logger.get_logs();
assert!(!logs.is_empty());
let pressure_not_set_count = logs
.iter()
.filter(|log| matches!(log.error_type, LogErrorType::PressureNotSet(_)))
.count();
let molar_mass_not_found_count = logs
.iter()
.filter(|log| matches!(log.error_type, LogErrorType::MolarMassNotFound(_)))
.count();
assert!(
pressure_not_set_count > 0 || molar_mass_not_found_count > 0,
"Expected PressureNotSet or MolarMassNotFound errors"
);
println!("\n=== Transport Prerequisites ===");
subs_data.logger.print_pretty_table();
}
#[test]
fn test_logging_transport_batch_operations() {
let mut subs_data = SubsData::new();
subs_data.substances = vec!["H2O".to_string(), "FakeGas".to_string(), "CO".to_string()];
subs_data.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
subs_data.set_library_priority("Aramco_transport".to_string(), LibraryPriority::Priority);
for substance in &subs_data.substances {
subs_data
.map_of_phases
.insert(substance.clone(), Some(Phases::Gas));
}
let _ = subs_data.search_substances();
let _ = subs_data.set_P(101325.0, None);
let _ = subs_data.set_M(
HashMap::from([
("H2O".to_string(), 18.0),
("CO".to_string(), 28.0),
("FakeGas".to_string(), 32.0),
]),
None,
);
subs_data.clear_error_logs();
let _ = subs_data.extract_all_transport_coeffs(400.0);
let _ = subs_data.extract_all_thermal_coeffs(400.0);
let _ = subs_data.calculate_therm_map_of_properties(400.0);
let _ = subs_data.calculate_transport_map_of_properties(400.0);
let _ = subs_data.calculate_transport_map_of_functions();
let _ = subs_data.calculate_transport_map_of_sym();
println!("\n=== Transport Batch Operations ===");
subs_data.logger.print_pretty_table();
}
#[test]
fn test_logging_extreme_conditions() {
let mut subs_data = SubsData::new();
subs_data.substances = vec!["CO".to_string()];
subs_data.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
let _ = subs_data.search_substances();
subs_data.clear_error_logs();
let extreme_temps = vec![10_000.0, 50_000.0, 100_000.0];
for temp in extreme_temps {
let _ = subs_data.extract_thermal_coeffs("CO", temp);
}
println!("\n=== Extreme Conditions ===");
subs_data.logger.print_pretty_table();
}
#[test]
fn test_comprehensive_error_coverage() {
use crate::Thermodynamics::User_substances_error::LogErrorType;
let mut subs_data = SubsData::new();
subs_data.substances = vec!["CO".to_string(), "FakeSubstance".to_string()];
subs_data.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
subs_data.set_library_priority("Aramco_transport".to_string(), LibraryPriority::Priority);
let _ = subs_data.search_substances();
subs_data.clear_error_logs();
let _ = subs_data.extract_thermal_coeffs("FakeSubstance", 400.0);
let _ = subs_data.extract_thermal_coeffs("CO", -100.0);
let _ = subs_data.calculate_transport_properties("CO", 400.0, Some(30.0), None);
let _ = subs_data.set_P(101325.0, None);
let _ = subs_data.calculate_transport_properties("CO", 400.0, Some(30.0), None);
let _ = subs_data.set_M(HashMap::from([("CO".to_string(), 28.0)]), None);
let _ = subs_data.calculate_transport_properties("FakeSubstance", 400.0, Some(30.0), None);
let logs = subs_data.logger.get_logs();
println!("\n=== Comprehensive Error Coverage ===");
println!("Total errors logged: {}", logs.len());
subs_data.logger.print_pretty_table();
let substance_not_found = logs
.iter()
.any(|log| matches!(log.error_type, LogErrorType::SubstanceNotFound(_)));
let invalid_temperature = logs
.iter()
.any(|log| matches!(log.error_type, LogErrorType::InvalidTemperature(_)));
let pressure_not_set = logs
.iter()
.any(|log| matches!(log.error_type, LogErrorType::PressureNotSet(_)));
let molar_mass_not_found = logs
.iter()
.any(|log| matches!(log.error_type, LogErrorType::MolarMassNotFound(_)));
assert!(substance_not_found, "Expected SubstanceNotFound error");
assert!(invalid_temperature, "Expected InvalidTemperature error");
assert!(
pressure_not_set || molar_mass_not_found,
"Expected PressureNotSet or MolarMassNotFound error"
);
let error_types: std::collections::HashSet<_> = logs
.iter()
.map(|log| {
format!("{:?}", log.error_type)
.split('(')
.next()
.unwrap_or("Unknown")
.to_string()
})
.collect();
println!("Error types: {:?}", error_types);
assert!(error_types.len() > 1);
}
#[test]
fn test_logger_functionality() {
use crate::Thermodynamics::User_substances_error::LogErrorType;
let mut subs_data = SubsData::new();
subs_data.substances = vec!["NonExistent".to_string()];
subs_data.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
let _ = subs_data.search_substances();
subs_data.clear_error_logs();
assert!(subs_data.logger.get_logs().is_empty());
let _ = subs_data.extract_thermal_coeffs("NonExistent", 400.0);
let _ = subs_data.extract_thermal_coeffs("NonExistent", -100.0);
let logs = subs_data.logger.get_logs();
assert_eq!(logs.len(), 2);
assert!(
logs.iter()
.any(|log| matches!(log.error_type, LogErrorType::SubstanceNotFound(_)))
);
assert!(
logs.iter()
.any(|log| matches!(log.error_type, LogErrorType::InvalidTemperature(_)))
);
assert!(logs.iter().all(|log| log.substance == "NonExistent"));
assert!(
logs.iter()
.all(|log| log.function == "extract_thermal_coeffs")
);
println!("\n=== Logger Functionality Test ===");
subs_data.logger.print_pretty_table();
subs_data.clear_error_logs();
assert!(subs_data.logger.get_logs().is_empty());
}
#[test]
fn test_with_calculator_logging() {
let mut subs_data = SubsData::new();
subs_data.substances = vec!["CO".to_string()];
subs_data.set_library_priority("NASA_gas".to_string(), LibraryPriority::Priority);
subs_data.set_library_priority("Aramco_transport".to_string(), LibraryPriority::Priority);
let _ = subs_data.search_substances();
subs_data.clear_error_logs();
let _ = subs_data.set_T_range_for_thermo("CO", 10000.0, 5000.0);
let _ = subs_data.fitting_thermal_coeffs_for_T_interval("CO");
let _ = subs_data.parse_thermal_coeffs("CO");
let _ = subs_data.integr_mean("CO");
println!("\n=== With Calculator Logging ===");
subs_data.logger.print_pretty_table();
}
}