#![allow(missing_docs, non_camel_case_types)]
mod binary_mix;
mod custom_mix;
mod incomp_pure;
mod predefined_mix;
mod pure;
mod with_backend;
use std::borrow::Cow;
pub use binary_mix::*;
pub use custom_mix::*;
pub use incomp_pure::*;
pub use predefined_mix::*;
pub use pure::*;
pub use with_backend::*;
use crate::{
config,
fluid::backend::{Backend, DefaultBackend},
io::SubstanceParam,
native::CoolProp,
};
#[derive(Clone, Debug, PartialEq)]
pub enum Substance {
Pure(Pure),
IncompPure(IncompPure),
PredefinedMix(PredefinedMix),
BinaryMix(BinaryMix),
CustomMix(CustomMix),
}
impl Substance {
#[must_use]
pub fn with_backend(&self, backend: impl Into<Backend>) -> SubstanceWithBackend {
SubstanceWithBackend { substance: self.clone(), backend: backend.into() }
}
#[must_use]
pub fn into_with_backend(self, backend: impl Into<Backend>) -> SubstanceWithBackend {
SubstanceWithBackend { substance: self, backend: backend.into() }
}
#[must_use]
pub fn with_default_backend(&self) -> SubstanceWithBackend {
self.with_backend(self.default_backend())
}
#[must_use]
pub fn into_with_default_backend(self) -> SubstanceWithBackend {
let default_backend = self.default_backend();
self.into_with_backend(default_backend)
}
#[must_use]
pub fn name(&self) -> Cow<'static, str> {
match self {
Substance::Pure(pure) => Cow::Borrowed(pure.into()),
Substance::IncompPure(incomp_pure) => Cow::Borrowed(incomp_pure.into()),
Substance::PredefinedMix(predefined_mix) => Cow::Borrowed(predefined_mix.into()),
Substance::BinaryMix(binary_mix) => {
let name = format!("{}[{}]", binary_mix.kind.as_ref(), binary_mix.fraction);
Cow::Owned(name)
}
Substance::CustomMix(custom_mix) => {
let mix = custom_mix.clone().into_mole_based();
let mut components: Vec<_> = mix.components().iter().collect();
components.sort_by(|left, right| {
right.1.total_cmp(left.1).then_with(|| left.0.as_ref().cmp(right.0.as_ref()))
});
let name = components
.into_iter()
.map(|(substance, fraction)| format!("{}[{}]", substance.as_ref(), fraction))
.collect::<Vec<_>>()
.join("&");
Cow::Owned(name)
}
}
}
#[must_use]
pub fn composition_id(&self) -> Cow<'static, str> {
match self {
Substance::BinaryMix(binary_mix) => Cow::Borrowed(binary_mix.kind.into()),
Substance::CustomMix(custom_mix) => {
let mix = custom_mix.clone().into_mole_based();
let mut components: Vec<_> = mix.components().iter().collect();
components.sort_by(|left, right| {
right.1.total_cmp(left.1).then_with(|| left.0.as_ref().cmp(right.0.as_ref()))
});
let name = components
.into_iter()
.map(|component| component.0.as_ref())
.collect::<Vec<_>>()
.join("&");
Cow::Owned(name)
}
_ => self.name(),
}
}
#[must_use]
pub fn is_pure(&self) -> bool {
matches!(self, Substance::Pure(_) | Substance::IncompPure(_))
}
#[must_use]
pub fn aliases(&self) -> Vec<String> {
let sep = config::read().list_punctuation;
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::Aliases)
.map(|aliases| aliases.split(sep).map(|s| s.trim().to_string()).collect())
.unwrap_or_default()
}
#[must_use]
pub fn refprop_name(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::RefpropName)
}
#[must_use]
pub fn cas(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::Cas)
}
#[allow(clippy::doc_markdown)]
#[must_use]
pub fn inchi(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::Inchi)
}
#[allow(clippy::doc_markdown)]
#[must_use]
pub fn inchi_key(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::InchiKey)
}
#[must_use]
pub fn chemspider_id(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::ChemSpiderId)
}
#[must_use]
pub fn smiles(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::Smiles)
}
#[must_use]
pub fn ashrae34(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::Ashrae34)
}
#[must_use]
pub fn two_d_png_url(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::TwoDPngUrl)
}
#[must_use]
pub fn bibtex_eos(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::BibtexEos)
}
#[must_use]
pub fn bibtex_ideal_gas_specific_heat(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::BibtexCp0)
}
#[must_use]
pub fn bibtex_conductivity(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::BibtexConductivity)
}
#[must_use]
pub fn bibtex_melting_line(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::BibtexMeltingLine)
}
#[must_use]
pub fn bibtex_surface_tension(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::BibtexSurfaceTension)
}
#[must_use]
pub fn bibtex_viscosity(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::BibtexViscosity)
}
#[must_use]
pub fn formula(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::Formula)
}
#[must_use]
pub fn metadata(&self) -> Option<String> {
CoolProp::get_substance_param(self.composition_id(), SubstanceParam::Metadata)
}
}
impl From<Pure> for Substance {
fn from(value: Pure) -> Self {
Self::Pure(value)
}
}
impl From<IncompPure> for Substance {
fn from(value: IncompPure) -> Self {
Self::IncompPure(value)
}
}
impl From<PredefinedMix> for Substance {
fn from(value: PredefinedMix) -> Self {
Self::PredefinedMix(value)
}
}
impl From<BinaryMix> for Substance {
fn from(value: BinaryMix) -> Self {
Self::BinaryMix(value)
}
}
impl From<CustomMix> for Substance {
fn from(value: CustomMix) -> Self {
Self::CustomMix(value)
}
}
#[cfg(test)]
mod tests {
use rstest::*;
use super::*;
use crate::fluid::backend::BaseBackend;
#[test]
fn with_backend() {
let sut: Substance = Pure::Water.into();
let res = sut.with_backend(BaseBackend::If97);
assert_eq!(res, SubstanceWithBackend { substance: sut, backend: BaseBackend::If97.into() });
}
#[test]
fn with_default_backend() {
let sut: Substance = Pure::Water.into();
let res = sut.with_default_backend();
assert_eq!(res, SubstanceWithBackend { substance: sut, backend: BaseBackend::Heos.into() });
}
#[test]
fn into_with_backend() {
let sut: Substance = Pure::Water.into();
let res = sut.into_with_backend(BaseBackend::If97);
assert_eq!(
res,
SubstanceWithBackend {
substance: Pure::Water.into(),
backend: BaseBackend::If97.into()
}
);
}
#[test]
fn into_with_default_backend() {
let sut: Substance = Pure::Water.into();
let res = sut.into_with_default_backend();
assert_eq!(
res,
SubstanceWithBackend {
substance: Pure::Water.into(),
backend: BaseBackend::Heos.into()
}
);
}
#[rstest]
#[case(Pure::Water, "Water")]
#[case(IncompPure::Water, "Water")]
#[case(PredefinedMix::R444A, "R444A.mix")]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), "MPG[0.4]")]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
"Water[0.8]&Ethanol[0.2]"
)]
#[case(
CustomMix::mole_based(
[(Pure::Methanol, 0.1), (Pure::Ethanol, 0.1), (Pure::Water, 0.8)]
).unwrap(),
"Water[0.8]&Ethanol[0.1]&Methanol[0.1]"
)]
fn name(#[case] sut: impl Into<Substance>, #[case] expected: &str) {
let sut: Substance = sut.into();
let res = sut.name();
assert_eq!(res, expected);
}
#[rstest]
#[case(Pure::Water, "Water")]
#[case(IncompPure::Water, "Water")]
#[case(PredefinedMix::R444A, "R444A.mix")]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), "MPG")]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
"Water&Ethanol"
)]
#[case(
CustomMix::mole_based(
[(Pure::Methanol, 0.1), (Pure::Ethanol, 0.1), (Pure::Water, 0.8)]
).unwrap(),
"Water&Ethanol&Methanol"
)]
fn composition_id(#[case] sut: impl Into<Substance>, #[case] expected: &str) {
let sut: Substance = sut.into();
let res = sut.composition_id();
assert_eq!(res, expected);
}
#[rstest]
#[case(Pure::Water, true)]
#[case(IncompPure::Water, true)]
#[case(PredefinedMix::R444A, false)]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), false)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
false
)]
#[case(
CustomMix::mole_based(
[(Pure::Methanol, 0.1), (Pure::Ethanol, 0.1), (Pure::Water, 0.8)]
).unwrap(),
false
)]
fn is_pure(#[case] sut: impl Into<Substance>, #[case] expected: bool) {
let sut: Substance = sut.into();
let res = sut.is_pure();
assert_eq!(res, expected);
}
#[rstest]
#[case(Pure::Water, vec!["water", "WATER", "H2O", "h2o", "R718"])]
#[case(IncompPure::Water, vec!["water", "WATER", "H2O", "h2o", "R718"])]
#[case(PredefinedMix::R444A, Vec::new())]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), Vec::new())]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
vec!["water", "WATER", "H2O", "h2o", "R718"]
)]
fn aliases(#[case] sut: impl Into<Substance>, #[case] expected: Vec<&str>) {
let sut: Substance = sut.into();
let res = sut.aliases();
assert_eq!(res, expected);
}
#[rstest]
#[case(Pure::Water, Some("WATER"))]
#[case(IncompPure::Water, Some("WATER"))]
#[case(PredefinedMix::R444A, Some("R32"))]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
Some("WATER")
)]
fn refprop_name(#[case] sut: impl Into<Substance>, #[case] expected: Option<&str>) {
let sut: Substance = sut.into();
let res = sut.refprop_name();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::Water, Some("7732-18-5"))]
#[case(IncompPure::Water, Some("7732-18-5"))]
#[case(Pure::R32, Some("75-10-5"))]
#[case(PredefinedMix::R444A, Some("75-10-5"))]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
Some("7732-18-5")
)]
fn cas(#[case] sut: impl Into<Substance>, #[case] expected: Option<&str>) {
let sut: Substance = sut.into();
let res = sut.cas();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::Water, Some("InChI=1S/H2O/h1H2"))]
#[case(IncompPure::Water, Some("InChI=1S/H2O/h1H2"))]
#[case(Pure::R32, Some("InChI=1S/CH2F2/c2-1-3/h1H2"))]
#[case(PredefinedMix::R444A, Some("InChI=1S/CH2F2/c2-1-3/h1H2"))]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
Some("InChI=1S/H2O/h1H2")
)]
fn inchi(#[case] sut: impl Into<Substance>, #[case] expected: Option<&str>) {
let sut: Substance = sut.into();
let res = sut.inchi();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::Water, Some("XLYOFNOQVPJJNP-UHFFFAOYSA-N"))] #[case(IncompPure::Water, Some("XLYOFNOQVPJJNP-UHFFFAOYSA-N"))] #[case(Pure::R32, Some("RWRIWBAIICGTTQ-UHFFFAOYSA-N"))] #[case(PredefinedMix::R444A, Some("RWRIWBAIICGTTQ-UHFFFAOYSA-N"))] #[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
Some("XLYOFNOQVPJJNP-UHFFFAOYSA-N") )]
fn inchi_key(#[case] sut: impl Into<Substance>, #[case] expected: Option<&str>) {
let sut: Substance = sut.into();
let res = sut.inchi_key();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::Water, Some("937"))]
#[case(IncompPure::Water, Some("937"))]
#[case(Pure::R32, Some("6105"))]
#[case(PredefinedMix::R444A, Some("6105"))]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
Some("937")
)]
fn chemspider_id(#[case] sut: impl Into<Substance>, #[case] expected: Option<&str>) {
let sut: Substance = sut.into();
let res = sut.chemspider_id();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::Water, Some("O"))]
#[case(IncompPure::Water, Some("O"))]
#[case(Pure::R32, Some("C(F)F"))]
#[case(PredefinedMix::R444A, Some("C(F)F"))]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
Some("O")
)]
fn smiles(#[case] sut: impl Into<Substance>, #[case] expected: Option<&str>) {
let sut: Substance = sut.into();
let res = sut.smiles();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::Water, Some("A1"))]
#[case(IncompPure::Water, Some("A1"))]
#[case(Pure::R32, Some("A2"))]
#[case(PredefinedMix::R444A, Some("A2"))]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
Some("A1")
)]
fn ashrae34(#[case] sut: impl Into<Substance>, #[case] expected: Option<&str>) {
let sut: Substance = sut.into();
let res = sut.ashrae34();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::Water, Some("http://www.chemspider.com/ImagesHandler.ashx?id=937"))]
#[case(IncompPure::Water, Some("http://www.chemspider.com/ImagesHandler.ashx?id=937"))]
#[case(Pure::R32, Some("http://www.chemspider.com/ImagesHandler.ashx?id=6105"))]
#[case(PredefinedMix::R444A, Some("http://www.chemspider.com/ImagesHandler.ashx?id=6105"))]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
Some("http://www.chemspider.com/ImagesHandler.ashx?id=937")
)]
fn two_d_png_url(#[case] sut: impl Into<Substance>, #[case] expected: Option<&str>) {
let sut: Substance = sut.into();
let res = sut.two_d_png_url();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::Water, Some("Wagner-JPCRD-2002"))] #[case(IncompPure::Water, Some("Wagner-JPCRD-2002"))] #[case(Pure::R32, Some("TillnerRoth-JPCRD-1997"))] #[case(PredefinedMix::R444A, Some("TillnerRoth-JPCRD-1997"))] #[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
Some("Wagner-JPCRD-2002") )]
fn bibtex_eos(#[case] sut: impl Into<Substance>, #[case] expected: Option<&str>) {
let sut: Substance = sut.into();
let res = sut.bibtex_eos();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::R13, Some("Platzer-BOOK-1990"))] #[case(IncompPure::Water, None)]
#[case(Pure::R32, None)]
#[case(PredefinedMix::R444A, None)]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::R22, 0.2), (Pure::R13, 0.8)]).unwrap(),
None
)]
fn bibtex_ideal_gas_specific_heat(
#[case] sut: impl Into<Substance>,
#[case] expected: Option<&str>,
) {
let sut: Substance = sut.into();
let res = sut.bibtex_ideal_gas_specific_heat();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::Water, Some("Huber-JPCRD-2012"))] #[case(IncompPure::Water, Some("Huber-JPCRD-2012"))] #[case(Pure::R32, Some("Huber-IECR-2003"))] #[case(PredefinedMix::R444A, Some("Huber-IECR-2003"))] #[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
Some("Huber-JPCRD-2012") )]
fn bibtex_conductivity(#[case] sut: impl Into<Substance>, #[case] expected: Option<&str>) {
let sut: Substance = sut.into();
let res = sut.bibtex_conductivity();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::Water, Some("IAPWS-Melting-2011"))]
#[case(IncompPure::Water, Some("IAPWS-Melting-2011"))]
#[case(Pure::R32, None)]
#[case(PredefinedMix::R444A, None)]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
Some("IAPWS-Melting-2011")
)]
fn bibtex_melting_line(#[case] sut: impl Into<Substance>, #[case] expected: Option<&str>) {
let sut: Substance = sut.into();
let res = sut.bibtex_melting_line();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::Water, Some("Mulero-JPCRD-2012"))] #[case(IncompPure::Water, Some("Mulero-JPCRD-2012"))] #[case(Pure::R32, Some("Mulero-JPCRD-2012"))] #[case(PredefinedMix::R444A, Some("Mulero-JPCRD-2012"))] #[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
Some("Mulero-JPCRD-2012") )]
fn bibtex_surface_tension(#[case] sut: impl Into<Substance>, #[case] expected: Option<&str>) {
let sut: Substance = sut.into();
let res = sut.bibtex_surface_tension();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::Water, Some("Huber-JPCRD-2009"))] #[case(IncompPure::Water, Some("Huber-JPCRD-2009"))] #[case(Pure::R32, Some("Bell-PURDUE-2016-ETA"))]
#[case(PredefinedMix::R444A, Some("Bell-PURDUE-2016-ETA"))]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
Some("Huber-JPCRD-2009") )]
fn bibtex_viscosity(#[case] sut: impl Into<Substance>, #[case] expected: Option<&str>) {
let sut: Substance = sut.into();
let res = sut.bibtex_viscosity();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::Water, Some("H_{2}O_{1}"))]
#[case(IncompPure::Water, Some("H_{2}O_{1}"))]
#[case(Pure::R32, Some("C_{1}F_{2}H_{2}"))]
#[case(PredefinedMix::R444A, Some("C_{1}F_{2}H_{2}"))]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), None)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
Some("H_{2}O_{1}")
)]
fn formula(#[case] sut: impl Into<Substance>, #[case] expected: Option<&str>) {
let sut: Substance = sut.into();
let res = sut.formula();
assert_eq!(res.as_deref(), expected);
}
#[rstest]
#[case(Pure::Water, true)]
#[case(IncompPure::Water, true)]
#[case(Pure::R32, true)]
#[case(PredefinedMix::R444A, true)]
#[case(BinaryMixKind::MPG.with_fraction(0.4).unwrap(), false)]
#[case(
CustomMix::mole_based([(Pure::Ethanol, 0.2), (Pure::Water, 0.8)]).unwrap(),
true
)]
fn metadata(#[case] sut: impl Into<Substance>, #[case] is_some: bool) {
let sut: Substance = sut.into();
let res = sut.metadata();
assert_eq!(res.is_some(), is_some);
}
}