use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug)]
pub enum MobilePhaseError {
InvalidVelocity(f64),
InvalidViscosity(f64),
}
impl fmt::Display for MobilePhaseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MobilePhaseError::InvalidVelocity(v) => {
write!(f, "mobile phase: velocity must be > 0, got {v}")
}
MobilePhaseError::InvalidViscosity(v) => {
write!(f, "mobile phase: viscosity must be > 0, got {v}")
}
}
}
}
impl std::error::Error for MobilePhaseError {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MobilePhase {
pub velocity: f64,
pub viscosity: Option<f64>,
}
impl MobilePhase {
pub fn new(velocity: f64, viscosity: Option<f64>) -> Result<Self, MobilePhaseError> {
if velocity <= 0.0 {
return Err(MobilePhaseError::InvalidVelocity(velocity));
}
if let Some(eta) = viscosity
&& eta <= 0.0
{
return Err(MobilePhaseError::InvalidViscosity(eta));
}
Ok(Self {
velocity,
viscosity,
})
}
#[inline]
pub fn interstitial_velocity(&self, porosity: f64) -> f64 {
self.velocity / porosity
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mobile_phase_valid() {
let mp = MobilePhase::new(1e-4, None).unwrap();
assert!((mp.velocity - 1e-4).abs() < 1e-15);
assert!(mp.viscosity.is_none());
}
#[test]
fn test_mobile_phase_with_viscosity() {
let mp = MobilePhase::new(1e-4, Some(1e-3)).unwrap();
assert!((mp.viscosity.unwrap() - 1e-3).abs() < 1e-15);
}
#[test]
fn test_mobile_phase_invalid_velocity() {
assert!(matches!(
MobilePhase::new(0.0, None),
Err(MobilePhaseError::InvalidVelocity(_))
));
assert!(matches!(
MobilePhase::new(-1e-4, None),
Err(MobilePhaseError::InvalidVelocity(_))
));
}
#[test]
fn test_mobile_phase_invalid_viscosity() {
assert!(matches!(
MobilePhase::new(1e-4, Some(0.0)),
Err(MobilePhaseError::InvalidViscosity(_))
));
assert!(matches!(
MobilePhase::new(1e-4, Some(-1e-3)),
Err(MobilePhaseError::InvalidViscosity(_))
));
}
#[test]
fn test_interstitial_velocity() {
let mp = MobilePhase::new(1e-4, None).unwrap();
assert!((mp.interstitial_velocity(0.4) - 2.5e-4).abs() < 1e-15);
}
#[test]
fn test_mobile_phase_serde_roundtrip() {
let mp = MobilePhase::new(1e-4, Some(1e-3)).unwrap();
let json = serde_json::to_string(&mp).unwrap();
let mp2: MobilePhase = serde_json::from_str(&json).unwrap();
assert!((mp.velocity - mp2.velocity).abs() < 1e-15);
assert!((mp.viscosity.unwrap() - mp2.viscosity.unwrap()).abs() < 1e-15);
}
}