OpenSWMM Engine  6.0.0-alpha.3
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.3)
Loading...
Searching...
No Matches
DynamicWave.hpp
Go to the documentation of this file.
1
27
28#ifndef OPENSWMM_DYNAMIC_WAVE_HPP
29#define OPENSWMM_DYNAMIC_WAVE_HPP
30
31#include "XSectBatch.hpp"
32#include "../core/Constants.hpp"
34#include "../data/NodeData.hpp"
35#include "../data/LinkData.hpp"
36#include <cstdint>
37#include <functional>
38#include <vector>
39
40namespace openswmm {
41
43
44namespace dynwave {
45
46// ============================================================================
47// Constants — imported from global Constants.hpp
48// ============================================================================
49
60
61// ============================================================================
62// Dynamic Preissmann Slot (DPS) configuration and per-link state
63// Sharior, Hodges & Vasconcelos (2023), J. Hydraul. Eng. 149(11)
64// ============================================================================
65
67struct DPSConfig {
68 double c_pT = 25.0;
69 double alpha = 3.0;
70 double r = 0.5;
71 double c_pT_sq = 625.0;
72};
73
86 std::vector<double> As;
87 std::vector<double> hs;
88 std::vector<double> hs_prev_iter;
89 std::vector<double> P;
90 std::vector<double> P_hat;
91 std::vector<double> P_hat_0;
92 std::vector<double> T_s_target;
93 std::vector<double> t_s;
94 std::vector<uint8_t> surcharged;
95
96 void resize(std::size_t n) {
97 As.assign(n, 0.0);
98 hs.assign(n, 0.0);
99 hs_prev_iter.assign(n, 0.0);
100 P.assign(n, 1.0);
101 P_hat.assign(n, 1.0);
102 P_hat_0.assign(n, 1.0);
103 T_s_target.assign(n, 0.0);
104 t_s.assign(n, 0.0);
105 surcharged.assign(n, 0);
106 }
107};
108
109// ============================================================================
110// Per-node extended state for DW iterations
111// ============================================================================
112
116 std::vector<double> new_surf_area;
117 std::vector<double> old_surf_area;
118 std::vector<double> sumdqdh;
119 std::vector<double> dYdT;
120 std::vector<uint8_t> converged;
121 std::vector<uint8_t> is_surcharged;
122
123 void resize(std::size_t n) {
124 new_surf_area.assign(n, 0.0);
125 old_surf_area.assign(n, 0.0);
126 sumdqdh.assign(n, 0.0);
127 dYdT.assign(n, 0.0);
128 converged.assign(n, 0);
129 is_surcharged.assign(n, 0);
130 }
131};
132
133// ============================================================================
134// DW solver — batch-oriented
135// ============================================================================
136
141enum class SurchargeMethod : int {
142 EXTRAN = 0,
143 SLOT = 1,
145};
146
163
172class DWSolver {
173public:
174 void init(int n_nodes, int n_links, const XSectGroups& groups,
175 const SimulationContext& ctx);
176
186 void setNumThreads(int n);
187
190 using NonConduitFlowFunc = std::function<void(SimulationContext&, double, int)>;
191
201 int execute(SimulationContext& ctx, double dt,
202 NonConduitFlowFunc non_conduit_fn = nullptr);
203
208 bool lastConverged() const { return last_converged_; }
209
216 double fixed_step, double courant_factor);
217
220 double omega = OMEGA;
223 bool anderson_accel = false;
224
227 double evap_rate = 0.0;
228
229private:
230 int n_nodes_ = 0;
231 int n_links_ = 0;
232 int n_conduits_ = 0;
233 int num_threads_ = 1;
234 const XSectGroups* groups_ = nullptr;
235
239 bool any_conduit_seep_ = false;
240
245 bool losses_all_zero_ = true;
246
247 // Pre-built conduit index list for skipping non-conduits in inner loops
248 std::vector<int> conduit_idx_;
249
250 // Pre-computed per-link invariants (populated once at first execute, reused)
251 // Using uint8_t instead of bool to allow .data() pointer access for SIMD/restrict
252 std::vector<uint8_t> is_open_;
253 std::vector<uint8_t> is_force_main_;
254 std::vector<uint8_t> has_losses_;
255 std::vector<double> barrels_d_;
256 std::vector<double> cached_length_;
257 std::vector<double> inv_length_;
258
259 // ------------------------------------------------------------------------
260 // Phase A — Conduit-dense "hot tile" of timestep-invariant data.
261 //
262 // Sized n_conduits_, accessed by ci (0..n_conduits_-1) for dense linear
263 // memory access pattern. This replaces sparse `links.X[uj]` /
264 // `nodes.X[un]` reads inside the Picard inner loops, where uj/un are
265 // sparse-indexed via conduit_idx_ + links.node1/node2. Each ci-indexed
266 // read maps to one contiguous cache line that holds 8+ conduits' data,
267 // versus the sparse pattern where each uj read can miss into a new line.
268 //
269 // All fields below are populated once in init() (or refreshConduitTile
270 // when a hot-start changes invariants) and remain constant for the rest
271 // of the simulation. None of these change per Picard iter, per
272 // timestep, or per outfall update.
273 // ------------------------------------------------------------------------
274 std::vector<int> tile_uj_;
275 std::vector<int> tile_n1_;
276 std::vector<int> tile_n2_;
277 std::vector<double> tile_inv1_elev_;
278 std::vector<double> tile_inv2_elev_;
279 std::vector<double> tile_z1_off_;
280 std::vector<double> tile_z2_off_;
281 std::vector<double> tile_y_full_;
282 std::vector<double> tile_a_full_;
283 std::vector<double> tile_r_full_;
284 std::vector<double> tile_w_max_;
285 std::vector<double> tile_length_;
286 std::vector<double> tile_inv_length_;
287 std::vector<double> tile_links_length_;
288 std::vector<double> tile_beta_;
289 std::vector<double> tile_q_max_;
290 std::vector<double> tile_rough_factor_;
291 std::vector<double> tile_barrels_d_;
292 std::vector<uint8_t> tile_is_open_;
293 std::vector<uint8_t> tile_is_force_main_;
294 std::vector<uint8_t> tile_is_closed_;
295 std::vector<uint8_t> tile_has_losses_;
296 std::vector<int> tile_xsect_batch_shape_;
297 std::vector<XsectShape> tile_shape_;
304 std::vector<uint8_t> tile_has_offset_;
309 std::vector<int> tile_uj_to_ci_;
312 std::vector<int> tile_culvert_code_;
313 std::vector<double> tile_slope_;
314 std::vector<double> tile_q_limit_;
315 std::vector<double> tile_loss_inlet_;
316 std::vector<double> tile_loss_outlet_;
317 std::vector<double> tile_loss_avg_;
318 std::vector<double> tile_roughness_;
319 std::vector<uint8_t> tile_has_flap_gate_;
320 std::vector<int8_t> tile_direction_;
321
325 void refreshConduitTile(const SimulationContext& ctx);
326
327 // Per-timestep constants
328 double dt_gravity_ = 0.0;
329
330 bool last_converged_ = false;
331
341 double min_surf_area_ = constants::MIN_SURFAREA;
342
343 // Pre-allocated width-capping buffers (avoids thread_local per-call allocation)
344 std::vector<double> wcap_d1_, wcap_d2_, wcap_dm_;
345
346 // Variable timestep state (matching legacy VariableStep in dynwave.c)
347 mutable double variable_step_ = 0.0;
348
349 // Per-node working state (SoA)
350 DWNodeArrays xnode_;
351
352 // Per-link pre-computed geometry (batch-filled by XSectGroups each iteration)
353 std::vector<double> area1_;
354 std::vector<double> area2_;
355 std::vector<double> area_mid_;
356 std::vector<double> hrad_mid_;
357 std::vector<double> width_mid_;
358 std::vector<double> depth1_;
359 std::vector<double> depth2_;
360 std::vector<double> depth_mid_;
361
362 // Per-link momentum working arrays
363 std::vector<double> velocity_;
364 std::vector<double> froude_;
365 std::vector<double> sigma_;
366 std::vector<double> dqdh_;
367 std::vector<double> new_flow_;
368
369 // Per-link area from previous iteration (for unsteady term)
370 std::vector<double> area_old_;
371
372 // Per-link bypass flag (true when both end nodes converged; skip momentum solve)
373 // uint8_t instead of bool: avoids std::vector<bool> bit-packing overhead
374 std::vector<uint8_t> bypassed_;
375
376 // ------------------------------------------------------------------------
377 // B2 threading — CSR node→incident-conduit adjacency for the parallel
378 // node-centric flow gather (see gatherConduitNodeFlows). Built once in
379 // init() from static topology.
380 //
381 // PROOF OF BIT-EXACTNESS vs the serial per-link scatter: legacy
382 // findLinkFlows (dynwave.c:385-388) scatters conduits one link at a time
383 // in ascending link index; each link updates node1's accumulators, then
384 // node2's. For a given NODE, the projection of that global order is
385 // simply its incident conduit links in ascending link index (node1-end
386 // entry before node2-end entry when both ends touch the same node). CSR
387 // entries are stored per node in exactly that order, so the per-node
388 // gather performs the identical FP accumulation sequence on each
389 // accumulator (inflow, outflow, sumdqdh, new_surf_area) — bit-exact at
390 // any thread count, since each node is owned by exactly one thread.
391 // ------------------------------------------------------------------------
392 std::vector<int> csr_row_;
393 std::vector<int32_t> csr_link_;
394 std::vector<uint8_t> csr_is_n2_;
395 std::vector<uint8_t> csr_other_outfall_;
396
397 // Node-dense tile of the per-node invariants setNodeDepth touches every
398 // Picard iteration. setNodeDepth reads ~20 SoA arrays per node; the seven
399 // step-invariant ones below otherwise cost seven separate cache streams
400 // (legacy's AoS TNode record pays 1-2 lines per node for the same reads).
401 // Rebuilt once per routing step in execute() — amortised over the Picard
402 // iterations and automatically correct even if the editing API mutates
403 // node geometry mid-run. y_crown pre-evaluates crownElev − invertElev
404 // with the identical operands the per-call subtraction used, so the
405 // value is bit-identical (legacy setNodeDepth recomputes it per call).
406 struct NodeTile {
407 double full_depth;
408 double y_crown;
409 double invert_elev;
410 double ponded_area;
411 double sur_depth;
412 double full_volume;
413 int32_t degree;
414 uint8_t is_storage;
415 uint8_t is_outfall;
416 };
417 std::vector<NodeTile> node_tile_;
418 // Unit system for node volume/surf-area table dispatch, hoisted from the
419 // per-call ucf::getUnitSystem(options.flow_units) (options are fixed
420 // during a run).
421 int unit_sys_ = 0;
422
423 // Per-link surface area contributions to upstream/downstream nodes
424 // (matching legacy Link[].surfArea1/surfArea2 from dwflow.c findSurfArea)
425 std::vector<double> surf_area1_;
426 std::vector<double> surf_area2_;
427
428 // Per-link upstream geometry (for proper weighted hyd. radius)
429 std::vector<double> hrad1_;
430 std::vector<double> width1_;
431 std::vector<double> width2_;
432
433 // Per-link head values (persisted from computeLinkGeometry for solveMomentumBatch,
434 // may be modified by flow classification for UP_CRITICAL/DN_CRITICAL cases)
435 std::vector<double> h1_;
436 std::vector<double> h2_;
437 std::vector<double> fasnh_;
438
439 // Anderson acceleration state (per-node, depth-2 mixing)
440 std::vector<double> aa_y_prev_;
441 std::vector<double> aa_g_prev_;
442 std::vector<double> aa_r_prev_;
443 std::vector<uint8_t> aa_skip_;
444
445
446 // Per-conduit momentum category (rebuilt each Picard iteration).
447 // solveMomentumBatch dispatches on category_[uj] inline — no auxiliary
448 // per-category index list is needed.
449 std::vector<MomentumCategory> category_;
450
451 // Internal methods
452 void initNodeStates(SimulationContext& ctx);
453 void findBypassedLinks(const SimulationContext& ctx);
454 void computeLinkGeometry(SimulationContext& ctx);
463 void recomputeConduitLossOne(SimulationContext& ctx, double dt, int ci);
471 void momentumKernels(SimulationContext& ctx, double dt, int step);
472
477 void processDryLink(SimulationContext& ctx, double dt, std::size_t uj);
478 void processManningLink(SimulationContext& ctx, double dt, int step,
479 std::size_t uj, MomentumCategory cat);
480 void processForceMainLink(SimulationContext& ctx, double dt, int step,
481 std::size_t uj, MomentumCategory cat);
482 void applyFlowLimits(SimulationContext& ctx, double dt, int step,
483 std::size_t uj, double& q, double qLast,
484 double barrels_d, bool isFull);
489 void updateNodeFlows(SimulationContext& ctx);
491 void buildConduitNodeCSR(const SimulationContext& ctx);
497 void gatherConduitNodeFlows(SimulationContext& ctx);
498 void computeAASkipFlags(const SimulationContext& ctx);
505 void updateNodeDepthsTeam(SimulationContext& ctx, double dt, int step,
506 int& unconv_shared);
507 void setNodeDepth(SimulationContext& ctx, int node_idx, double dt, int step);
508 double getLinkStep(const SimulationContext& ctx, int link_idx) const;
509
510public:
512 uint8_t& nodeSurchargedFlag(int idx) { return xnode_.is_surcharged[static_cast<std::size_t>(idx)]; }
513
515 double* nodeNewSurfAreaDataMut() { return xnode_.new_surf_area.data(); }
516
520 bool isBypassed(int j) const {
521 return bypassed_[static_cast<std::size_t>(j)] != 0;
522 }
523
525 double& nodeSumDqdh(int n) { return xnode_.sumdqdh[static_cast<std::size_t>(n)]; }
526
528 const std::vector<uint8_t>& aaSkipFlags() const { return aa_skip_; }
529
531 const DPSLinkArrays& dpsState() const { return dps_; }
533 const DPSConfig& dpsConfig() const { return dps_config_; }
535 DPSLinkArrays& dpsStateMut() { return dps_; }
536private:
537
538 // Preissmann slot helpers (matching legacy dwflow.c)
539 double getSlotWidth(double y, double y_full, double w_max, XsectShape shape) const;
540 double getSlotArea(double y, double y_full, double a_full, double slot_width) const;
541 double getSlotHydRad(double y, double y_full, double r_full) const;
542 double getCrownCutoff() const;
543
544 // Dynamic Preissmann Slot (DPS) state and methods
545 DPSConfig dps_config_;
546 DPSLinkArrays dps_;
547 double sim_time_ = 0.0;
548
550 void applyDPSGeometry(SimulationContext& ctx);
551
553 void updateDPSState(SimulationContext& ctx, double dt);
554
556 void spatialSmoothP(const SimulationContext& ctx);
557};
558
559} // namespace dynwave
560} // namespace openswmm
561
562#endif // OPENSWMM_DYNAMIC_WAVE_HPP
Global physical, numerical, and model constants for OpenSWMM Engine.
Structure-of-Arrays (SoA) storage for all node types.
Simulation options parsed from the [OPTIONS] section.
Cross-section geometry — unified batch + per-element API.
Shape-grouped cross-section manager for batch computation.
Definition XSectBatch.hpp:219
Dynamic wave solver — operates on entire link/node system.
Definition DynamicWave.hpp:172
double evap_rate
Definition DynamicWave.hpp:227
void setNumThreads(int n)
Set the number of OpenMP threads for parallel loops.
Definition DynamicWave.cpp:764
SurchargeMethod surcharge_method
Definition DynamicWave.hpp:221
void init(int n_nodes, int n_links, const XSectGroups &groups, const SimulationContext &ctx)
Definition DynamicWave.cpp:368
bool isBypassed(int j) const
Definition DynamicWave.hpp:520
DPSLinkArrays & dpsStateMut()
Mutable access to DPS state for tests that need to seed slot conditions.
Definition DynamicWave.hpp:535
const DPSLinkArrays & dpsState() const
Read-only access to DPS per-conduit state arrays (for tests/diagnostics).
Definition DynamicWave.hpp:531
bool lastConverged() const
Definition DynamicWave.hpp:208
double head_tol
Definition DynamicWave.hpp:218
NodeContinuity node_continuity
Definition DynamicWave.hpp:222
const DPSConfig & dpsConfig() const
Read-only access to DPS configuration (for tests/diagnostics).
Definition DynamicWave.hpp:533
bool anderson_accel
Enable Anderson acceleration.
Definition DynamicWave.hpp:223
double omega
Definition DynamicWave.hpp:220
int execute(SimulationContext &ctx, double dt, NonConduitFlowFunc non_conduit_fn=nullptr)
Execute one DW routing timestep.
Definition DynamicWave.cpp:820
double & nodeSumDqdh(int n)
Mutable reference to the per-node sumdqdh accumulator at index n.
Definition DynamicWave.hpp:525
uint8_t & nodeSurchargedFlag(int idx)
Direct write access to the per-node is_surcharged flag (for tests/non-conduit scatter).
Definition DynamicWave.hpp:512
double * nodeNewSurfAreaDataMut()
Mutable pointer to the per-node new_surf_area array (for HydStructures scatter).
Definition DynamicWave.hpp:515
int max_trials
Definition DynamicWave.hpp:219
double getRoutingStep(SimulationContext &ctx, double fixed_step, double courant_factor)
Compute CFL-based variable timestep.
Definition DynamicWave.cpp:2997
std::function< void(SimulationContext &, double, int)> NonConduitFlowFunc
Definition DynamicWave.hpp:190
const std::vector< uint8_t > & aaSkipFlags() const
Access per-node AA skip flags (read-only, for testing/diagnostics).
Definition DynamicWave.hpp:528
constexpr double MIN_SURFAREA
Definition Constants.hpp:79
constexpr double DEFAULT_HEAD_TOL
Definition Constants.hpp:105
constexpr double FUDGE
Definition Constants.hpp:75
constexpr double EXTRAN_CROWN_CUTOFF
Definition Constants.hpp:121
constexpr double MAX_VELOCITY
Definition Constants.hpp:113
constexpr double SLOT_WIDTH_FACTOR
Preissmann slot width factor (slot_width = y_full * this factor).
Definition Constants.hpp:127
constexpr double OMEGA
Definition Constants.hpp:101
constexpr int DEFAULT_MAX_TRIALS
Definition Constants.hpp:109
constexpr double MIN_TIMESTEP
Definition Constants.hpp:117
constexpr double SLOT_CROWN_CUTOFF
Preissmann slot crown cutoff fraction.
Definition Constants.hpp:124
Definition DynamicWave.cpp:73
constexpr double DEFAULT_HEAD_TOL
Definition Constants.hpp:105
constexpr double OMEGA
Definition Constants.hpp:101
constexpr int DEFAULT_MAX_TRIALS
Definition Constants.hpp:109
SurchargeMethod
Surcharge method: EXTRAN (classic) or SLOT (Preissmann).
Definition DynamicWave.hpp:141
@ SLOT
Preissmann slot — fictitious narrow slot above crown.
Definition DynamicWave.hpp:143
@ DYNAMIC_SLOT
Dynamic slot — slot width varies with flow conditions (experimental) Sharior, S., Hodges,...
Definition DynamicWave.hpp:144
@ EXTRAN
Classic EXTRAN approach — dQ/dH for surcharged nodes.
Definition DynamicWave.hpp:142
MomentumCategory
Momentum category for branch-free per-category kernel dispatch.
Definition DynamicWave.hpp:154
@ MANNING_OPEN
Standard Manning, open channel (Froude-based sigma)
Definition DynamicWave.hpp:156
@ MANNING_CLOSED_FULL
Manning, closed conduit, surcharged (fr=0, sig=0)
Definition DynamicWave.hpp:158
@ FORCE_MAIN_HW
Force main, Hazen-Williams friction.
Definition DynamicWave.hpp:159
@ FORCE_MAIN_DW
Force main, Darcy-Weisbach friction.
Definition DynamicWave.hpp:160
@ MANNING_CLOSED_FS
Manning, closed conduit, free surface.
Definition DynamicWave.hpp:157
@ N_CATEGORIES
Definition DynamicWave.hpp:161
@ SKIP_DRY
DRY/UP_DRY/DN_DRY, aMid<=FUDGE, or is_closed.
Definition DynamicWave.hpp:155
Definition NodeCoupling.cpp:15
NodeContinuity
Node continuity formulation for depth update.
Definition SimulationOptions.hpp:101
@ EXPLICIT
Classic explicit two-branch (default)
Definition SimulationOptions.hpp:102
XsectShape
Conduit cross-section shape code.
Definition LinkData.hpp:54
double * y
Definition odesolve.c:28
Central, reentrant simulation context.
Definition SimulationContext.hpp:292
DPS configuration parameters (derived from SimulationOptions at init).
Definition DynamicWave.hpp:67
double alpha
Surcharge shock parameter (>= 2)
Definition DynamicWave.hpp:69
double c_pT_sq
c_pT^2 (pre-computed)
Definition DynamicWave.hpp:71
double c_pT
Target pressure celerity (ft/s, internal units)
Definition DynamicWave.hpp:68
double r
Decay time scale for P → 1 (seconds)
Definition DynamicWave.hpp:70
Definition DynamicWave.hpp:115
std::vector< double > dYdT
Definition DynamicWave.hpp:119
std::vector< double > old_surf_area
Surface area from last non-surcharged state.
Definition DynamicWave.hpp:117
std::vector< double > new_surf_area
Definition DynamicWave.hpp:116
void resize(std::size_t n)
Definition DynamicWave.hpp:123
std::vector< uint8_t > is_surcharged
TRUE when node depth > crown elevation.
Definition DynamicWave.hpp:121
std::vector< double > sumdqdh
Definition DynamicWave.hpp:118
std::vector< uint8_t > converged
Definition DynamicWave.hpp:120