use core::f64::consts::PI;
use crate::fluid::G;
const M3S_TO_MGD: f64 = 22.824_5;
const M3S_TO_MLD: f64 = 86.4;
const M3S_TO_LPM: f64 = 60_000.0;
const M3_TO_AF: f64 = 1.0 / 1233.48;
const SECONDS_PER_YEAR: f64 = 365.25 * 24.0 * 3600.0;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FlowRate {
pub m3_per_s: f64,
}
impl FlowRate {
#[inline]
pub fn from_m3s(q: f64) -> Self {
Self { m3_per_s: q }
}
#[inline]
pub fn from_lpm(lpm: f64) -> Self {
Self { m3_per_s: lpm / M3S_TO_LPM }
}
#[inline]
pub fn from_mgd(mgd: f64) -> Self {
Self { m3_per_s: mgd / M3S_TO_MGD }
}
#[inline]
pub fn to_mgd(&self) -> f64 {
self.m3_per_s * M3S_TO_MGD
}
#[inline]
pub fn to_mld(&self) -> f64 {
self.m3_per_s * M3S_TO_MLD
}
#[inline]
pub fn to_lpm(&self) -> f64 {
self.m3_per_s * M3S_TO_LPM
}
#[inline]
pub fn annual_volume_m3(&self) -> f64 {
self.m3_per_s * SECONDS_PER_YEAR
}
#[inline]
pub fn annual_volume_af(&self) -> f64 {
self.annual_volume_m3() * M3_TO_AF
}
}
#[inline]
pub fn pipe_area(diameter_m: f64) -> f64 {
PI * (diameter_m / 2.0) * (diameter_m / 2.0)
}
#[inline]
pub fn flow_rate(diameter_m: f64, velocity_m_s: f64) -> f64 {
pipe_area(diameter_m) * velocity_m_s
}
#[inline]
pub fn velocity(diameter_m: f64, flow_rate_m3s: f64) -> f64 {
flow_rate_m3s / pipe_area(diameter_m)
}
#[inline]
pub fn required_diameter(flow_rate_m3s: f64, velocity_m_s: f64) -> f64 {
let area = flow_rate_m3s / velocity_m_s;
2.0 * (area / PI).sqrt()
}
#[inline]
pub fn mass_flow_rate(density_kg_m3: f64, flow_rate_m3s: f64) -> f64 {
density_kg_m3 * flow_rate_m3s
}
#[inline]
pub fn bernoulli_pressure(
p1_pa: f64,
v1_m_s: f64,
h1_m: f64,
v2_m_s: f64,
h2_m: f64,
density_kg_m3: f64,
) -> f64 {
p1_pa
+ 0.5 * density_kg_m3 * (v1_m_s * v1_m_s - v2_m_s * v2_m_s)
+ density_kg_m3 * G * (h1_m - h2_m)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PumpPower {
pub hydraulic_w: f64,
pub shaft_w: f64,
pub efficiency: f64,
}
impl PumpPower {
#[inline]
pub fn shaft_kw(&self) -> f64 {
self.shaft_w / 1e3
}
#[inline]
pub fn shaft_mw(&self) -> f64 {
self.shaft_w / 1e6
}
#[inline]
pub fn shaft_gw(&self) -> f64 {
self.shaft_w / 1e9
}
}
pub fn pump_power(
density_kg_m3: f64,
flow_rate_m3s: f64,
total_head_m: f64,
efficiency: f64,
) -> PumpPower {
let hydraulic_w = density_kg_m3 * G * flow_rate_m3s * total_head_m;
let shaft_w = hydraulic_w / efficiency;
PumpPower {
hydraulic_w,
shaft_w,
efficiency,
}
}
#[inline]
pub fn pressure_to_depth(pressure_pa: f64, density_kg_m3: f64) -> f64 {
pressure_pa / (density_kg_m3 * G)
}
#[inline]
pub fn depth_to_pressure(depth_m: f64, density_kg_m3: f64) -> f64 {
density_kg_m3 * G * depth_m
}
#[inline]
pub fn required_replenishment_flow(annual_loss_m3: f64, recharge_efficiency: f64) -> f64 {
(annual_loss_m3 / recharge_efficiency) / SECONDS_PER_YEAR
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pipe_area_1m() {
let a = pipe_area(1.0);
assert!((a - PI / 4.0).abs() < 1e-10);
}
#[test]
fn test_flow_rate_continuity() {
let q = flow_rate(0.05, 3.0);
let expected = PI * 0.025 * 0.025 * 3.0;
assert!((q - expected).abs() < 1e-10);
}
#[test]
fn test_velocity_from_flow() {
let q = 1.0; let d = 1.0; let v = velocity(d, q);
assert!((v - 4.0 / PI).abs() < 1e-10);
}
#[test]
fn test_required_diameter_large_flow() {
let d = required_diameter(595.0, 3.0);
assert!((d - 15.9).abs() < 0.5, "diameter = {d:.1}");
}
#[test]
fn test_required_diameter_very_large_flow() {
let d = required_diameter(1172.0, 3.0);
assert!((d - 22.3).abs() < 0.5, "diameter = {d:.1}");
}
#[test]
fn test_flow_rate_mgd_conversion() {
let fr = FlowRate::from_m3s(1.0);
assert!((fr.to_mgd() - 22.8245).abs() < 0.01);
}
#[test]
fn test_flow_rate_mld_conversion() {
let fr = FlowRate::from_m3s(1.0);
assert!((fr.to_mld() - 86.4).abs() < 0.1);
}
#[test]
fn test_flow_rate_lpm_conversion() {
let fr = FlowRate::from_m3s(1.0);
assert!((fr.to_lpm() - 60_000.0).abs() < 0.1);
}
#[test]
fn test_flow_rate_roundtrip_mgd() {
let fr = FlowRate::from_mgd(64.0);
assert!((fr.to_mgd() - 64.0).abs() < 0.01);
}
#[test]
fn test_moderate_flow_mgd() {
let fr = FlowRate::from_m3s(2.8);
assert!((fr.to_mgd() - 63.9).abs() < 1.0, "MGD = {:.1}", fr.to_mgd());
}
#[test]
fn test_bernoulli_static_column() {
let p2 = bernoulli_pressure(0.0, 0.0, 10.0, 0.0, 0.0, 1000.0);
assert!((p2 - 98_066.5).abs() < 1.0, "P2 = {p2:.1}");
}
#[test]
fn test_bernoulli_venturi() {
let p2 = bernoulli_pressure(200_000.0, 2.0, 0.0, 4.0, 0.0, 1000.0);
assert!((p2 - 194_000.0).abs() < 1.0, "P2 = {p2:.1}");
}
#[test]
fn test_pump_power_high_flow_moderate_head() {
let pp = pump_power(1000.0, 595.0, 300.0, 0.85);
assert!((pp.shaft_gw() - 2.06).abs() < 0.1, "pump = {:.2} GW", pp.shaft_gw());
}
#[test]
fn test_pump_power_very_high_flow_high_head() {
let pp = pump_power(1000.0, 1172.0, 500.0, 0.85);
assert!((pp.shaft_gw() - 6.76).abs() < 0.5, "pump = {:.2} GW", pp.shaft_gw());
}
#[test]
fn test_pressure_to_depth_5m_column() {
let d = pressure_to_depth(49_050.0, 1000.0);
assert!((d - 5.0).abs() < 0.05, "depth = {d:.2} m");
}
#[test]
fn test_depth_to_pressure_roundtrip() {
let depth = 25.0;
let p = depth_to_pressure(depth, 997.0);
let d2 = pressure_to_depth(p, 997.0);
assert!((d2 - depth).abs() < 1e-10);
}
#[test]
fn test_dn40_pipe_flow_lpm() {
let q = flow_rate(0.04, 2.0);
let fr = FlowRate::from_m3s(q);
assert!((fr.to_lpm() - 151.0).abs() < 1.0, "DN40 flow = {:.1} L/min", fr.to_lpm());
}
#[test]
fn test_required_flow_15km3_80pct() {
let q = required_replenishment_flow(15e9, 0.80);
assert!((q - 595.0).abs() < 5.0, "required Q = {q:.1} m³/s");
}
#[test]
fn test_required_flow_37km3_100pct() {
let q = required_replenishment_flow(3.7e10, 1.0);
assert!((q - 1172.0).abs() < 5.0, "required Q = {q:.1} m³/s");
}
}