use crate::Thermodynamics::DBhandlers::Diffusion::MultiSubstanceDiffusion;
use crate::Thermodynamics::DBhandlers::thermo_api::{
ThermoCalculator, ThermoEnum, ThermoError, create_thermal_by_name,
};
use crate::Thermodynamics::DBhandlers::transport_api::{
TransportCalculator, TransportEnum, create_transport_calculator_by_name,
};
use crate::Thermodynamics::User_substances_error::SimpleExceptionLogger;
use crate::Thermodynamics::User_substances_error::{SubsDataError, SubsDataResult};
pub use crate::Thermodynamics::physical_state::PhysicalState as Phases;
use crate::Thermodynamics::thermo_lib_api::{
LibraryCapability, LibraryId, ResolvedThermoRecord, ThermoData, ThermoRepository,
};
use std::fmt;
use RustedSciThe::symbolic::symbolic_engine::Expr;
use nalgebra::DMatrix;
use serde_json::Value;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
#[allow(non_camel_case_types)]
pub enum DataType {
Cp,
Cp_fun,
Cp_sym,
dH,
dH_fun,
dH_sym,
dS,
dS_fun,
dS_sym,
dmu,
dmu_fun,
dmu_sym,
Lambda,
Lambda_fun,
Lambda_sym,
Visc,
Visc_fun,
Visc_sym,
}
#[allow(non_camel_case_types)]
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
pub enum PropertyKind {
Cp,
dH,
dS,
dmu,
Lambda,
Visc,
}
impl PropertyKind {
pub fn numeric_type(self) -> DataType {
match self {
PropertyKind::Cp => DataType::Cp,
PropertyKind::dH => DataType::dH,
PropertyKind::dS => DataType::dS,
PropertyKind::dmu => DataType::dmu,
PropertyKind::Lambda => DataType::Lambda,
PropertyKind::Visc => DataType::Visc,
}
}
pub fn function_type(self) -> DataType {
match self {
PropertyKind::Cp => DataType::Cp_fun,
PropertyKind::dH => DataType::dH_fun,
PropertyKind::dS => DataType::dS_fun,
PropertyKind::dmu => DataType::dmu_fun,
PropertyKind::Lambda => DataType::Lambda_fun,
PropertyKind::Visc => DataType::Visc_fun,
}
}
pub fn symbolic_type(self) -> DataType {
match self {
PropertyKind::Cp => DataType::Cp_sym,
PropertyKind::dH => DataType::dH_sym,
PropertyKind::dS => DataType::dS_sym,
PropertyKind::dmu => DataType::dmu_sym,
PropertyKind::Lambda => DataType::Lambda_sym,
PropertyKind::Visc => DataType::Visc_sym,
}
}
pub fn is_transport(self) -> bool {
matches!(self, PropertyKind::Lambda | PropertyKind::Visc)
}
pub fn from_data_type(data_type: DataType) -> Option<Self> {
match data_type {
DataType::Cp | DataType::Cp_fun | DataType::Cp_sym => Some(PropertyKind::Cp),
DataType::dH | DataType::dH_fun | DataType::dH_sym => Some(PropertyKind::dH),
DataType::dS | DataType::dS_fun | DataType::dS_sym => Some(PropertyKind::dS),
DataType::dmu | DataType::dmu_fun | DataType::dmu_sym => Some(PropertyKind::dmu),
DataType::Lambda | DataType::Lambda_fun | DataType::Lambda_sym => {
Some(PropertyKind::Lambda)
}
DataType::Visc | DataType::Visc_fun | DataType::Visc_sym => Some(PropertyKind::Visc),
}
}
}
#[derive(Debug, Clone)]
pub enum CalculatorType {
Thermo(ThermoEnum),
Transport(TransportEnum),
}
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
pub enum WhatIsFound {
Thermo,
Transport,
NotFound,
}
#[derive(Debug, Clone)]
pub enum PropertySearchState {
NotSearched,
Found(SearchResult),
Missing,
Failed(String),
}
impl PropertySearchState {
pub fn is_pending(&self) -> bool {
matches!(self, PropertySearchState::NotSearched)
}
pub fn is_resolved(&self) -> bool {
matches!(self, PropertySearchState::Found(_))
}
pub fn is_missing(&self) -> bool {
matches!(self, PropertySearchState::Missing)
}
pub fn is_failed(&self) -> bool {
matches!(self, PropertySearchState::Failed(_))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DerivedValueState<'a, T: ?Sized> {
NotCalculated,
Ready(&'a T),
}
impl<'a, T: ?Sized> DerivedValueState<'a, T> {
pub fn is_not_calculated(&self) -> bool {
matches!(self, DerivedValueState::NotCalculated)
}
pub fn is_ready(&self) -> bool {
matches!(self, DerivedValueState::Ready(_))
}
pub fn value(&self) -> Option<&'a T> {
match self {
DerivedValueState::Ready(value) => Some(*value),
DerivedValueState::NotCalculated => None,
}
}
}
#[derive(Debug, Clone)]
pub(crate) enum TransportCalculationMode {
Cea,
Standard {
pressure: f64,
molar_mass: f64,
pressure_unit: Option<String>,
molar_mass_unit: Option<String>,
},
}
#[derive(Debug, Clone)]
pub struct SubstanceSearchState {
pub thermo: PropertySearchState,
pub transport: PropertySearchState,
}
impl SubstanceSearchState {
pub fn thermo(&self) -> &PropertySearchState {
&self.thermo
}
pub fn transport(&self) -> &PropertySearchState {
&self.transport
}
pub fn property_state(&self, found_kind: WhatIsFound) -> Option<&PropertySearchState> {
match found_kind {
WhatIsFound::Thermo => Some(&self.thermo),
WhatIsFound::Transport => Some(&self.transport),
WhatIsFound::NotFound => None,
}
}
pub fn result(&self, found_kind: WhatIsFound) -> Option<&SearchResult> {
match self.property_state(found_kind)? {
PropertySearchState::Found(result) => Some(result),
PropertySearchState::NotSearched
| PropertySearchState::Missing
| PropertySearchState::Failed(_) => None,
}
}
pub fn failure(&self, found_kind: WhatIsFound) -> Option<&str> {
match self.property_state(found_kind)? {
PropertySearchState::Failed(message) => Some(message.as_str()),
PropertySearchState::NotSearched
| PropertySearchState::Found(_)
| PropertySearchState::Missing => None,
}
}
pub(crate) fn new() -> Self {
Self {
thermo: PropertySearchState::NotSearched,
transport: PropertySearchState::NotSearched,
}
}
pub(crate) fn set_found(
&mut self,
found_kind: WhatIsFound,
result: SearchResult,
overwrite: bool,
) {
let target = match found_kind {
WhatIsFound::Thermo => &mut self.thermo,
WhatIsFound::Transport => &mut self.transport,
WhatIsFound::NotFound => return,
};
if overwrite
|| matches!(
target,
PropertySearchState::NotSearched | PropertySearchState::Missing
)
{
*target = PropertySearchState::Found(result);
}
}
pub(crate) fn mark_missing(&mut self) {
if matches!(self.thermo, PropertySearchState::NotSearched)
&& matches!(self.transport, PropertySearchState::NotSearched)
{
self.thermo = PropertySearchState::Missing;
self.transport = PropertySearchState::Missing;
}
}
pub(crate) fn to_compat_map(&self) -> HashMap<WhatIsFound, Option<SearchResult>> {
let mut map = HashMap::new();
match self.thermo() {
PropertySearchState::Found(result) => {
map.insert(WhatIsFound::Thermo, Some(result.clone()));
}
PropertySearchState::Missing | PropertySearchState::Failed(_) => {
map.insert(WhatIsFound::NotFound, None);
}
PropertySearchState::NotSearched => {}
}
match self.transport() {
PropertySearchState::Found(result) => {
map.insert(WhatIsFound::Transport, Some(result.clone()));
}
PropertySearchState::Missing | PropertySearchState::Failed(_) => {
map.entry(WhatIsFound::NotFound).or_insert(None);
}
PropertySearchState::NotSearched => {}
}
map
}
pub(crate) fn is_missing(&self) -> bool {
matches!(self.thermo, PropertySearchState::Missing)
&& matches!(self.transport, PropertySearchState::Missing)
}
pub(crate) fn has_priority_found(&self) -> bool {
matches!(
&self.thermo,
PropertySearchState::Found(SearchResult {
priority_type: LibraryPriority::Priority,
..
})
) || matches!(
&self.transport,
PropertySearchState::Found(SearchResult {
priority_type: LibraryPriority::Priority,
..
})
)
}
pub(crate) fn has_permitted_found(&self) -> bool {
matches!(
&self.thermo,
PropertySearchState::Found(SearchResult {
priority_type: LibraryPriority::Permitted,
..
})
) || matches!(
&self.transport,
PropertySearchState::Found(SearchResult {
priority_type: LibraryPriority::Permitted,
..
})
)
}
}
#[derive(Debug, Clone)]
pub struct SearchResult {
pub(crate) library: String,
pub(crate) record_key: String,
pub(crate) priority_type: LibraryPriority,
pub(crate) data: Value,
pub(crate) calculator: Option<CalculatorType>,
}
impl SearchResult {
pub fn library(&self) -> &str {
&self.library
}
pub fn record_key(&self) -> &str {
&self.record_key
}
pub fn priority_type(&self) -> LibraryPriority {
self.priority_type
}
pub fn data(&self) -> &Value {
&self.data
}
pub fn calculator(&self) -> Option<&CalculatorType> {
self.calculator.as_ref()
}
pub(crate) fn calculator_mut(&mut self) -> Option<&mut CalculatorType> {
self.calculator.as_mut()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LibraryPriority {
Priority,
Permitted,
Explicit,
}
pub struct SubsData {
pub(crate) P: Option<f64>,
pub(crate) P_unit: Option<String>,
pub(crate) T: Option<f64>,
pub(crate) Molar_mass_unit: Option<String>,
pub(crate) substances: Vec<String>,
pub(crate) library_priorities: HashMap<String, LibraryPriority>,
pub(crate) explicit_search_instructions: HashMap<String, String>,
pub(crate) ro_map: Option<HashMap<String, f64>>,
pub(crate) ro_map_sym: Option<HashMap<String, Box<Expr>>>,
pub(crate) map_of_phases: HashMap<String, Option<Phases>>,
physical_state_requirements: HashMap<String, Phases>,
pub(crate) search_states: HashMap<String, SubstanceSearchState>,
pub(crate) search_results: HashMap<String, HashMap<WhatIsFound, Option<SearchResult>>>,
pub(crate) thermo_data: ThermoData,
pub(crate) therm_map_of_properties_values: HashMap<String, HashMap<DataType, Option<f64>>>,
pub(crate) therm_map_of_fun:
HashMap<String, HashMap<DataType, Option<Box<dyn Fn(f64) -> f64 + Send + Sync>>>>,
pub(crate) therm_map_of_sym: HashMap<String, HashMap<DataType, Option<Box<Expr>>>>,
pub(crate) transport_map_of_properties_values: HashMap<String, HashMap<DataType, Option<f64>>>,
pub(crate) transport_map_of_fun:
HashMap<String, HashMap<DataType, Option<Box<dyn Fn(f64) -> f64 + Send + Sync>>>>,
pub(crate) transport_map_of_sym: HashMap<String, HashMap<DataType, Option<Box<Expr>>>>,
pub(crate) elem_composition_matrix: Option<DMatrix<f64>>,
pub(crate) molar_mass_by_substance: HashMap<String, f64>,
pub(crate) diffusion_data: Option<MultiSubstanceDiffusion>,
pub(crate) unique_elements: Vec<String>,
pub(crate) logger: SimpleExceptionLogger,
}
impl fmt::Debug for SubsData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SubsData")
.field("substances", &self.substances)
.field("library_priorities", &self.library_priorities)
.field("search_states", &self.search_states)
.field("search_results", &self.search_results)
.field(
"therm_map_of_properties_values",
&self.therm_map_of_properties_values,
)
.field("therm_map_of_sym", &self.therm_map_of_sym)
.finish()
}
}
fn clone_function_cache(
source: &HashMap<String, HashMap<DataType, Option<Box<dyn Fn(f64) -> f64 + Send + Sync>>>>,
) -> HashMap<String, HashMap<DataType, Option<Box<dyn Fn(f64) -> f64 + Send + Sync>>>> {
let mut cloned = HashMap::with_capacity(source.len());
for (substance, type_map) in source {
let mut cloned_type_map = HashMap::with_capacity(type_map.len());
for (data_type, _) in type_map {
cloned_type_map.insert(*data_type, None);
}
cloned.insert(substance.clone(), cloned_type_map);
}
cloned
}
impl Clone for SubsData {
fn clone(&self) -> Self {
let new_therm_map_of_fun = clone_function_cache(&self.therm_map_of_fun);
let new_transport_map_of_fun = clone_function_cache(&self.transport_map_of_fun);
let sd = SubsData {
P: self.P,
P_unit: self.P_unit.clone(),
T: self.T.clone(),
Molar_mass_unit: self.Molar_mass_unit.clone(),
substances: self.substances.clone(),
library_priorities: self.library_priorities.clone(),
explicit_search_instructions: self.explicit_search_instructions.clone(),
ro_map: self.ro_map.clone(),
ro_map_sym: self.ro_map_sym.clone(),
map_of_phases: self.map_of_phases.clone(),
physical_state_requirements: self.physical_state_requirements.clone(),
search_states: self.search_states.clone(),
search_results: self.get_all_results(),
thermo_data: self.thermo_data.clone(),
therm_map_of_properties_values: self.therm_map_of_properties_values.clone(),
therm_map_of_fun: new_therm_map_of_fun,
therm_map_of_sym: self.therm_map_of_sym.clone(),
transport_map_of_properties_values: self.transport_map_of_properties_values.clone(),
transport_map_of_fun: new_transport_map_of_fun,
transport_map_of_sym: self.transport_map_of_sym.clone(),
elem_composition_matrix: self.elem_composition_matrix.clone(),
molar_mass_by_substance: self.molar_mass_by_substance.clone(),
diffusion_data: self.diffusion_data.clone(),
unique_elements: self.unique_elements.clone(),
logger: self.logger.clone(),
};
sd
}
}
impl SubsData {
pub fn from_thermo_repository(repository: Arc<ThermoRepository>) -> Self {
let mut data = Self::empty();
data.thermo_data = ThermoData::from_repository(repository);
data
}
pub fn substances(&self) -> &[String] {
&self.substances
}
pub fn library_priorities(&self) -> &HashMap<String, LibraryPriority> {
&self.library_priorities
}
pub fn explicit_search_instructions(&self) -> &HashMap<String, String> {
&self.explicit_search_instructions
}
pub fn explicit_search_map(&self) -> &HashMap<String, String> {
&self.explicit_search_instructions
}
pub fn pressure(&self) -> Option<f64> {
self.P
}
pub fn pressure_unit(&self) -> Option<&str> {
self.P_unit.as_deref()
}
pub fn temperature(&self) -> Option<f64> {
self.T
}
pub fn molar_mass_unit(&self) -> Option<&str> {
self.Molar_mass_unit.as_deref()
}
pub fn phases(&self) -> &HashMap<String, Option<Phases>> {
&self.map_of_phases
}
pub fn search_states(&self) -> &HashMap<String, SubstanceSearchState> {
&self.search_states
}
pub fn therm_values(&self) -> &HashMap<String, HashMap<DataType, Option<f64>>> {
&self.therm_map_of_properties_values
}
pub fn therm_functions(
&self,
) -> &HashMap<String, HashMap<DataType, Option<Box<dyn Fn(f64) -> f64 + Send + Sync>>>> {
&self.therm_map_of_fun
}
pub fn therm_function_state(
&self,
substance: &str,
data_type: DataType,
) -> DerivedValueState<'_, dyn Fn(f64) -> f64 + Send + Sync> {
self.derived_function_state(&self.therm_map_of_fun, substance, data_type)
}
pub fn therm_symbolic(&self) -> &HashMap<String, HashMap<DataType, Option<Box<Expr>>>> {
&self.therm_map_of_sym
}
pub fn property_value_state(
&self,
substance: &str,
kind: PropertyKind,
) -> DerivedValueState<'_, f64> {
if kind.is_transport() {
self.transport_value_state(substance, kind.numeric_type())
} else {
self.therm_value_state(substance, kind.numeric_type())
}
}
pub fn property_function_state(
&self,
substance: &str,
kind: PropertyKind,
) -> DerivedValueState<'_, dyn Fn(f64) -> f64 + Send + Sync> {
if kind.is_transport() {
self.transport_function_state(substance, kind.function_type())
} else {
self.therm_function_state(substance, kind.function_type())
}
}
pub fn property_symbolic_state(
&self,
substance: &str,
kind: PropertyKind,
) -> DerivedValueState<'_, Expr> {
if kind.is_transport() {
self.transport_symbolic_state(substance, kind.symbolic_type())
} else {
self.therm_symbolic_state(substance, kind.symbolic_type())
}
}
pub fn transport_values(&self) -> &HashMap<String, HashMap<DataType, Option<f64>>> {
&self.transport_map_of_properties_values
}
pub fn therm_value_state(
&self,
substance: &str,
data_type: DataType,
) -> DerivedValueState<'_, f64> {
self.derived_value_state(&self.therm_map_of_properties_values, substance, data_type)
}
pub fn transport_value_state(
&self,
substance: &str,
data_type: DataType,
) -> DerivedValueState<'_, f64> {
self.derived_value_state(
&self.transport_map_of_properties_values,
substance,
data_type,
)
}
pub fn therm_symbolic_state(
&self,
substance: &str,
data_type: DataType,
) -> DerivedValueState<'_, Expr> {
self.derived_boxed_value_state(&self.therm_map_of_sym, substance, data_type)
}
pub fn transport_symbolic_state(
&self,
substance: &str,
data_type: DataType,
) -> DerivedValueState<'_, Expr> {
self.derived_boxed_value_state(&self.transport_map_of_sym, substance, data_type)
}
pub fn transport_functions(
&self,
) -> &HashMap<String, HashMap<DataType, Option<Box<dyn Fn(f64) -> f64 + Send + Sync>>>> {
&self.transport_map_of_fun
}
pub fn transport_function_state(
&self,
substance: &str,
data_type: DataType,
) -> DerivedValueState<'_, dyn Fn(f64) -> f64 + Send + Sync> {
self.derived_function_state(&self.transport_map_of_fun, substance, data_type)
}
pub fn transport_symbolic(&self) -> &HashMap<String, HashMap<DataType, Option<Box<Expr>>>> {
&self.transport_map_of_sym
}
pub fn molar_masses(&self) -> &HashMap<String, f64> {
&self.molar_mass_by_substance
}
pub fn molar_mass_map(&self) -> &HashMap<String, f64> {
&self.molar_mass_by_substance
}
pub fn molar_mass_of(&self, substance: &str) -> Option<f64> {
self.molar_mass_by_substance.get(substance).copied()
}
pub fn unique_elements(&self) -> &[String] {
&self.unique_elements
}
pub fn element_composition_matrix(&self) -> Option<&DMatrix<f64>> {
self.elem_composition_matrix.as_ref()
}
pub fn diffusion_data(&self) -> Option<&MultiSubstanceDiffusion> {
self.diffusion_data.as_ref()
}
pub fn thermo_data(&self) -> &ThermoData {
&self.thermo_data
}
pub fn logger(&self) -> &SimpleExceptionLogger {
&self.logger
}
fn derived_value_state<'a>(
&'a self,
cache: &'a HashMap<String, HashMap<DataType, Option<f64>>>,
substance: &str,
data_type: DataType,
) -> DerivedValueState<'a, f64> {
cache
.get(substance)
.and_then(|property_map| property_map.get(&data_type))
.and_then(|value| value.as_ref())
.map_or(DerivedValueState::NotCalculated, DerivedValueState::Ready)
}
fn derived_boxed_value_state<'a, T>(
&'a self,
cache: &'a HashMap<String, HashMap<DataType, Option<Box<T>>>>,
substance: &str,
data_type: DataType,
) -> DerivedValueState<'a, T> {
cache
.get(substance)
.and_then(|property_map| property_map.get(&data_type))
.and_then(|value| value.as_ref())
.map(|value| value.as_ref())
.map_or(DerivedValueState::NotCalculated, DerivedValueState::Ready)
}
fn derived_function_state<'a>(
&'a self,
cache: &'a HashMap<
String,
HashMap<DataType, Option<Box<dyn Fn(f64) -> f64 + Send + Sync>>>,
>,
substance: &str,
data_type: DataType,
) -> DerivedValueState<'a, dyn Fn(f64) -> f64 + Send + Sync> {
cache
.get(substance)
.and_then(|property_map| property_map.get(&data_type))
.and_then(|value| value.as_deref())
.map_or(DerivedValueState::NotCalculated, DerivedValueState::Ready)
}
pub(crate) fn sync_search_state_view(&mut self, substance: &str) {
if let Some(state) = self.search_states.get(substance) {
self.search_results
.insert(substance.to_string(), state.to_compat_map());
}
}
pub(crate) fn store_search_result(
&mut self,
substance: &str,
found_kind: WhatIsFound,
library: String,
priority_type: LibraryPriority,
data: Value,
calculator: CalculatorType,
overwrite: bool,
) {
let search_result = SearchResult {
library,
record_key: substance.to_string(),
priority_type,
data,
calculator: Some(calculator),
};
let state = self
.search_states
.entry(substance.to_string())
.or_insert_with(SubstanceSearchState::new);
state.set_found(found_kind, search_result, overwrite);
self.sync_search_state_view(substance);
}
fn store_resolved_search_result(
&mut self,
substance: &str,
found_kind: WhatIsFound,
library: String,
record_key: String,
priority_type: LibraryPriority,
data: Value,
calculator: CalculatorType,
overwrite: bool,
) {
let search_result = SearchResult {
library,
record_key,
priority_type,
data,
calculator: Some(calculator),
};
let state = self
.search_states
.entry(substance.to_string())
.or_insert_with(SubstanceSearchState::new);
state.set_found(found_kind, search_result, overwrite);
self.sync_search_state_view(substance);
}
pub fn set_substance_physical_state(&mut self, substance: String, state: Phases) {
self.physical_state_requirements.insert(substance, state);
self.invalidate_lookup_dependent_state();
}
pub fn set_physical_state_requirements(&mut self, states: HashMap<String, Phases>) {
self.physical_state_requirements = states;
self.invalidate_lookup_dependent_state();
}
pub fn clear_substance_physical_state(&mut self, substance: &str) {
if self.physical_state_requirements.remove(substance).is_some() {
self.invalidate_lookup_dependent_state();
}
}
pub fn substance_physical_state(&self, substance: &str) -> Option<Phases> {
self.physical_state_requirements.get(substance).copied()
}
fn resolve_library_record(
&self,
library: &str,
substance: &str,
) -> SubsDataResult<Option<ResolvedThermoRecord>> {
let query = crate::Thermodynamics::physical_state::ThermoRecordQuery::new(substance)
.with_physical_state_opt(self.substance_physical_state(substance));
let resolved = self
.thermo_data
.resolve_thermo_record(library, &query)
.map_err(|error| SubsDataError::CalculationFailed {
substance: substance.to_string(),
operation: "physical-state-aware library lookup".to_string(),
reason: error.to_string(),
source: None,
})?;
Ok(resolved)
}
pub(crate) fn insert_not_found_if_absent(&mut self, substance: &str) {
let state = self
.search_states
.entry(substance.to_string())
.or_insert_with(SubstanceSearchState::new);
state.mark_missing();
self.sync_search_state_view(substance);
}
pub fn new() -> Self {
Self {
P: None,
P_unit: None,
T: None,
Molar_mass_unit: None,
substances: Vec::new(),
library_priorities: HashMap::new(),
explicit_search_instructions: HashMap::new(),
search_states: HashMap::new(),
search_results: HashMap::new(),
ro_map: None,
ro_map_sym: None,
map_of_phases: HashMap::new(),
physical_state_requirements: HashMap::new(),
thermo_data: ThermoData::new(),
therm_map_of_properties_values: HashMap::new(),
therm_map_of_fun: HashMap::new(),
therm_map_of_sym: HashMap::new(),
transport_map_of_properties_values: HashMap::new(),
transport_map_of_fun: HashMap::new(),
transport_map_of_sym: HashMap::new(),
elem_composition_matrix: None,
molar_mass_by_substance: HashMap::new(),
diffusion_data: None,
unique_elements: Vec::new(),
logger: SimpleExceptionLogger::new(),
}
}
pub fn empty() -> Self {
Self {
P: None,
P_unit: None,
T: None,
Molar_mass_unit: None,
substances: Vec::new(),
library_priorities: HashMap::new(),
explicit_search_instructions: HashMap::new(),
search_states: HashMap::new(),
search_results: HashMap::new(),
ro_map: None,
ro_map_sym: None,
map_of_phases: HashMap::new(),
physical_state_requirements: HashMap::new(),
thermo_data: ThermoData::empty(),
therm_map_of_properties_values: HashMap::new(),
therm_map_of_fun: HashMap::new(),
therm_map_of_sym: HashMap::new(),
transport_map_of_properties_values: HashMap::new(),
transport_map_of_fun: HashMap::new(),
transport_map_of_sym: HashMap::new(),
elem_composition_matrix: None,
molar_mass_by_substance: HashMap::new(),
diffusion_data: None,
unique_elements: Vec::new(),
logger: SimpleExceptionLogger::new(),
}
}
pub fn set_substances(&mut self, substances: Vec<String>) {
self.invalidate_substance_dependent_state();
self.substances = substances;
}
fn invalidate_substance_dependent_state(&mut self) {
self.search_states.clear();
self.search_results.clear();
self.therm_map_of_properties_values.clear();
self.therm_map_of_fun.clear();
self.therm_map_of_sym.clear();
self.transport_map_of_properties_values.clear();
self.transport_map_of_fun.clear();
self.transport_map_of_sym.clear();
self.elem_composition_matrix = None;
self.molar_mass_by_substance.clear();
self.diffusion_data = None;
self.unique_elements.clear();
self.ro_map = None;
self.ro_map_sym = None;
self.map_of_phases.clear();
self.physical_state_requirements.clear();
}
fn invalidate_lookup_dependent_state(&mut self) {
self.search_states.clear();
self.search_results.clear();
self.therm_map_of_properties_values.clear();
self.therm_map_of_fun.clear();
self.therm_map_of_sym.clear();
self.transport_map_of_properties_values.clear();
self.transport_map_of_fun.clear();
self.transport_map_of_sym.clear();
self.elem_composition_matrix = None;
self.molar_mass_by_substance.clear();
self.diffusion_data = None;
self.unique_elements.clear();
self.ro_map = None;
self.ro_map_sym = None;
}
fn invalidate_temperature_dependent_state(&mut self) {
self.therm_map_of_properties_values.clear();
self.transport_map_of_properties_values.clear();
self.transport_map_of_fun.clear();
self.transport_map_of_sym.clear();
self.ro_map = None;
self.ro_map_sym = None;
self.diffusion_data = None;
}
fn invalidate_pressure_and_molar_mass_dependent_state(&mut self) {
self.transport_map_of_properties_values.clear();
self.transport_map_of_fun.clear();
self.transport_map_of_sym.clear();
self.ro_map = None;
self.ro_map_sym = None;
self.diffusion_data = None;
}
pub fn set_library_priority(&mut self, library: String, priority: LibraryPriority) {
let canonical_library = ThermoData::canonical_library_name(&library);
self.library_priorities.insert(canonical_library, priority);
self.invalidate_lookup_dependent_state();
}
pub fn set_library_priority_id(&mut self, library: LibraryId, priority: LibraryPriority) {
self.set_library_priority(library.canonical_name().to_string(), priority);
}
pub fn set_multiple_library_priorities(
&mut self,
libraries: Vec<String>,
priority: LibraryPriority,
) {
for library in libraries {
let canonical_library = ThermoData::canonical_library_name(&library);
self.library_priorities
.insert(canonical_library, priority.clone());
}
self.invalidate_lookup_dependent_state();
}
pub fn set_multiple_library_priorities_ids(
&mut self,
libraries: Vec<LibraryId>,
priority: LibraryPriority,
) {
for library in libraries {
self.set_library_priority_id(library, priority);
}
}
pub fn set_explicit_search_instructions(&mut self, direct_search: HashMap<String, String>) {
self.explicit_search_instructions = direct_search
.into_iter()
.map(|(substance, library)| (substance, ThermoData::canonical_library_name(&library)))
.collect();
self.invalidate_lookup_dependent_state();
}
#[deprecated(note = "Use set_explicit_search_instructions")]
pub fn set_explicis_searh_instructions(&mut self, direct_search: HashMap<String, String>) {
self.set_explicit_search_instructions(direct_search);
}
pub fn set_explicit_search_instruction(&mut self, substance: String, library: LibraryId) {
self.explicit_search_instructions
.insert(substance, library.canonical_name().to_string());
self.invalidate_lookup_dependent_state();
}
pub fn create_calculator(&self, library: &str) -> SubsDataResult<CalculatorType> {
let canonical_library = ThermoData::canonical_library_name(library);
match ThermoData::library_capability(&canonical_library) {
Some(LibraryCapability::Thermo) => Ok(CalculatorType::Thermo(create_thermal_by_name(
&canonical_library,
))),
Some(LibraryCapability::Transport) => Ok(CalculatorType::Transport(
create_transport_calculator_by_name(&canonical_library)?,
)),
None => Err(SubsDataError::UnsupportedLibrary(library.to_string())),
}
}
pub fn search_substances(&mut self) -> SubsDataResult<()> {
let previous_search_states = self.search_states.clone();
let result = (|| -> SubsDataResult<()> {
let mut priority_libs: Vec<String> = self
.library_priorities
.iter()
.filter(move |&(_, &ref p)| *p == LibraryPriority::Priority)
.map(|(lib, _)| ThermoData::canonical_library_name(lib))
.collect();
priority_libs.sort();
priority_libs.dedup();
let mut permitted_libs: Vec<String> = self
.library_priorities
.iter()
.filter(move |&(_, &ref p)| *p == LibraryPriority::Permitted)
.map(|(lib, _)| ThermoData::canonical_library_name(lib))
.collect();
permitted_libs.sort();
permitted_libs.dedup();
let subs_to_search_explicit_instruction: HashSet<String> =
self.explicit_search_instructions.keys().cloned().collect();
let mut subs_to_search = self.substances.clone();
subs_to_search.retain(|subs_i| !subs_to_search_explicit_instruction.contains(subs_i));
for substance in subs_to_search {
let mut found_thermo = false;
let mut found_transport = false;
for lib in &priority_libs {
if let Some(resolved_record) = self.resolve_library_record(lib, &substance)? {
let substance_data = &resolved_record.data;
let mut calculator = self.create_calculator(lib)?;
match &mut calculator {
CalculatorType::Thermo(thermo) => {
if found_thermo {
continue;
}
thermo.from_serde(substance_data.clone())?;
self.store_resolved_search_result(
&substance,
WhatIsFound::Thermo,
lib.clone(),
resolved_record.record_key.clone(),
LibraryPriority::Priority,
substance_data.clone(),
calculator.clone(),
false,
);
found_thermo = true;
}
CalculatorType::Transport(transport) => {
if found_transport {
continue;
}
transport.from_serde(substance_data.clone())?;
self.store_resolved_search_result(
&substance,
WhatIsFound::Transport,
lib.clone(),
resolved_record.record_key.clone(),
LibraryPriority::Priority,
substance_data.clone(),
calculator.clone(),
false,
);
found_transport = true;
}
}
}
}
if !found_thermo || !found_transport {
for lib in &permitted_libs {
if let Some(resolved_record) =
self.resolve_library_record(lib, &substance)?
{
let substance_data = &resolved_record.data;
let mut calculator = self.create_calculator(lib)?;
match &mut calculator {
CalculatorType::Thermo(thermo) => {
if found_thermo {
continue;
}
thermo.newinstance()?;
thermo.from_serde(substance_data.clone())?;
self.store_resolved_search_result(
&substance,
WhatIsFound::Thermo,
lib.clone(),
resolved_record.record_key.clone(),
LibraryPriority::Permitted,
substance_data.clone(),
calculator.clone(),
false,
);
found_thermo = true;
}
CalculatorType::Transport(transport) => {
if found_transport {
continue;
}
transport.from_serde(substance_data.clone())?;
self.store_resolved_search_result(
&substance,
WhatIsFound::Transport,
lib.clone(),
resolved_record.record_key.clone(),
LibraryPriority::Permitted,
substance_data.clone(),
calculator.clone(),
false,
);
found_transport = true;
}
}
}
}
}
if !found_thermo && !found_transport {
self.insert_not_found_if_absent(&substance);
}
} let explicit_search_instructions: Vec<(String, String)> = self
.explicit_search_instructions
.iter()
.map(|(substance, library)| {
(
substance.clone(),
ThermoData::canonical_library_name(library),
)
})
.collect();
for (substance, library) in explicit_search_instructions {
let mut found_thermo = false;
let mut found_transport = false;
if let Some(resolved_record) = self.resolve_library_record(&library, &substance)? {
let substance_data = &resolved_record.data;
let mut calculator = self.create_calculator(&library)?;
match &mut calculator {
CalculatorType::Thermo(thermo) => {
thermo.from_serde(substance_data.clone())?;
self.store_resolved_search_result(
&substance,
WhatIsFound::Thermo,
library.clone(),
resolved_record.record_key.clone(),
LibraryPriority::Explicit,
substance_data.clone(),
calculator.clone(),
true,
);
found_thermo = true;
}
CalculatorType::Transport(transport) => {
transport.from_serde(substance_data.clone())?;
self.store_resolved_search_result(
&substance,
WhatIsFound::Transport,
library.clone(),
resolved_record.record_key.clone(),
LibraryPriority::Explicit,
substance_data.clone(),
calculator.clone(),
true,
);
found_transport = true;
}
}
}
if !found_thermo && !found_transport && !self.search_states.contains_key(&substance)
{
self.insert_not_found_if_absent(&substance);
}
} Ok(())
})();
if result.is_err() {
self.search_states = previous_search_states;
self.search_results = self.get_all_results();
}
result
}
pub fn get_substance_result(
&self,
substance: &str,
) -> Option<HashMap<WhatIsFound, Option<SearchResult>>> {
self.search_states
.get(substance)
.map(SubstanceSearchState::to_compat_map)
}
pub fn get_substance_search_state(&self, substance: &str) -> Option<&SubstanceSearchState> {
self.search_states.get(substance)
}
pub fn get_search_result(
&self,
substance: &str,
found_kind: WhatIsFound,
) -> Option<&SearchResult> {
self.get_canonical_search_result(substance, found_kind)
}
pub(crate) fn get_canonical_search_result(
&self,
substance: &str,
found_kind: WhatIsFound,
) -> Option<&SearchResult> {
self.search_states.get(substance)?.result(found_kind)
}
pub(crate) fn get_canonical_search_library(
&self,
substance: &str,
found_kind: WhatIsFound,
) -> SubsDataResult<String> {
self.get_canonical_search_result(substance, found_kind)
.map(|result| result.library().to_string())
.ok_or_else(|| SubsDataError::CalculatorNotAvailable {
substance: substance.to_string(),
calc_type: match found_kind {
WhatIsFound::Thermo => "Thermo".to_string(),
WhatIsFound::Transport => "Transport".to_string(),
WhatIsFound::NotFound => "NotFound".to_string(),
},
})
}
pub fn get_all_results(&self) -> HashMap<String, HashMap<WhatIsFound, Option<SearchResult>>> {
self.search_states
.iter()
.map(|(substance, state)| (substance.clone(), state.to_compat_map()))
.collect()
}
pub fn get_all_search_states(&self) -> &HashMap<String, SubstanceSearchState> {
&self.search_states
}
pub(crate) fn get_canonical_search_result_mut(
&mut self,
substance: &str,
found_kind: WhatIsFound,
) -> SubsDataResult<&mut SearchResult> {
let state = self
.search_states
.get_mut(substance)
.ok_or_else(|| SubsDataError::SubstanceNotFound(substance.to_string()))?;
if state.is_missing() {
return Err(SubsDataError::SubstanceNotFound(substance.to_string()));
}
let slot = match found_kind {
WhatIsFound::Thermo => &mut state.thermo,
WhatIsFound::Transport => &mut state.transport,
WhatIsFound::NotFound => {
return Err(SubsDataError::CalculatorNotAvailable {
substance: substance.to_string(),
calc_type: "NotFound".to_string(),
});
}
};
match slot {
PropertySearchState::Found(result) => Ok(result),
PropertySearchState::NotSearched
| PropertySearchState::Missing
| PropertySearchState::Failed(_) => Err(SubsDataError::CalculatorNotAvailable {
substance: substance.to_string(),
calc_type: match found_kind {
WhatIsFound::Thermo => "Thermo".to_string(),
WhatIsFound::Transport => "Transport".to_string(),
WhatIsFound::NotFound => "NotFound".to_string(),
},
}),
}
}
pub fn get_not_found_substances(&self) -> Vec<String> {
self.search_states
.iter()
.filter(|(_, state)| state.is_missing())
.map(|(substance, _)| substance.clone())
.collect()
}
pub fn get_priority_found_substances(&self) -> Vec<String> {
self.search_states
.iter()
.filter(|(_, state)| state.has_priority_found())
.map(|(substance, _)| substance.clone())
.collect()
}
pub fn search_by_elements(&mut self, elements: Vec<String>) -> SubsDataResult<Vec<String>> {
let found_substances = self.thermo_data.search_by_elements(elements);
self.substances = found_substances.clone();
self.populate_element_search_results(found_substances)
}
pub fn search_by_elements_only(
&mut self,
elements: Vec<String>,
) -> SubsDataResult<Vec<String>> {
let found_substances = self.thermo_data.search_by_elements_only(elements);
self.substances = found_substances.clone();
self.populate_element_search_results(found_substances)
}
fn populate_element_search_results(
&mut self,
found_substances: Vec<String>,
) -> SubsDataResult<Vec<String>> {
for substance in &found_substances {
let library_data_pairs: Vec<(String, Value)> = self
.thermo_data
.hashmap_of_thermo_data
.get(substance)
.map(|thermo_data| {
let mut libraries: Vec<String> = thermo_data.keys().cloned().collect();
libraries.sort();
libraries
.into_iter()
.filter_map(|library| {
thermo_data
.get(&library)
.cloned()
.map(|data| (library, data))
})
.collect()
})
.unwrap_or_default();
for (library, data) in library_data_pairs {
let mut calculator = self.create_calculator(&library)?;
if let CalculatorType::Thermo(thermo) = &mut calculator {
thermo.from_serde(data.clone())?;
let search_result = SearchResult {
library: library.clone(),
record_key: substance.clone(),
priority_type: LibraryPriority::Priority,
data: data.clone(),
calculator: Some(calculator.clone()),
};
let state = self
.search_states
.entry(substance.clone())
.or_insert_with(SubstanceSearchState::new);
state.set_found(WhatIsFound::Thermo, search_result, true);
self.sync_search_state_view(substance);
}
}
}
Ok(found_substances)
}
pub fn get_permitted_found_substances(&self) -> Vec<String> {
self.search_states
.iter()
.filter(|(_, state)| state.has_permitted_found())
.map(|(substance, _)| substance.clone())
.collect()
}
pub fn get_thermo_function(
&self,
substance: &str,
data_type: DataType,
) -> Option<&(dyn Fn(f64) -> f64 + Send + Sync)> {
self.therm_map_of_fun
.get(substance)
.and_then(|map| map.get(&data_type))
.and_then(|opt| opt.as_deref())
}
pub fn get_thermo_symbolic(&self, substance: &str, data_type: DataType) -> Option<&Expr> {
self.therm_map_of_sym
.get(substance)
.and_then(|map| map.get(&data_type))
.and_then(|opt| opt.as_deref())
}
pub(crate) fn with_thermo_calculator<F, R>(
&mut self,
substance: &str,
f: F,
) -> SubsDataResult<R>
where
F: FnOnce(&mut dyn ThermoCalculator) -> Result<R, ThermoError>,
{
let search_result = self.get_canonical_search_result_mut(substance, WhatIsFound::Thermo)?;
let calculator = search_result.calculator_mut().ok_or_else(|| {
SubsDataError::CalculatorNotAvailable {
substance: substance.to_string(),
calc_type: "Thermo".to_string(),
}
})?;
match calculator {
CalculatorType::Thermo(thermo) => {
let result: SubsDataResult<R> = f(thermo).map_err(Into::into);
if let Err(ref e) = result {
crate::log_error!(self.logger, e, substance, "with_thermo_calculator");
}
result
}
CalculatorType::Transport(_) => Err(SubsDataError::CalculatorNotAvailable {
substance: substance.to_string(),
calc_type: "Thermo (found Transport instead)".to_string(),
}),
}
}
pub fn extract_thermal_coeffs(
&mut self,
substance: &str,
temperature: f64,
) -> SubsDataResult<()> {
if temperature <= 0.0 {
let error = SubsDataError::InvalidTemperature(temperature);
return self.log_and_propagate(Err(error), substance, "extract_thermal_coeffs");
}
let result = self.with_thermo_calculator(substance, |thermo| {
thermo.extract_model_coefficients(temperature)
});
self.log_and_propagate(result, substance, "extract_thermal_coeffs")
}
pub fn extract_all_thermal_coeffs(&mut self, temperature: f64) -> SubsDataResult<()> {
if temperature <= 0.0 {
let error = SubsDataError::InvalidTemperature(temperature);
crate::log_error!(
self.logger,
&error,
"ALL_SUBSTANCES",
"extract_all_thermal_coeffs"
);
return Err(error);
}
for substance in self.substances.clone() {
let result = self.extract_thermal_coeffs(&substance, temperature);
self.log_and_propagate(result, &substance, "extract_all_thermal_coeffs")?;
}
Ok(())
}
pub fn is_coeffs_valid_for_T(
&mut self,
substance: &str,
temperature: f64,
) -> SubsDataResult<bool> {
if temperature <= 0.0 {
let error = SubsDataError::InvalidTemperature(temperature);
return self.log_and_propagate(Err(error), substance, "is_coeffs_valid_for_T");
}
let result = self.with_thermo_calculator(substance, |thermo| {
thermo.is_coeffs_valid_for_T(temperature)
});
self.log_and_propagate(result, substance, "is_coeffs_valid_for_T")
}
pub fn extract_coeffs_if_current_coeffs_not_valid(
&mut self,
substance: &str,
temperature: f64,
) -> SubsDataResult<bool> {
let check_flag = self.is_coeffs_valid_for_T(substance, temperature);
match check_flag {
Ok(flag) => match flag {
true => return Ok(true),
false => {
self.extract_thermal_coeffs(substance, temperature)?;
return self.log_and_propagate(
check_flag,
substance,
"extract_coeffs_if_current_coeffs_not_valid",
);
}
},
Err(error) => {
crate::log_error!(
self.logger,
&error,
"ALL_SUBSTANCES",
"extract_coeffs_if_current_coeffs_not_valid"
);
return Err(error);
}
}
}
pub fn extract_coeffs_if_current_coeffs_not_valid_for_all_subs(
&mut self,
temperature: f64,
) -> SubsDataResult<Vec<String>> {
let mut subs_with_changed_coeffs = Vec::new();
for substance in self.substances.clone() {
let flag = self.extract_coeffs_if_current_coeffs_not_valid(&substance, temperature)?;
if flag {
subs_with_changed_coeffs.push(substance.clone());
}
}
Ok(subs_with_changed_coeffs)
}
pub fn set_T_range_for_thermo(
&mut self,
substance: &str,
T_min: f64,
T_max: f64,
) -> SubsDataResult<()> {
let result =
self.with_thermo_calculator(substance, |thermo| thermo.set_T_interval(T_min, T_max));
self.log_and_propagate(result, substance, "set_T_range_for_thermo")
}
pub fn set_T_range_for_all_thermo(&mut self, T_min: f64, T_max: f64) -> SubsDataResult<()> {
for substance in self.substances.clone() {
let result = self.set_T_range_for_thermo(&substance, T_min, T_max);
self.log_and_propagate(result, &substance, "set_T_range_for_all_thermo")?;
}
Ok(())
}
pub fn parse_thermal_coeffs(&mut self, substance: &str) -> SubsDataResult<()> {
let result = self.with_thermo_calculator(substance, |thermo| thermo.parse_coefficients());
self.log_and_propagate(result, substance, "parse_thermal_coeffs")
}
pub fn parse_all_thermal_coeffs(&mut self) -> SubsDataResult<()> {
for substance in self.substances.clone() {
let result = self.parse_thermal_coeffs(&substance);
self.log_and_propagate(result, &substance, "parse_all_thermal_coeffs")?;
}
Ok(())
}
pub fn fitting_thermal_coeffs_for_T_interval(&mut self, substance: &str) -> SubsDataResult<()> {
let result =
self.with_thermo_calculator(substance, |thermo| thermo.fitting_coeffs_for_T_interval());
self.log_and_propagate(result, substance, "fitting_thermal_coeffs_for_T_interval")
}
pub fn fitting_all_thermal_coeffs_for_T_interval(&mut self) -> SubsDataResult<()> {
for substance in self.substances.clone() {
let result = self.fitting_thermal_coeffs_for_T_interval(&substance);
self.log_and_propagate(
result,
&substance,
"fitting_all_thermal_coeffs_for_T_interval",
)?;
}
Ok(())
}
pub fn integr_mean(&mut self, substance: &str) -> SubsDataResult<()> {
let result = self.with_thermo_calculator(substance, |thermo| thermo.integr_mean());
self.log_and_propagate(result, substance, "integr_mean")
}
pub fn calculate_thermo_properties(
&mut self,
substance: &str,
temperature: f64,
) -> SubsDataResult<(f64, f64, f64)> {
let result = self._calculate_thermo_properties_internal(substance, temperature);
self.log_and_propagate(result, substance, "calculate_thermo_properties")
}
fn _calculate_thermo_properties_internal(
&mut self,
substance: &str,
temperature: f64,
) -> SubsDataResult<(f64, f64, f64)> {
if temperature <= 0.0 {
return Err(SubsDataError::InvalidTemperature(temperature));
}
let state = self
.get_substance_search_state(substance)
.ok_or_else(|| SubsDataError::SubstanceNotFound(substance.to_string()))?;
if state.is_missing() {
return Err(SubsDataError::SubstanceNotFound(substance.to_string()));
}
let search_result = self
.get_canonical_search_result(substance, WhatIsFound::Thermo)
.ok_or_else(|| SubsDataError::CalculatorNotAvailable {
substance: substance.to_string(),
calc_type: "Thermo".to_string(),
})?;
match search_result {
SearchResult {
calculator: Some(CalculatorType::Thermo(thermo)),
..
} => {
let mut thermo = thermo.clone();
thermo.calculate_Cp_dH_dS(temperature)?;
let cp = thermo.get_Cp()?;
let dh = thermo.get_dh()?;
let ds = thermo.get_ds()?;
Self::validate_finite("Cp", cp)?;
Self::validate_finite("dH", dh)?;
Self::validate_finite("dS", ds)?;
Ok((cp, dh, ds))
}
SearchResult {
calculator: Some(CalculatorType::Transport(_)),
..
} => Err(SubsDataError::CalculatorNotAvailable {
substance: substance.to_string(),
calc_type: "Thermo (found Transport instead)".to_string(),
}),
SearchResult {
calculator: None, ..
} => Err(SubsDataError::CalculatorNotAvailable {
substance: substance.to_string(),
calc_type: "Thermo".to_string(),
}),
}
}
fn validate_finite(field: &str, value: f64) -> SubsDataResult<()> {
if value.is_finite() {
Ok(())
} else {
Err(SubsDataError::InvalidPhysicalValue {
field: field.to_string(),
value,
})
}
}
fn validate_finite_positive(field: &str, value: f64) -> SubsDataResult<()> {
if value.is_finite() && value > 0.0 {
Ok(())
} else {
Err(SubsDataError::InvalidPhysicalValue {
field: field.to_string(),
value,
})
}
}
pub fn set_M(
&mut self,
M_map: HashMap<String, f64>,
M_unit: Option<String>,
) -> SubsDataResult<()> {
for (substance, molar_mass) in &M_map {
Self::validate_finite_positive(&format!("molar_mass:{}", substance), *molar_mass)?;
}
self.molar_mass_by_substance = M_map;
self.Molar_mass_unit = M_unit;
self.invalidate_pressure_and_molar_mass_dependent_state();
Ok(())
}
pub fn set_P(&mut self, P: f64, P_unit: Option<String>) -> SubsDataResult<()> {
Self::validate_finite_positive("pressure", P)?;
self.P = Some(P);
self.P_unit = P_unit;
self.invalidate_pressure_and_molar_mass_dependent_state();
Ok(())
}
pub fn set_T(&mut self, T: f64) -> SubsDataResult<()> {
Self::validate_finite_positive("temperature", T)?;
if self.T == Some(T) {
return Ok(());
}
self.T = Some(T);
self.invalidate_temperature_dependent_state();
Ok(())
}
pub fn build_property_map<T, F>(
&mut self,
calculator_fn: F,
target_map: &mut HashMap<String, HashMap<DataType, Option<T>>>,
_property_types: &[DataType],
_empty_value_fn: fn() -> Option<T>,
) -> SubsDataResult<()>
where
F: Fn(&mut Self, &str) -> SubsDataResult<HashMap<DataType, Option<T>>>,
{
let mut rebuilt_map = HashMap::new();
for substance in self.substances.clone() {
let properties = calculator_fn(self, &substance)?;
rebuilt_map.insert(substance, properties);
}
*target_map = rebuilt_map;
Ok(())
}
fn calculate_single_thermo_properties(
&mut self,
substance: &str,
temperature: f64,
) -> SubsDataResult<HashMap<DataType, Option<f64>>> {
let (cp, dh, ds) = self._calculate_thermo_properties_internal(substance, temperature)?;
let mut property_map = HashMap::new();
property_map.insert(DataType::Cp, Some(cp));
property_map.insert(DataType::dH, Some(dh));
property_map.insert(DataType::dS, Some(ds));
Ok(property_map)
}
pub fn calculate_therm_map_of_properties(&mut self, temperature: f64) -> SubsDataResult<()> {
let mut temp_map = HashMap::new();
for substance in self.substances.clone() {
let result = self.calculate_single_thermo_properties(&substance, temperature);
let properties =
self.log_and_propagate(result, &substance, "calculate_therm_map_of_properties")?;
temp_map.insert(substance, properties);
}
self.therm_map_of_properties_values = temp_map;
Ok(())
}
fn calculate_single_thermo_functions(
&mut self,
substance: &str,
) -> SubsDataResult<HashMap<DataType, Option<Box<dyn Fn(f64) -> f64 + Send + Sync>>>> {
self.with_thermo_calculator(substance, |thermo| {
thermo.create_closures_Cp_dH_dS()?;
let mut function_map = HashMap::new();
function_map.insert(DataType::Cp_fun, thermo.get_C_fun().ok());
function_map.insert(DataType::dH_fun, thermo.get_dh_fun().ok());
function_map.insert(DataType::dS_fun, thermo.get_ds_fun().ok());
Ok(function_map)
})
}
pub fn calculate_therm_map_of_fun(&mut self) -> SubsDataResult<()> {
let mut temp_map = HashMap::new();
for substance in self.substances.clone() {
let result = self.calculate_single_thermo_functions(&substance);
let properties =
self.log_and_propagate(result, &substance, "calculate_therm_map_of_fun")?;
temp_map.insert(substance, properties);
}
self.therm_map_of_fun = temp_map;
Ok(())
}
fn calculate_single_thermo_symbolic(
&mut self,
substance: &str,
) -> SubsDataResult<HashMap<DataType, Option<Box<Expr>>>> {
self.with_thermo_calculator(substance, |thermo| {
thermo.create_sym_Cp_dH_dS()?;
let mut sym_map = HashMap::new();
sym_map.insert(DataType::Cp_sym, thermo.get_Cp_sym().ok().map(Box::new));
sym_map.insert(DataType::dH_sym, thermo.get_dh_sym().ok().map(Box::new));
sym_map.insert(DataType::dS_sym, thermo.get_ds_sym().ok().map(Box::new));
Ok(sym_map)
})
}
pub fn calculate_therm_map_of_sym(&mut self) -> SubsDataResult<()> {
let mut temp_map = HashMap::new();
for substance in self.substances.clone() {
let result = self.calculate_single_thermo_symbolic(&substance);
let properties =
self.log_and_propagate(result, &substance, "calculate_therm_map_of_sym")?;
temp_map.insert(substance, properties);
}
self.therm_map_of_sym = temp_map;
Ok(())
}
pub fn log_and_propagate<T>(
&mut self,
result: SubsDataResult<T>,
substance: &str,
function: &str,
) -> SubsDataResult<T> {
if let Err(ref e) = result {
crate::log_error!(self.logger, e, substance, function);
}
result
}
pub fn print_error_logs(&self) {
use crate::Thermodynamics::User_substances_error::ExceptionLogger;
self.logger.print_logs();
}
pub fn clear_error_logs(&mut self) {
use crate::Thermodynamics::User_substances_error::ExceptionLogger;
self.logger.clear_logs();
}
}