hydra_engine_wds/simulation/
lifecycle.rs1use super::*;
2
3impl Default for Simulation {
4 fn default() -> Self {
5 Self::create()
6 }
7}
8
9impl Simulation {
10 pub fn create() -> Self {
12 Simulation {
13 phase: Phase::Created,
14 network: None,
15 favad: None,
16 solver_ctx: None,
17 node_states: vec![],
18 link_states: vec![],
19 current_t: 0.0,
20 next_report_t: 0.0,
21 report_count: 0,
22 hyd_snapshots: vec![],
23 quality_state: None,
24 quality_t: 0.0,
25 accounting: None,
26 warnings: vec![],
27 neg_pressure_seen: vec![],
28 analysis_begun: None,
29 analysis_ended: None,
30 }
31 }
32
33 pub fn from_network(network: Network) -> Result<Self, SessionError> {
39 let mut session = Self::create();
40 session.load(network)?;
41 Ok(session)
42 }
43
44 pub fn load(&mut self, network: Network) -> Result<(), SessionError> {
50 network.validate().map_err(SessionError::ValidationFailed)?;
52
53 let favad = network.compute_favad();
55
56 let solver_ctx = hydraulics::build_solver_context(&network, &favad)
58 .map_err(SessionError::HydraulicSolve)?;
59
60 let node_states = init_node_states(&network);
62 let link_states = init_link_states(&network);
63
64 let accounting = accounting::init_accounting(&network, &node_states);
66
67 let options = &network.options;
68 let next_report = options.report_start;
69
70 self.network = Some(network);
71 self.favad = Some(favad);
72 self.solver_ctx = Some(solver_ctx);
73 self.node_states = node_states;
74 self.link_states = link_states;
75 self.current_t = 0.0;
76 self.next_report_t = next_report;
77 self.hyd_snapshots = vec![];
78 self.quality_state = None;
79 self.quality_t = 0.0;
80 self.accounting = Some(accounting);
81 self.warnings = vec![];
82 self.neg_pressure_seen = vec![false; self.node_states.len()];
83 self.phase = Phase::Loaded;
84 Ok(())
85 }
86
87 pub fn run_hydraulics(&mut self) -> Result<(), SessionError> {
91 self.require_phase(Phase::Loaded)?;
92 self.analysis_begun = Some(SystemTime::now());
93 loop {
94 let dt = self.step_hydraulics()?;
95 if dt == 0.0 {
96 break;
97 }
98 }
99 self.analysis_ended = Some(SystemTime::now());
100 Ok(())
101 }
102
103 pub fn run(&mut self) -> Result<(), SessionError> {
111 self.run_hydraulics()?;
112 self.run_quality()
113 }
114
115 pub fn step_hydraulics(&mut self) -> Result<f64, SessionError> {
120 self.require_phase(Phase::Loaded)?;
121
122 if self.analysis_begun.is_none() {
124 self.analysis_begun = Some(SystemTime::now());
125 }
126
127 let network = self
128 .network
129 .as_ref()
130 .expect("invariant: network set in load()");
131 let t = self.current_t;
132 let duration = network.options.duration;
133
134 if t > duration {
135 self.phase = Phase::HydraulicsDone;
136 return Ok(0.0);
137 }
138
139 let network = self
142 .network
143 .as_ref()
144 .expect("invariant: network set in load()");
145 for (k, link) in network.links.iter().enumerate() {
146 if let LinkKind::Pump(pump) = &link.kind {
147 if let Some(ref pat_id) = pump.speed_pattern {
148 if let Some(pat) = network.pattern_by_id(pat_id) {
149 let factor = pat.eval(
150 t,
151 network.options.pattern_step,
152 network.options.pattern_start,
153 );
154 self.link_states[k].setting =
155 link.base.initial_setting.unwrap_or(1.0) * factor;
156 }
157 }
158 }
159 }
160
161 let network = self
163 .network
164 .as_ref()
165 .expect("invariant: network set in load()");
166 let _changed =
167 controls::apply_simple_controls(network, &self.node_states, &mut self.link_states, t);
168
169 let network = self
172 .network
173 .as_ref()
174 .expect("invariant: network set in load()");
175 let favad = self.favad.as_ref().expect("invariant: favad set in load()");
176 let solver_context = self
177 .solver_ctx
178 .as_mut()
179 .expect("invariant: solver_ctx set in load()");
180 let result = hydraulics::solve_hydraulic_step(
181 network,
182 favad,
183 solver_context,
184 &mut self.node_states,
185 &mut self.link_states,
186 t,
187 controls::pswitch,
188 )
189 .map_err(SessionError::HydraulicSolve)?;
190
191 if result == SolveResult::Unbalanced {
192 self.warnings.push(SimWarning {
193 t,
194 kind: WarningKind::UnbalancedHydraulics,
195 });
196 if network.options.extra_iter < 0 {
198 self.maybe_record_snapshot(t);
199 self.phase = Phase::HydraulicsDone;
200 return Ok(0.0);
201 }
202 }
203
204 let network = self
208 .network
209 .as_ref()
210 .expect("invariant: network set in load()");
211 for (i, node) in network.nodes.iter().enumerate() {
212 if !self.neg_pressure_seen[i]
213 && matches!(node.kind, NodeKind::Junction(_))
214 && self.node_states[i].head < node.base.elevation
215 && self.node_states[i].demand_flow > 0.0
216 {
217 self.neg_pressure_seen[i] = true;
218 self.warnings.push(SimWarning {
219 t,
220 kind: WarningKind::NegativePressure { node_index: i },
221 });
222 }
223 }
224
225 let network = self
228 .network
229 .as_ref()
230 .expect("invariant: network set in load()");
231 let ctx = self
232 .solver_ctx
233 .as_ref()
234 .expect("invariant: solver_ctx set in load()");
235 for (k, link) in network.links.iter().enumerate() {
236 if let LinkKind::Pump(_) = &link.kind {
237 let link_state = &self.link_states[k];
238 if matches!(link_state.status, LinkStatus::Open | LinkStatus::Active) {
239 let qmax = ctx.pump_qmax(k);
240 if link_state.flow > link_state.setting * qmax || link_state.flow < 0.0 {
241 self.warnings.push(SimWarning {
242 t,
243 kind: WarningKind::PumpXHead { link_index: k },
244 });
245 }
246 }
247 }
248 }
249
250 self.maybe_record_snapshot(t);
253
254 let network = self
257 .network
258 .as_ref()
259 .expect("invariant: network set in load()");
260 let mut dt = timestep::adaptive_timestep(t, network, &self.node_states);
261
262 let dt_control =
264 timestep::control_timestep(t, network, &self.node_states, &self.link_states);
265 if dt_control < dt {
266 dt = dt_control;
267 }
268
269 if dt == 0.0 {
270 if duration == 0.0 {
276 let network = self
277 .network
278 .as_ref()
279 .expect("invariant: network set in load()");
280 let pump_powers = accounting::precompute_pump_powers(
281 network,
282 &self.node_states,
283 &self.link_states,
284 );
285 let accounting = self
286 .accounting
287 .as_mut()
288 .expect("invariant: accounting set in load()");
289 accounting::accumulate_step(
290 accounting,
291 network,
292 &self.node_states,
293 &pump_powers,
294 3600.0,
295 t,
296 0.0,
297 );
298 }
299 self.phase = Phase::HydraulicsDone;
300 return Ok(0.0);
301 }
302
303 let network = self
312 .network
313 .as_ref()
314 .expect("invariant: network set in load()");
315 let pump_powers =
316 accounting::precompute_pump_powers(network, &self.node_states, &self.link_states);
317 let mut step_overflow: f64 = 0.0;
318 if !network.rules.is_empty() {
319 let rule_step = network.options.rule_timestep;
320 let mut elapsed = 0.0;
321
322 let first_dt = {
325 let rem = t % rule_step;
326 let d = rule_step - rem;
327 if d <= 0.0 || d > rule_step {
328 rule_step
329 } else {
330 d
331 }
332 };
333 let mut dt1 = first_dt.min(dt);
334 if dt1 == 0.0 {
335 dt1 = rule_step.min(dt);
336 }
337
338 loop {
339 let updates = timestep::update_tank_levels(network, &self.node_states, dt1);
341 for u in &updates {
342 let node_state = &mut self.node_states[u.node_index];
343 node_state.head = u.new_head;
344 node_state.level = u.new_level;
345 node_state.volume = u.new_volume;
346 step_overflow += u.overflow_volume;
347 }
348 elapsed += dt1;
349
350 let sub_t = t + elapsed;
352 if let Some((actions, _then_fired)) =
353 controls::eval_rules(network, &self.node_states, &self.link_states, sub_t)
354 {
355 let any_changed =
356 controls::apply_link_actions(&mut self.link_states, &actions, network);
357 if any_changed {
358 dt = elapsed;
360 break;
361 }
362 }
363
364 let remaining = dt - elapsed;
366 if remaining <= 0.0 {
367 break;
368 }
369 dt1 = rule_step.min(remaining);
370 }
371 } else {
372 let updates = timestep::update_tank_levels(network, &self.node_states, dt);
374 for u in &updates {
375 let node_state = &mut self.node_states[u.node_index];
376 node_state.head = u.new_head;
377 node_state.level = u.new_level;
378 node_state.volume = u.new_volume;
379 step_overflow += u.overflow_volume;
380 }
381 }
382
383 let network = self
385 .network
386 .as_ref()
387 .expect("invariant: network set in load()");
388 let accounting = self
389 .accounting
390 .as_mut()
391 .expect("invariant: accounting set in load()");
392 accounting::accumulate_step(
393 accounting,
394 network,
395 &self.node_states,
396 &pump_powers,
397 dt,
398 t,
399 step_overflow,
400 );
401
402 let new_t = t + dt;
403 self.current_t = new_t;
404
405 Ok(dt)
406 }
407
408 pub fn run_quality(&mut self) -> Result<(), SessionError> {
412 self.require_phase(Phase::HydraulicsDone)?;
413 let network = self
415 .network
416 .as_ref()
417 .expect("invariant: network set in load()");
418 if network.options.quality_mode == QualityMode::None {
419 self.analysis_ended = Some(SystemTime::now());
420 self.phase = Phase::QualityDone;
421 return Ok(());
422 }
423 let (init_ns, init_ls) = self.first_snapshot_states();
425 let qs = quality::init_quality(network, init_ns, init_ls)
426 .map_err(SessionError::QualityEngine)?;
427
428 if let Some(snap0) = self.hyd_snapshots.first_mut() {
431 for (i, ns) in snap0.node_states.iter_mut().enumerate() {
432 ns.quality = qs.node_conc[i];
433 }
434 for (k, ls) in snap0.link_states.iter_mut().enumerate() {
435 ls.quality = quality::avg_link_quality(
436 &qs,
437 k,
438 network.links[k].base.from_idx(),
439 network.links[k].base.to_idx(),
440 );
441 }
442 }
443
444 self.quality_state = Some(qs);
445 self.quality_t = 0.0;
446 loop {
447 let dt = self.step_quality()?;
448 if dt == 0.0 {
449 break;
450 }
451 }
452 self.analysis_ended = Some(SystemTime::now());
453 Ok(())
454 }
455
456 pub fn step_quality(&mut self) -> Result<f64, SessionError> {
461 if self.phase != Phase::HydraulicsDone && self.phase != Phase::QualityDone {
462 return Err(SessionError::InvalidPhase {
463 expected: "HydraulicsDone".into(),
464 actual: self.phase.name().to_string(),
465 });
466 }
467
468 if self.quality_state.is_none() {
474 let network = self
475 .network
476 .as_ref()
477 .expect("invariant: network set in load()");
478 if network.options.quality_mode == QualityMode::None {
479 self.analysis_ended = Some(SystemTime::now());
480 self.phase = Phase::QualityDone;
481 return Ok(0.0);
482 }
483 let (init_ns, init_ls) = self.first_snapshot_states();
484 let qs = quality::init_quality(network, init_ns, init_ls)
485 .map_err(SessionError::QualityEngine)?;
486 if let Some(snap0) = self.hyd_snapshots.first_mut() {
487 for (i, ns) in snap0.node_states.iter_mut().enumerate() {
488 ns.quality = qs.node_conc[i];
489 }
490 for (k, ls) in snap0.link_states.iter_mut().enumerate() {
491 ls.quality = quality::avg_link_quality(
492 &qs,
493 k,
494 network.links[k].base.from_idx(),
495 network.links[k].base.to_idx(),
496 );
497 }
498 }
499 self.quality_state = Some(qs);
500 self.quality_t = 0.0;
501 }
502
503 let network = self
504 .network
505 .as_ref()
506 .expect("invariant: network set in load()");
507 let duration = network.options.duration;
508 let qt = self.quality_t;
509 if qt >= duration {
510 self.analysis_ended = Some(SystemTime::now());
511 self.phase = Phase::QualityDone;
512 return Ok(0.0);
513 }
514
515 let snap_idx = self.find_snapshot_index_at(qt);
517 let snap_idx = match snap_idx {
518 Some(idx) => idx,
519 None => {
520 self.phase = Phase::QualityDone;
521 return Ok(0.0);
522 }
523 };
524
525 let next_snap_idx = snap_idx + 1;
530 let next_t = if next_snap_idx < self.hyd_snapshots.len() {
531 self.hyd_snapshots[next_snap_idx].t
532 } else {
533 duration
534 };
535 let dt_h = next_t - qt;
536 if dt_h <= 0.0 {
537 self.phase = Phase::QualityDone;
538 return Ok(0.0);
539 }
540
541 let node_states = &self.hyd_snapshots[snap_idx].node_states;
545 let link_states = &self.hyd_snapshots[snap_idx].link_states;
546
547 if let Some(qs) = self.quality_state.as_mut() {
548 quality::advance_quality(qs, network, node_states, link_states, dt_h, qt);
549 if next_snap_idx < self.hyd_snapshots.len() {
552 let snap = &mut self.hyd_snapshots[next_snap_idx];
553 for (i, ns) in snap.node_states.iter_mut().enumerate() {
554 ns.quality = qs.node_conc[i];
555 }
556 for (k, ls) in snap.link_states.iter_mut().enumerate() {
557 ls.quality = quality::avg_link_quality(
558 qs,
559 k,
560 network.links[k].base.from_idx(),
561 network.links[k].base.to_idx(),
562 );
563 ls.reaction_rate = qs.pipe_rate_coeff[k];
564 }
565 }
566 self.quality_t = qt + dt_h;
567 }
568
569 if self.quality_t >= duration {
570 self.phase = Phase::QualityDone;
571 }
572 Ok(dt_h)
573 }
574}