use crate::fluid::{Fluid, G, P_ATM};
use crate::pipe::DarcyWeisbach;
#[derive(Debug, Clone)]
pub struct VacuumLift {
pub fluid: Fluid,
pub pipe_radius_m: f64,
pub target_height_m: f64,
pub roughness_m: f64,
pub flow_velocity_m_s: f64,
pub ambient_pressure_pa: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct VacuumResult {
pub target_height_m: f64,
pub atmospheric_max_lift_m: f64,
pub theoretical_max_lift_m: f64,
pub hydrostatic_pressure_pa: f64,
pub friction_loss_pa: f64,
pub vapor_pressure_pa: f64,
pub net_atmospheric_pa: f64,
pub achievable_by_atmosphere: bool,
pub achievable_with_pump: bool,
pub required_pump_pressure_pa: f64,
pub suction_pressure_pa_abs: f64,
pub reynolds_number: f64,
pub friction_factor: f64,
}
impl VacuumLift {
pub fn new(fluid: Fluid, pipe_radius_m: f64, target_height_m: f64) -> Self {
Self {
fluid,
pipe_radius_m,
target_height_m,
roughness_m: 1.5e-6,
flow_velocity_m_s: 0.0,
ambient_pressure_pa: P_ATM,
}
}
pub fn roughness(mut self, roughness_m: f64) -> Self {
self.roughness_m = roughness_m;
self
}
pub fn flow_velocity(mut self, velocity_m_s: f64) -> Self {
self.flow_velocity_m_s = velocity_m_s;
self
}
pub fn ambient_pressure(mut self, pressure_pa: f64) -> Self {
self.ambient_pressure_pa = pressure_pa;
self
}
pub fn calculate(&self) -> VacuumResult {
let rho = self.fluid.density_kg_m3;
let p_vap = self.fluid.vapor_pressure_pa;
let h = self.target_height_m;
let diameter_m = 2.0 * self.pipe_radius_m;
let (friction_loss_pa, reynolds_number, friction_factor) =
if self.flow_velocity_m_s > 0.0 {
let dw = DarcyWeisbach::new(
&self.fluid,
diameter_m,
h, self.flow_velocity_m_s,
self.roughness_m,
);
let res = dw.calculate();
(res.pressure_loss_pa, res.reynolds_number, res.friction_factor)
} else {
(0.0, 0.0, 0.0)
};
let net_atmospheric_pa = self.ambient_pressure_pa - p_vap;
let hydrostatic_pressure_pa = rho * G * h;
let atmospheric_max_lift_m = net_atmospheric_pa / (rho * G);
let theoretical_max_lift_m = f64::INFINITY;
let total_required_pa = hydrostatic_pressure_pa + friction_loss_pa + p_vap;
let atmosphere_provides_pa = self.ambient_pressure_pa;
let surplus_pa = atmosphere_provides_pa - total_required_pa;
let achievable_by_atmosphere = surplus_pa >= 0.0;
let achievable_with_pump = true;
let required_pump_pressure_pa = if achievable_by_atmosphere {
0.0
} else {
-surplus_pa };
let suction_pressure_pa_abs =
self.ambient_pressure_pa + required_pump_pressure_pa
- hydrostatic_pressure_pa
- friction_loss_pa;
VacuumResult {
target_height_m: h,
atmospheric_max_lift_m,
theoretical_max_lift_m,
hydrostatic_pressure_pa,
friction_loss_pa,
vapor_pressure_pa: p_vap,
net_atmospheric_pa,
achievable_by_atmosphere,
achievable_with_pump,
required_pump_pressure_pa,
suction_pressure_pa_abs,
reynolds_number,
friction_factor,
}
}
}
impl VacuumResult {
#[inline]
pub fn required_pump_pressure_bar(&self) -> f64 {
self.required_pump_pressure_pa / 100_000.0
}
#[inline]
pub fn required_pump_pressure_psi(&self) -> f64 {
self.required_pump_pressure_pa / 6_894.757
}
#[inline]
pub fn required_pump_head_m(&self, density_kg_m3: f64) -> f64 {
self.required_pump_pressure_pa / (density_kg_m3 * crate::fluid::G)
}
#[inline]
pub fn atmospheric_max_lift_ft(&self) -> f64 {
self.atmospheric_max_lift_m * 3.280_84
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fluid::Fluid;
#[test]
fn test_atmospheric_max_lift_20c() {
let fluid = Fluid::water(20.0);
let lift = VacuumLift::new(fluid, 0.05, 5.0);
let result = lift.calculate();
assert!(
(result.atmospheric_max_lift_m - 10.18).abs() < 0.2,
"atm max lift = {:.3} m",
result.atmospheric_max_lift_m
);
}
#[test]
fn test_5m_lift_achievable_by_atmosphere() {
let fluid = Fluid::water(20.0);
let lift = VacuumLift::new(fluid, 0.05, 5.0);
let result = lift.calculate();
assert!(result.achievable_by_atmosphere, "5m should be achievable by atmosphere");
assert_eq!(result.required_pump_pressure_pa, 0.0);
}
#[test]
fn test_50m_lift_requires_pump() {
let fluid = Fluid::water(20.0);
let lift = VacuumLift::new(fluid, 0.05, 50.0);
let result = lift.calculate();
assert!(!result.achievable_by_atmosphere, "50m requires a pump");
assert!(result.achievable_with_pump, "50m is achievable with pump");
assert!(result.required_pump_pressure_pa > 0.0);
}
#[test]
fn test_1000m_lift_requires_pump() {
let fluid = Fluid::water(20.0);
let lift = VacuumLift::new(fluid, 0.05, 1000.0);
let result = lift.calculate();
assert!(!result.achievable_by_atmosphere);
assert!(result.achievable_with_pump);
assert!(
(result.required_pump_pressure_pa - 9_79_0000.0).abs() < 100_000.0,
"pump pressure for 1000m = {:.0} Pa",
result.required_pump_pressure_pa
);
}
#[test]
fn test_pump_head_conversion() {
let fluid = Fluid::water(20.0);
let lift = VacuumLift::new(fluid.clone(), 0.05, 50.0);
let result = lift.calculate();
let head = result.required_pump_head_m(fluid.density_kg_m3);
assert!((head - (50.0 - result.atmospheric_max_lift_m)).abs() < 1.0);
}
#[test]
fn test_high_altitude_reduces_max_lift() {
let fluid_sea = Fluid::water(20.0);
let fluid_alt = Fluid::water(20.0);
let sea_result = VacuumLift::new(fluid_sea, 0.05, 5.0).calculate();
let alt_result = VacuumLift::new(fluid_alt, 0.05, 5.0)
.ambient_pressure(74_682.0)
.calculate();
assert!(
alt_result.atmospheric_max_lift_m < sea_result.atmospheric_max_lift_m,
"altitude reduces max lift"
);
}
}