use core::f64::consts::PI;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ManningFlow {
pub diameter_m: f64,
pub length_m: f64,
pub roughness_n: f64,
pub head_difference_m: f64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ManningResult {
pub area_m2: f64,
pub hydraulic_radius_m: f64,
pub slope: f64,
pub velocity_m_s: f64,
pub flow_rate_m3_s: f64,
pub flow_rate_mgd: f64,
}
impl ManningFlow {
pub fn new(diameter_m: f64, length_m: f64, roughness_n: f64, head_difference_m: f64) -> Self {
Self {
diameter_m,
length_m,
roughness_n,
head_difference_m,
}
}
pub fn calculate(&self) -> ManningResult {
let r = self.diameter_m / 2.0;
let area = PI * r * r;
let hydraulic_radius = self.diameter_m / 4.0;
let slope = self.head_difference_m.abs() / self.length_m;
let velocity = (1.0 / self.roughness_n)
* hydraulic_radius.powf(2.0 / 3.0)
* slope.sqrt();
let flow_rate = area * velocity;
let flow_rate_mgd = flow_rate * 22.824_5;
ManningResult {
area_m2: area,
hydraulic_radius_m: hydraulic_radius,
slope,
velocity_m_s: velocity,
flow_rate_m3_s: flow_rate,
flow_rate_mgd,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_concrete_pipe_gentle_slope() {
let mf = ManningFlow::new(3.66, 1609.0, 0.012, 5.0);
let r = mf.calculate();
assert!((r.slope - 0.0031).abs() < 0.001, "slope = {:.4}", r.slope);
assert!((r.velocity_m_s - 4.3).abs() < 0.5, "v = {:.2} m/s", r.velocity_m_s);
assert!(r.flow_rate_m3_s > 30.0 && r.flow_rate_m3_s < 60.0,
"Q = {:.1} m³/s", r.flow_rate_m3_s);
}
#[test]
fn test_concrete_pipe_steep_slope() {
let mf = ManningFlow::new(3.66, 1609.0, 0.012, 50.0);
let r = mf.calculate();
assert!((r.slope - 0.031).abs() < 0.002, "slope = {:.4}", r.slope);
assert!((r.velocity_m_s - 13.8).abs() < 1.0, "v = {:.2} m/s", r.velocity_m_s);
assert!(r.flow_rate_m3_s > 100.0 && r.flow_rate_m3_s < 200.0,
"Q = {:.1} m³/s", r.flow_rate_m3_s);
}
#[test]
fn test_hydraulic_radius_full_pipe() {
let mf = ManningFlow::new(4.0, 100.0, 0.012, 1.0);
let r = mf.calculate();
assert!((r.hydraulic_radius_m - 1.0).abs() < 1e-10);
}
#[test]
fn test_zero_slope_zero_flow() {
let mf = ManningFlow::new(3.66, 1609.0, 0.012, 0.0);
let r = mf.calculate();
assert!(r.velocity_m_s.abs() < 1e-10, "zero slope → zero velocity");
assert!(r.flow_rate_m3_s.abs() < 1e-10, "zero slope → zero flow");
}
#[test]
fn test_rougher_pipe_slower_flow() {
let smooth = ManningFlow::new(3.66, 1609.0, 0.009, 10.0).calculate();
let rough = ManningFlow::new(3.66, 1609.0, 0.020, 10.0).calculate();
assert!(smooth.velocity_m_s > rough.velocity_m_s, "smoother pipe → faster flow");
}
#[test]
fn test_velocity_equals_q_over_a() {
let mf = ManningFlow::new(3.66, 1287.0, 0.012, 42.6);
let r = mf.calculate();
let expected_v = r.flow_rate_m3_s / r.area_m2;
assert!((r.velocity_m_s - expected_v).abs() < 1e-10,
"v={:.4} vs Q/A={:.4}", r.velocity_m_s, expected_v);
}
#[test]
fn test_larger_diameter_more_flow() {
let small = ManningFlow::new(1.0, 1000.0, 0.012, 10.0).calculate();
let large = ManningFlow::new(3.0, 1000.0, 0.012, 10.0).calculate();
assert!(large.flow_rate_m3_s > small.flow_rate_m3_s,
"larger pipe should carry more: {:.1} vs {:.1}", large.flow_rate_m3_s, small.flow_rate_m3_s);
}
}