use crate::fluid::Fluid;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FlowRegime {
Laminar,
Transitional,
Turbulent,
}
impl FlowRegime {
pub fn from_reynolds(re: f64) -> Self {
if re < 2300.0 {
FlowRegime::Laminar
} else if re < 4000.0 {
FlowRegime::Transitional
} else {
FlowRegime::Turbulent
}
}
}
#[derive(Debug, Clone)]
pub struct DarcyWeisbach {
pub fluid: Fluid,
pub diameter_m: f64,
pub length_m: f64,
pub velocity_m_s: f64,
pub roughness_m: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PipeFlowResult {
pub reynolds_number: f64,
pub flow_regime: FlowRegime,
pub friction_factor: f64,
pub pressure_loss_pa: f64,
pub head_loss_m: f64,
pub relative_roughness: f64,
}
impl DarcyWeisbach {
pub fn new(
fluid: &Fluid,
diameter_m: f64,
length_m: f64,
velocity_m_s: f64,
roughness_m: f64,
) -> Self {
Self {
fluid: fluid.clone(),
diameter_m,
length_m,
velocity_m_s,
roughness_m,
}
}
#[inline]
pub fn reynolds_number(&self) -> f64 {
self.fluid.density_kg_m3 * self.velocity_m_s * self.diameter_m
/ self.fluid.dynamic_viscosity_pa_s
}
pub fn friction_factor(&self, re: f64) -> f64 {
if re < 2300.0 {
64.0 / re.max(1e-10)
} else if re < 4000.0 {
let f_lam = 64.0 / 2300.0;
let f_turb = self.colebrook_white(4000.0);
let t = (re - 2300.0) / (4000.0 - 2300.0);
f_lam + t * (f_turb - f_lam)
} else {
self.colebrook_white(re)
}
}
fn colebrook_white(&self, re: f64) -> f64 {
let rel_rough = self.roughness_m / self.diameter_m;
if rel_rough == 0.0 && re > 0.0 {
return smooth_pipe_friction(re);
}
let log_arg = rel_rough / 3.7 + 5.74 / pow_f64(re, 0.9);
let f_init = 0.25 / (log10(log_arg) * log10(log_arg));
let mut f = f_init;
for _ in 0..15 {
let sqrt_f = f.sqrt();
let rhs = -2.0 * log10(rel_rough / 3.7 + 2.51 / (re * sqrt_f));
let f_new = 1.0 / (rhs * rhs);
if (f_new - f).abs() < 1e-12 * f {
return f_new;
}
f = f_new;
}
f
}
pub fn calculate(&self) -> PipeFlowResult {
let re = self.reynolds_number();
let regime = FlowRegime::from_reynolds(re);
let f = self.friction_factor(re);
let relative_roughness = self.roughness_m / self.diameter_m;
let dynamic_pressure = 0.5 * self.fluid.density_kg_m3 * self.velocity_m_s * self.velocity_m_s;
let pressure_loss_pa = f * (self.length_m / self.diameter_m) * dynamic_pressure;
let head_loss_m = pressure_loss_pa / (self.fluid.density_kg_m3 * crate::fluid::G);
PipeFlowResult {
reynolds_number: re,
flow_regime: regime,
friction_factor: f,
pressure_loss_pa,
head_loss_m,
relative_roughness,
}
}
}
fn smooth_pipe_friction(re: f64) -> f64 {
let mut f = 0.316 / re.powf(0.25);
for _ in 0..20 {
let sqrt_f = f.sqrt();
let rhs = 2.0 * log10(re * sqrt_f) - 0.8;
let f_new = 1.0 / (rhs * rhs);
if (f_new - f).abs() < 1e-12 * f {
return f_new;
}
f = f_new;
}
f
}
#[inline]
fn log10(x: f64) -> f64 {
x.log10()
}
#[inline]
fn pow_f64(x: f64, n: f64) -> f64 {
x.powf(n)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fluid::Fluid;
#[test]
fn test_laminar_flow_regime() {
assert_eq!(FlowRegime::from_reynolds(1000.0), FlowRegime::Laminar);
assert_eq!(FlowRegime::from_reynolds(2299.0), FlowRegime::Laminar);
}
#[test]
fn test_turbulent_flow_regime() {
assert_eq!(FlowRegime::from_reynolds(4000.0), FlowRegime::Turbulent);
assert_eq!(FlowRegime::from_reynolds(100_000.0), FlowRegime::Turbulent);
}
#[test]
fn test_laminar_friction_factor() {
let fluid = Fluid::water(20.0);
let dw = DarcyWeisbach::new(&fluid, 0.05, 100.0, 0.005, 0.0);
let re = dw.reynolds_number();
assert!(re < 2300.0, "Re must be laminar, got {re:.1}");
let f = dw.friction_factor(re);
assert!((f - 64.0 / re).abs() < 1e-10, "laminar f = {f:.6}, expected {:.6}", 64.0 / re);
}
#[test]
fn test_turbulent_reynolds() {
let fluid = Fluid::water(20.0);
let dw = DarcyWeisbach::new(&fluid, 0.05, 100.0, 1.0, 1.5e-6);
let re = dw.reynolds_number();
assert!((re - 49_800.0).abs() < 500.0, "Re = {re:.0}");
}
#[test]
fn test_pressure_loss_positive() {
let fluid = Fluid::water(20.0);
let dw = DarcyWeisbach::new(&fluid, 0.05, 100.0, 1.0, 1.5e-6);
let result = dw.calculate();
assert!(result.pressure_loss_pa > 0.0);
assert!(result.head_loss_m > 0.0);
}
#[test]
fn test_moody_chart_known_point() {
let fluid = Fluid::water(20.0);
let dw = DarcyWeisbach::new(&fluid, 0.1, 100.0, 1.004, 1.0e-4);
let re = dw.reynolds_number();
let f = dw.friction_factor(re);
assert!((f - 0.022).abs() < 0.003, "f at Re=1e5, ε/D=0.001 = {f:.4}");
}
}