use rayon::prelude::*;
use simplelog::{debug, warn};
use crate::error::{InputError, SolverError};
use crate::model::network::Network;
use crate::model::node::NodeType;
use crate::model::options::HeadlossFormula;
use crate::model::options::SimulationOptions;
use crate::model::units::FlowUnits;
use crate::solver::hydraulicsolver::HydraulicSolver;
use crate::solver::result::SolverResult;
use crate::solver::state::SolverState;
use crate::model::control::ControlCondition;
pub struct Simulation {
pub network: Network,
pub solver: Option<HydraulicSolver>,
pub state: Option<SolverState>,
pub time: usize,
pub skip_timesteps: bool,
pub solved: bool,
}
impl Simulation {
pub fn from_file(path: &str) -> Result<Self, InputError> {
let network = Network::from_file(path)?;
Ok(Self::new(network))
}
pub fn init(flow_units: FlowUnits, headloss_formula: HeadlossFormula) -> Self {
let network = Network {
options: SimulationOptions::new(flow_units, headloss_formula),
..Network::default()
};
Self::new(network)
}
pub fn new(network: Network) -> Self {
Self {
network,
solver: None,
state: None,
time: 0,
skip_timesteps: true,
solved: false,
}
}
pub fn initialize_hydraulics(&mut self) -> Result<(), SolverError> {
self.solver = Some(HydraulicSolver::new(&self.network)?);
self.state = Some(SolverState::new_with_initial_values(&self.network));
self.network.reset_changes();
self.time = 0;
self.solved = false;
Ok(())
}
pub fn run_hydraulics(&mut self) -> Result<usize, SolverError> {
let needs_reinit = match self.solver.as_ref() {
Some(solver) => self.network.topology_version != solver.topology_version,
None => return Err(SolverError::NotInitialized),
};
if needs_reinit {
self.initialize_hydraulics()?;
}
let solver = self.solver.as_ref().unwrap();
let state = self.state.as_mut().unwrap();
state.update_with_network_changes(&mut self.network);
state.apply_patterns(&self.network, self.time);
state.apply_controls(&self.network, self.time);
self.state = Some(solver.solve(&self.network, state)?);
self.solved = true;
Ok(self.time)
}
pub fn next_hydraulic_timestep(&mut self) -> usize {
let Some(state) = self.state.as_mut() else {
return 0;
};
let duration = self.network.options.time_options.duration;
if self.time >= duration {
return 0;
}
let remaining = duration - self.time;
let timestep =
Self::calculate_time_step(&self.network, state, self.time, self.skip_timesteps)
.min(remaining);
state.update_tanks(&self.network, timestep);
self.time += timestep;
timestep
}
pub fn solve_hydraulics(&mut self, parallel: bool) -> Result<SolverResult, SolverError> {
if self.solver.is_none() {
self.initialize_hydraulics()?;
}
if parallel && !self.network.has_tanks() && !self.network.has_pressure_controls() {
return self.solve_parallel();
} else if parallel {
warn!(
"Networks with tanks or pressure controls cannot be solved in parallel, running sequentially"
);
}
let report_timestep = self.network.options.time_options.report_timestep;
let report_steps = (self.network.options.time_options.duration / report_timestep) + 1;
let mut results = SolverResult::new(
self.network.links.len(),
self.network.nodes.len(),
report_steps,
);
loop {
let t = self.time;
debug!(
"Running hydraulics at hour: {}:{:02}:{:02}",
t / 3600,
(t % 3600) / 60,
t % 60
);
let t = self.run_hydraulics()?;
if t % report_timestep == 0 {
results.append(self.state.as_ref().unwrap(), t / report_timestep);
}
let dt = self.next_hydraulic_timestep();
if dt == 0 {
break;
}
}
self.solved = true;
results.convert_units(
&self.network.options.flow_units,
&self.network.options.unit_system,
);
Ok(results)
}
fn solve_parallel(&mut self) -> Result<SolverResult, SolverError> {
let solver = self.solver.as_ref().ok_or(SolverError::NotInitialized)?;
let state = self.state.as_mut().unwrap();
let report_timestep = self.network.options.time_options.report_timestep;
let report_steps = (self.network.options.time_options.duration / report_timestep) + 1;
let mut results = SolverResult::new(
self.network.links.len(),
self.network.nodes.len(),
report_steps,
);
state.apply_patterns(&self.network, 0);
let initial_state = solver.solve(&self.network, state)?;
results.append(&initial_state, 0);
let par_results: Vec<Result<SolverState, SolverError>> = (1..report_steps)
.into_par_iter()
.map(|step| {
let mut state = initial_state.clone();
state.apply_patterns(&self.network, step * report_timestep);
solver.solve(&self.network, &state)
})
.collect();
for (step, step_result) in par_results.into_iter().enumerate() {
results.append(&step_result?, step + 1);
}
self.solved = true;
results.convert_units(
&self.network.options.flow_units,
&self.network.options.unit_system,
);
Ok(results)
}
fn calculate_time_step(
network: &Network,
state: &SolverState,
current_time: usize,
skip_timesteps: bool,
) -> usize {
let times = &network.options.time_options;
let clocktime = (current_time + times.start_clocktime) % (24 * 3600);
if network.controls.is_empty()
&& !network.has_tanks()
&& !network.has_quality()
&& skip_timesteps
{
return times.report_timestep;
}
let t_report = times.report_timestep - (current_time % times.report_timestep);
let t_pattern = times.pattern_timestep - (current_time % times.pattern_timestep);
let t_tanks = network
.nodes
.iter()
.zip(state.heads.iter())
.zip(state.demands.iter())
.map(|((node, head), demand)| {
let NodeType::Tank(tank) = &node.node_type else {
return usize::MAX;
};
let level = head - node.elevation;
tank.time_to_fill_or_drain(level, *demand)
})
.min()
.unwrap_or(usize::MAX);
let t_controls = network
.controls
.iter()
.map(|control| match &control.condition {
ControlCondition::Time { seconds } => {
if *seconds < current_time {
return usize::MAX;
}
seconds - current_time
}
ControlCondition::ClockTime { seconds } => {
if *seconds < clocktime {
return ((*seconds as i32 - clocktime as i32) + (24 * 3600)) as usize;
}
seconds - clocktime
}
ControlCondition::LowLevel { tank_index, target }
| ControlCondition::HighLevel { tank_index, target } => {
let node = &network.nodes[*tank_index];
if let NodeType::Tank(tank) = &node.node_type {
let level = state.heads[*tank_index] - node.elevation;
tank.time_to_reach_level(level, *target, state.demands[*tank_index])
} else {
usize::MAX
}
}
_ => usize::MAX,
})
.filter(|&x| x > 0)
.min()
.unwrap_or(usize::MAX);
*[
t_report,
t_pattern,
t_tanks,
t_controls,
times.hydraulic_timestep,
]
.iter()
.filter(|&x| *x > 0)
.min()
.unwrap()
}
pub(crate) fn solved_state(&self) -> Option<&SolverState> {
if self.solved {
self.state.as_ref()
} else {
None
}
}
}