use crate::permittivity::RelativePermittivity;
use crate::Result;
use crate::*;
use core::fmt::{Display, Formatter};
use permittivity::{ConstantPermittivity, Permittivity};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(deny_unknown_fields)
)]
pub struct Medium {
permittivity: Permittivity,
salt: Option<(Salt, f64)>,
temperature: f64,
}
impl Medium {
pub const fn new(
temperature: f64,
permittivity: Permittivity,
salt: Option<(Salt, f64)>,
) -> Self {
Self {
permittivity,
salt,
temperature,
}
}
pub const fn neat_water(temperature: f64) -> Self {
Self {
permittivity: Permittivity::Water,
salt: None,
temperature,
}
}
pub const fn salt_water(temperature: f64, salt: Salt, molarity: f64) -> Self {
Self {
permittivity: Permittivity::Water,
salt: Some((salt, molarity)),
temperature,
}
}
pub fn molarity(&self) -> Option<f64> {
self.salt.as_ref().map(|(_, molarity)| molarity).copied()
}
pub fn set_molarity(&mut self, molality: f64) -> Result<()> {
if molality.is_sign_negative() || !molality.is_finite() {
return Err(crate::Error::InvalidMolarity);
}
let Some((salt, _)) = &self.salt else {
return Err(crate::Error::MissingSalt);
};
self.salt = Some((salt.clone(), molality));
Ok(())
}
pub fn bjerrum_length(&self) -> f64 {
bjerrum_length(
self.temperature,
self.permittivity.permittivity(self.temperature).unwrap(),
)
}
pub fn permittivity(&self) -> f64 {
self.permittivity.permittivity(self.temperature).unwrap()
}
}
impl Display for Medium {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Medium: 𝑇 = {:.2} K, εᵣ = {:.2}, λᴮ = {:.2} Å",
self.temperature,
self.permittivity.permittivity(self.temperature).unwrap(),
self.bjerrum_length(),
)?;
if let Some((salt, molarity)) = &self.salt {
write!(
f,
", 𝐼 = {:.2} mM, λᴰ = {:.2} Å, {:.2} M {}",
self.ionic_strength().unwrap() * 1e3,
self.debye_length().unwrap(),
molarity,
salt
)?;
};
Ok(())
}
}
impl Temperature for Medium {
fn temperature(&self) -> f64 {
self.temperature
}
fn set_temperature(&mut self, temperature: f64) -> crate::Result<()> {
if self.permittivity.temperature_is_ok(temperature) {
self.temperature = temperature;
Ok(())
} else {
Err(crate::Error::TemperatureOutOfRange)
}
}
}
impl RelativePermittivity for Medium {
fn permittivity(&self, temperature: f64) -> Result<f64> {
self.permittivity.permittivity(temperature)
}
}
impl IonicStrength for Medium {
fn ionic_strength(&self) -> Option<f64> {
self.salt
.as_ref()
.and_then(|salt| salt.0.ionic_strength(salt.1).ok())
}
}
impl From<Medium> for ConstantPermittivity {
fn from(medium: Medium) -> Self {
ConstantPermittivity::new(medium.permittivity())
}
}