use crate::constants::*;
use crate::model::link::LinkStatus;
use crate::model::link::{LinkCoefficients, LinkTrait};
use crate::model::options::{HeadlossFormula, SimulationOptions};
use crate::model::units::{Cfs, Ft, UnitConversion, UnitSystem};
use fastapprox;
use serde::{Deserialize, Serialize};
const A1: f64 = 3.141_592_653_589_793_4e3; const A2: f64 = 1.570_796_326_794_896_7e3; const A8: f64 = 4.618_413_198_590_667; const A9: f64 = -8.685_889_638_065_036e-1; const AB: f64 = 3.288_954_763_453_990_7e-3; const AC: f64 = -5.142_149_657_990_939e-3;
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Pipe {
pub diameter: Ft,
pub length: Ft,
pub roughness: f64, pub minor_loss: f64,
pub check_valve: bool,
pub headloss_formula: HeadlossFormula,
}
const H_EXPONENT: f64 = 1.852;
impl LinkTrait for Pipe {
#[inline]
fn coefficients(
&self,
q: Cfs,
r: f64,
_setting: f64,
status: LinkStatus,
_: f64,
_: f64,
_: f64,
_: f64,
) -> LinkCoefficients {
if status == LinkStatus::Closed || status == LinkStatus::TempClosed {
return LinkCoefficients::simple(1.0 / BIG_VALUE, q);
}
if self.headloss_formula == HeadlossFormula::DarcyWeisbach {
let (mut g_inv, mut y) = self.dw_coefficients(q, r);
if self.check_valve {
(g_inv, y) = Pipe::add_cv_head_loss(q, g_inv, y);
}
return LinkCoefficients::simple(g_inv, y);
}
let q_abs = q.abs();
let ml = self.minor_loss;
let n = H_EXPONENT;
let mut hgrad = n * r * q_abs.powf(n - 1.0);
let mut hloss = if hgrad < RQ_TOL {
hgrad = RQ_TOL;
hgrad * q_abs
} else {
hgrad * q_abs / n
};
if ml > 0.0 {
hloss += ml * q_abs.powi(2);
hgrad += 2.0 * ml * q_abs;
}
hloss *= q.signum();
let mut g_inv = 1.0 / hgrad;
let mut y = hloss / hgrad;
if self.check_valve {
(g_inv, y) = Pipe::add_cv_head_loss(q, g_inv, y);
}
LinkCoefficients::simple(g_inv, y)
}
fn resistance(&self) -> f64 {
match self.headloss_formula {
HeadlossFormula::HazenWilliams => {
4.727 * self.roughness.powf(-1.852) * self.diameter.powf(-4.871) * self.length
}
HeadlossFormula::DarcyWeisbach => {
self.length
/ 2.0
/ 32.2
/ self.diameter
/ (PI * self.diameter.powi(2) / 4.0).powi(2)
}
HeadlossFormula::ChezyManning => {
panic!("Chezy Manning headloss formula not yet implemented");
}
}
}
fn update_status(
&self,
_: f64,
status: LinkStatus,
_: f64,
_: f64,
_: f64,
_: f64,
_: f64,
) -> Option<LinkStatus> {
if status == LinkStatus::TempClosed {
return Some(LinkStatus::Open); }
None
}
fn initial_flow(&self) -> f64 {
0.25 * std::f64::consts::PI * self.diameter.powi(2)
}
}
impl Pipe {
#[inline]
fn add_cv_head_loss(q: Cfs, g_inv: f64, y: f64) -> (f64, f64) {
let hgrad = 1.0 / g_inv;
let hloss = y * hgrad;
let a = BIG_VALUE * q;
let b = (a * a + CV_HEAD_EPSILON).sqrt();
let hloss = hloss + (a - b) * 0.5;
let hgrad = hgrad + BIG_VALUE * (1.0 - a / b) * 0.5;
(1.0 / hgrad, hloss / hgrad)
}
fn dw_coefficients(&self, q: f64, r: f64) -> (f64, f64) {
let q_abs = q.abs();
let ml = self.minor_loss;
let e = (self.roughness / 1000.0) / self.diameter; let s = VISCOSITY * self.diameter; if q_abs <= A2 * s {
let r = 16.0 * PI * s * r;
let hloss = q * (r + ml * q_abs);
let hgrad = r + 2.0 * ml * q_abs;
(1.0 / hgrad, hloss / hgrad)
} else {
let (f, dfdq) = self.dw_friction_factor(q_abs, e, s);
let r1 = f * r + ml;
let hloss = r1 * q_abs * q;
let hgrad = (2.0 * r1 * q_abs) + (dfdq * r * q_abs.powi(2));
(1.0 / hgrad, hloss / hgrad)
}
}
#[inline(always)]
fn dw_friction_factor(&self, q: f64, e: f64, s: f64) -> (f64, f64) {
let w = q / s;
if w >= A1 {
let y1 = A8 / fastapprox::fast::pow(w as f32, 0.9) as f64;
let y2 = e / 3.7 + y1;
let y3 = A9 * fastapprox::fast::ln(y2 as f32) as f64;
let f = 1.0 / y3.powi(2);
let dfdq = 1.8 * f * y1 * A9 / y2 / y3 / q;
(f, dfdq)
} else {
let y2 = e / 3.7 + AB;
let y3 = A9 * fastapprox::fast::ln(y2 as f32) as f64;
let fa = 1.0 / (y3 * y3);
let fb = (2.0 + AC / (y2 * y3)) * fa;
let r = w / A2;
let x1 = 7.0 * fa - fb;
let x2 = 0.128 - 17.0 * fa + 2.5 * fb;
let x3 = -0.128 + 13.0 * fa - (fb + fb);
let x4 = 0.032 - 3.0 * fa + 0.5 * fb;
let f = x1 + r * (x2 + r * (x3 + r * x4));
let dfdq = (x2 + r * (2.0 * x3 + r * 3.0 * x4)) / s / A2;
(f, dfdq)
}
}
}
impl UnitConversion for Pipe {
fn convert_to_standard(&mut self, options: &SimulationOptions) {
if options.unit_system == UnitSystem::US {
self.diameter /= 12.0; } else {
self.diameter /= 1000.0; }
self.diameter /= options.unit_system.per_feet();
self.length /= options.unit_system.per_feet();
if self.headloss_formula == HeadlossFormula::DarcyWeisbach {
self.roughness /= options.unit_system.per_feet();
}
}
fn convert_from_standard(&mut self, options: &SimulationOptions) {
if options.unit_system == UnitSystem::US {
self.diameter *= 12.0; } else {
self.diameter *= options.unit_system.per_feet() * 1000.0; }
self.length *= options.unit_system.per_feet();
if self.headloss_formula == HeadlossFormula::DarcyWeisbach {
self.roughness *= options.unit_system.per_feet();
}
}
}