use crate::constants::*;
use crate::model::curve::Curve;
use crate::model::options::SimulationOptions;
use crate::model::units::UnitConversion;
use crate::model::units::{Cfs, Ft, Ft3};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Tank {
pub elevation: Ft, pub initial_level: Ft, pub min_level: Ft, pub max_level: Ft, pub diameter: Ft, pub min_volume: Ft3, pub volume_curve_id: Option<Box<str>>, pub overflow: bool, #[serde(skip)]
pub volume_curve: Option<Arc<Curve>>, #[serde(skip)]
pub links_to: Vec<usize>, #[serde(skip)]
pub links_from: Vec<usize>, }
impl Tank {
pub fn time_to_reach_level(&self, current_level: Ft, target_level: Ft, flow: Cfs) -> usize {
if target_level > self.max_level {
return usize::MAX;
}
if target_level < self.min_level {
return usize::MAX;
}
let current_volume = self.volume_at_level(current_level);
let target_volume = self.volume_at_level(target_level);
let delta_volume = target_volume - current_volume;
if delta_volume.abs() < 0.5 {
return 0;
}
if delta_volume > 0.0 && flow > 0.0 {
return (delta_volume / flow).round() as usize;
}
if delta_volume < 0.0 && flow < 0.0 {
return (delta_volume / flow).round() as usize;
}
usize::MAX
}
pub fn time_to_fill_or_drain(&self, current_level: Ft, flow: Cfs) -> usize {
if flow == 0.0 {
return usize::MAX;
}
self.time_to_reach_level(current_level, self.max_level, flow)
.min(self.time_to_reach_level(current_level, self.min_level, flow))
}
pub fn volume_at_level(&self, level: Ft) -> Ft3 {
if level < 0.0 {
return 0.0;
}
if self.volume_curve.is_some() {
panic!("Tank volume curves not supported yet!");
} else {
level * PI * self.diameter * self.diameter / 4.0
}
}
pub fn volume_at_head(&self, head: Ft) -> Ft3 {
self.volume_at_level(head - self.elevation)
}
pub fn min_volume(&self) -> Ft3 {
if self.volume_curve.is_some() {
panic!("Tank volume curves not supported yet!");
} else {
self.min_level * PI * self.diameter * self.diameter / 4.0
}
}
pub fn max_volume(&self) -> Ft3 {
if self.volume_curve.is_some() {
panic!("Tank volume curves not supported yet!");
} else {
self.max_level * PI * self.diameter * self.diameter / 4.0
}
}
pub fn new_head(&self, delta_volume: Ft3, current_head: Ft) -> Ft {
let mut level = current_head - self.elevation;
if self.volume_curve.is_some() {
panic!("Tank volume curves not supported yet!");
} else {
let surface_area = PI * self.diameter * self.diameter / 4.0; let new_level = level + delta_volume / surface_area;
level = new_level.clamp(self.min_level, self.max_level);
}
self.elevation + level
}
}
impl UnitConversion for Tank {
fn convert_to_standard(&mut self, options: &SimulationOptions) {
self.elevation /= options.unit_system.per_feet();
self.initial_level /= options.unit_system.per_feet();
self.min_level /= options.unit_system.per_feet();
self.max_level /= options.unit_system.per_feet();
self.diameter /= options.unit_system.per_feet();
self.min_volume /= options.unit_system.per_cubic_feet();
}
fn convert_from_standard(&mut self, options: &SimulationOptions) {
self.elevation *= options.unit_system.per_feet();
self.initial_level *= options.unit_system.per_feet();
self.min_level *= options.unit_system.per_feet();
self.max_level *= options.unit_system.per_feet();
self.diameter *= options.unit_system.per_feet();
self.min_volume *= options.unit_system.per_cubic_feet();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_tank() -> Tank {
Tank {
elevation: 10.0,
initial_level: 10.0,
min_level: 5.0,
max_level: 20.0,
diameter: (10.0 / (PI * 0.25)).sqrt(), min_volume: 0.0,
volume_curve_id: None,
overflow: false,
volume_curve: None,
links_to: vec![],
links_from: vec![],
}
}
#[test]
fn test_volume_at_head() {
let tank = test_tank();
assert!(tank.volume_at_head(11.0) - 10.0 < 1e-6);
assert!(tank.volume_at_head(5.0) - 0.0 < 1e-6);
assert!(tank.max_volume() - 200.0 < 1e-6);
assert!(tank.min_volume() - 50.0 < 1e-6);
}
#[test]
fn test_time_to_reach_level() {
let tank = test_tank();
let flow = 1.0; let time = tank.time_to_reach_level(10.0, 11.0, flow);
assert_eq!(time, 10);
let time = tank.time_to_reach_level(10.0, 9.0, -flow);
assert_eq!(time, 10);
let time = tank.time_to_reach_level(10.0, 9.0, flow);
assert_eq!(time, usize::MAX);
let time = tank.time_to_reach_level(10.0, 21.0, flow);
assert_eq!(time, usize::MAX);
let time = tank.time_to_reach_level(10.0, 0.0, -flow);
assert_eq!(time, usize::MAX);
let time = tank.time_to_fill_or_drain(10.0, flow);
assert_eq!(time, 100);
let time = tank.time_to_fill_or_drain(10.0, -flow);
assert_eq!(time, 50);
let time = tank.time_to_fill_or_drain(10.0, 0.0);
assert_eq!(time, usize::MAX);
}
}