OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
NodeSubtypes.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2//
3// Copyright 2026 Caleb Buahin
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
53
54#ifndef OPENSWMM_ENGINE_NODE_SUBTYPES_HPP
55#define OPENSWMM_ENGINE_NODE_SUBTYPES_HPP
56
57#include <algorithm>
58#include <vector>
59#include <cstdint>
60#include <string>
61
62#include "NodeData.hpp"
63
64namespace openswmm {
65
66// ============================================================================
67// StorageData — side-table for STORAGE nodes
68// ============================================================================
69
77 std::vector<int> node_idx;
78
80 std::vector<int> curve;
82 std::vector<std::string> curve_name;
84 std::vector<StorageShape> shape;
87 std::vector<double> a;
90 std::vector<double> b;
92 std::vector<double> c;
96 std::vector<double> p1;
98 std::vector<double> p2;
100 std::vector<double> p3;
102 std::vector<double> seep_rate;
104 std::vector<double> evap_frac;
106 std::vector<double> evap_loss;
108 std::vector<double> exfil_loss;
110 std::vector<double> exfil_suction;
112 std::vector<double> exfil_ksat;
114 std::vector<double> exfil_imd;
115
117 int count() const noexcept { return static_cast<int>(node_idx.size()); }
118
120 void clear() noexcept {
121 node_idx.clear(); curve.clear(); curve_name.clear();
122 shape.clear();
123 a.clear(); b.clear(); c.clear();
124 p1.clear(); p2.clear(); p3.clear();
125 seep_rate.clear(); evap_frac.clear(); evap_loss.clear(); exfil_loss.clear();
126 exfil_suction.clear(); exfil_ksat.clear(); exfil_imd.clear();
127 }
128
130 void reserve(int n) {
131 const auto un = static_cast<std::size_t>(n);
132 node_idx.reserve(un); curve.reserve(un); curve_name.reserve(un);
133 shape.reserve(un);
134 a.reserve(un); b.reserve(un); c.reserve(un);
135 p1.reserve(un); p2.reserve(un); p3.reserve(un);
136 seep_rate.reserve(un); evap_frac.reserve(un);
137 evap_loss.reserve(un); exfil_loss.reserve(un);
138 exfil_suction.reserve(un); exfil_ksat.reserve(un); exfil_imd.reserve(un);
139 }
140
144 int add_default(int i) {
145 const auto p = static_cast<std::ptrdiff_t>(
146 std::lower_bound(node_idx.begin(), node_idx.end(), i) - node_idx.begin());
147 node_idx.insert(node_idx.begin() + p, i);
148 curve.insert(curve.begin() + p, -1);
149 curve_name.insert(curve_name.begin() + p, std::string{});
150 // FUNCTIONAL is the historical default for a curve-less storage row: it is what
151 // `curve < 0` has always meant, so existing callers see no behaviour change.
152 shape.insert(shape.begin() + p, StorageShape::FUNCTIONAL);
153 a.insert(a.begin() + p, 0.0);
154 b.insert(b.begin() + p, 0.0);
155 c.insert(c.begin() + p, 0.0);
156 p1.insert(p1.begin() + p, 0.0);
157 p2.insert(p2.begin() + p, 0.0);
158 p3.insert(p3.begin() + p, 0.0);
159 seep_rate.insert(seep_rate.begin() + p, 0.0);
160 evap_frac.insert(evap_frac.begin() + p, 0.0);
161 evap_loss.insert(evap_loss.begin() + p, 0.0);
162 exfil_loss.insert(exfil_loss.begin() + p, 0.0);
163 exfil_suction.insert(exfil_suction.begin() + p, 0.0);
164 exfil_ksat.insert(exfil_ksat.begin() + p, 0.0);
165 exfil_imd.insert(exfil_imd.begin() + p, 0.0);
166 return static_cast<int>(p);
167 }
168
170 void erase_at(int r) {
171 const auto p = static_cast<std::ptrdiff_t>(r);
172 node_idx.erase(node_idx.begin() + p);
173 curve.erase(curve.begin() + p);
174 curve_name.erase(curve_name.begin() + p);
175 shape.erase(shape.begin() + p);
176 a.erase(a.begin() + p);
177 b.erase(b.begin() + p);
178 c.erase(c.begin() + p);
179 p1.erase(p1.begin() + p);
180 p2.erase(p2.begin() + p);
181 p3.erase(p3.begin() + p);
182 seep_rate.erase(seep_rate.begin() + p);
183 evap_frac.erase(evap_frac.begin() + p);
184 evap_loss.erase(evap_loss.begin() + p);
185 exfil_loss.erase(exfil_loss.begin() + p);
186 exfil_suction.erase(exfil_suction.begin() + p);
187 exfil_ksat.erase(exfil_ksat.begin() + p);
188 exfil_imd.erase(exfil_imd.begin() + p);
189 }
190};
191
192// ============================================================================
193// OutfallData — side-table for OUTFALL nodes
194// ============================================================================
195
202 std::vector<int> node_idx;
203
205 std::vector<OutfallType> bc_type;
209 std::vector<double> param;
212 std::vector<std::string> param_name;
214 std::vector<uint8_t> has_flap_gate;
216 std::vector<int> route_to;
218 std::vector<int> link_idx;
220 std::vector<double> link_offset;
222 std::vector<double> head_2d;
225 std::vector<double> ramp_2d;
226
228 int count() const noexcept { return static_cast<int>(node_idx.size()); }
229
231 void clear() noexcept {
232 node_idx.clear(); bc_type.clear(); param.clear(); param_name.clear();
233 has_flap_gate.clear(); route_to.clear();
234 link_idx.clear(); link_offset.clear(); head_2d.clear();
235 ramp_2d.clear();
236 }
237
239 void reserve(int n) {
240 const auto un = static_cast<std::size_t>(n);
241 node_idx.reserve(un); bc_type.reserve(un); param.reserve(un);
242 param_name.reserve(un);
243 has_flap_gate.reserve(un); route_to.reserve(un);
244 link_idx.reserve(un); link_offset.reserve(un); head_2d.reserve(un);
245 ramp_2d.reserve(un);
246 }
247
250 int add_default(int i) {
251 const auto p = static_cast<std::ptrdiff_t>(
252 std::lower_bound(node_idx.begin(), node_idx.end(), i) - node_idx.begin());
253 node_idx.insert(node_idx.begin() + p, i);
254 bc_type.insert(bc_type.begin() + p, OutfallType::FREE);
255 param.insert(param.begin() + p, 0.0);
256 param_name.insert(param_name.begin() + p, std::string{});
257 has_flap_gate.insert(has_flap_gate.begin() + p, uint8_t{0});
258 route_to.insert(route_to.begin() + p, -1);
259 link_idx.insert(link_idx.begin() + p, -1);
260 link_offset.insert(link_offset.begin() + p, 0.0);
261 head_2d.insert(head_2d.begin() + p, -1.0e30);
262 ramp_2d.insert(ramp_2d.begin() + p, 0.0);
263 return static_cast<int>(p);
264 }
265
267 void erase_at(int r) {
268 const auto p = static_cast<std::ptrdiff_t>(r);
269 node_idx.erase(node_idx.begin() + p);
270 bc_type.erase(bc_type.begin() + p);
271 param.erase(param.begin() + p);
272 param_name.erase(param_name.begin() + p);
273 has_flap_gate.erase(has_flap_gate.begin() + p);
274 route_to.erase(route_to.begin() + p);
275 link_idx.erase(link_idx.begin() + p);
276 link_offset.erase(link_offset.begin() + p);
277 head_2d.erase(head_2d.begin() + p);
278 ramp_2d.erase(ramp_2d.begin() + p);
279 }
280};
281
282// ============================================================================
283// DividerData — side-table for DIVIDER nodes
284// ============================================================================
285
292 std::vector<int> node_idx;
293
295 std::vector<DividerType> method;
297 std::vector<double> cutoff;
299 std::vector<double> cd;
301 std::vector<double> max_depth;
303 std::vector<int> curve;
305 std::vector<int> link;
307 std::vector<std::string> link_name;
309 std::vector<std::string> curve_name;
310
312 int count() const noexcept { return static_cast<int>(node_idx.size()); }
313
315 void clear() noexcept {
316 node_idx.clear(); method.clear(); cutoff.clear();
317 cd.clear(); max_depth.clear(); curve.clear(); link.clear();
318 link_name.clear(); curve_name.clear();
319 }
320
322 void reserve(int n) {
323 const auto un = static_cast<std::size_t>(n);
324 node_idx.reserve(un); method.reserve(un); cutoff.reserve(un);
325 cd.reserve(un); max_depth.reserve(un); curve.reserve(un); link.reserve(un);
326 link_name.reserve(un); curve_name.reserve(un);
327 }
328
331 int add_default(int i) {
332 const auto p = static_cast<std::ptrdiff_t>(
333 std::lower_bound(node_idx.begin(), node_idx.end(), i) - node_idx.begin());
334 node_idx.insert(node_idx.begin() + p, i);
335 method.insert(method.begin() + p, DividerType::CUTOFF);
336 cutoff.insert(cutoff.begin() + p, 0.0);
337 cd.insert(cd.begin() + p, 0.0);
338 max_depth.insert(max_depth.begin() + p, 0.0);
339 curve.insert(curve.begin() + p, -1);
340 link.insert(link.begin() + p, -1);
341 link_name.insert(link_name.begin() + p, std::string{});
342 curve_name.insert(curve_name.begin() + p, std::string{});
343 return static_cast<int>(p);
344 }
345
347 void erase_at(int r) {
348 const auto p = static_cast<std::ptrdiff_t>(r);
349 node_idx.erase(node_idx.begin() + p);
350 method.erase(method.begin() + p);
351 cutoff.erase(cutoff.begin() + p);
352 cd.erase(cd.begin() + p);
353 max_depth.erase(max_depth.begin() + p);
354 curve.erase(curve.begin() + p);
355 link.erase(link.begin() + p);
356 link_name.erase(link_name.begin() + p);
357 curve_name.erase(curve_name.begin() + p);
358 }
359};
360
361// ============================================================================
362// NodeSubtypes — container + reverse map + build/verify
363// ============================================================================
364
377
379 std::vector<int> subtype_row;
380
382 void clear() noexcept {
383 storages.clear();
384 outfalls.clear();
385 dividers.clear();
386 subtype_row.clear();
387 }
388
399 void rebuild_index(int n_nodes) {
400 subtype_row.assign(static_cast<std::size_t>(n_nodes), -1);
401 for (int r = 0; r < storages.count(); ++r)
402 subtype_row[static_cast<std::size_t>(storages.node_idx[static_cast<std::size_t>(r)])] = r;
403 for (int r = 0; r < outfalls.count(); ++r)
404 subtype_row[static_cast<std::size_t>(outfalls.node_idx[static_cast<std::size_t>(r)])] = r;
405 for (int r = 0; r < dividers.count(); ++r)
406 subtype_row[static_cast<std::size_t>(dividers.node_idx[static_cast<std::size_t>(r)])] = r;
407 }
408
422 int set_node_type(NodeData& nodes, int i, NodeType t) {
423 const auto ui = static_cast<std::size_t>(i);
424 if (i >= static_cast<int>(subtype_row.size()))
425 subtype_row.resize(static_cast<std::size_t>(i) + 1, -1);
426
427 const NodeType old = nodes.type[ui];
428 const int existing = subtype_row[ui];
429 if (old == t && existing >= 0)
430 return existing; // already this subtype with a row — idempotent.
431
432 bool shifted = false;
433 if (existing >= 0) { // remove the old subtype row (real re-type/convert).
434 switch (old) {
435 case NodeType::STORAGE: storages.erase_at(existing); break;
436 case NodeType::OUTFALL: outfalls.erase_at(existing); break;
437 case NodeType::DIVIDER: dividers.erase_at(existing); break;
438 default: break;
439 }
440 shifted = true;
441 }
442
443 nodes.type[ui] = t;
444
445 int row = -1;
446 switch (t) {
447 case NodeType::STORAGE: row = storages.add_default(i);
448 if (row != storages.count() - 1) shifted = true; break;
449 case NodeType::OUTFALL: row = outfalls.add_default(i);
450 if (row != outfalls.count() - 1) shifted = true; break;
451 case NodeType::DIVIDER: row = dividers.add_default(i);
452 if (row != dividers.count() - 1) shifted = true; break;
454 default: break; // no subtype row
455 }
456
457 if (shifted) {
458 rebuild_index(nodes.count());
459 return (t == NodeType::JUNCTION) ? -1 : subtype_row[ui];
460 }
461 subtype_row[ui] = row; // O(1) ascending-parse / end-insert path
462 return row;
463 }
464
470 void erase_node(int i, int n_after) {
471 // Erase the row whose node_idx == i (in whichever table holds it).
472 auto drop = [i](auto& tbl) -> bool {
473 for (int r = 0; r < tbl.count(); ++r)
474 if (tbl.node_idx[static_cast<std::size_t>(r)] == i) { tbl.erase_at(r); return true; }
475 return false;
476 };
477 drop(storages) || drop(outfalls) || drop(dividers);
478
479 auto shift = [i](std::vector<int>& keys) {
480 for (auto& k : keys) if (k > i) --k;
481 };
482 shift(storages.node_idx);
483 shift(outfalls.node_idx);
484 shift(dividers.node_idx);
485
486 rebuild_index(n_after);
487 }
488
491 int storage_row(int i) const noexcept {
492 if (i >= 0 && i < static_cast<int>(subtype_row.size())) {
493 const int r = subtype_row[static_cast<std::size_t>(i)];
494 if (r >= 0 && r < storages.count() &&
495 storages.node_idx[static_cast<std::size_t>(r)] == i)
496 return r;
497 }
498 return -1;
499 }
500
503 int outfall_row(int i) const noexcept {
504 if (i >= 0 && i < static_cast<int>(subtype_row.size())) {
505 const int r = subtype_row[static_cast<std::size_t>(i)];
506 if (r >= 0 && r < outfalls.count() &&
507 outfalls.node_idx[static_cast<std::size_t>(r)] == i)
508 return r;
509 }
510 return -1;
511 }
512
515 int divider_row(int i) const noexcept {
516 if (i >= 0 && i < static_cast<int>(subtype_row.size())) {
517 const int r = subtype_row[static_cast<std::size_t>(i)];
518 if (r >= 0 && r < dividers.count() &&
519 dividers.node_idx[static_cast<std::size_t>(r)] == i)
520 return r;
521 }
522 return -1;
523 }
524};
525
526} // namespace openswmm
527
528#endif // OPENSWMM_ENGINE_NODE_SUBTYPES_HPP
Structure-of-Arrays (SoA) storage for all node types.
Definition NodeCoupling.cpp:16
@ FUNCTIONAL
Area = c + a*d^b.
Definition NodeData.hpp:106
@ FREE
Definition NodeData.hpp:71
NodeType
Node type codes.
Definition NodeData.hpp:59
@ JUNCTION
Definition NodeData.hpp:60
@ DIVIDER
Definition NodeData.hpp:62
@ STORAGE
Definition NodeData.hpp:63
@ OUTFALL
Definition NodeData.hpp:61
@ CUTOFF
Definition NodeData.hpp:83
Dense SoA for flow-divider properties (one row per DIVIDER node).
Definition NodeSubtypes.hpp:290
std::vector< double > cd
Weir discharge coefficient for WEIR dividers.
Definition NodeSubtypes.hpp:299
std::vector< std::string > link_name
Diversion link name (deferred resolution).
Definition NodeSubtypes.hpp:307
std::vector< int > curve
Diversion curve index for TABULAR dividers (-1 = none).
Definition NodeSubtypes.hpp:303
std::vector< double > cutoff
Cutoff flow for CUTOFF dividers.
Definition NodeSubtypes.hpp:297
void clear() noexcept
Drop all rows (capacity retained).
Definition NodeSubtypes.hpp:315
std::vector< DividerType > method
Diversion method (CUTOFF/OVERFLOW/TABULAR/WEIR).
Definition NodeSubtypes.hpp:295
int count() const noexcept
Number of divider rows.
Definition NodeSubtypes.hpp:312
int add_default(int i)
Insert a default divider row for base node i, keeping node_idx ascending; returns the inserted row in...
Definition NodeSubtypes.hpp:331
std::vector< double > max_depth
Weir max depth for WEIR dividers.
Definition NodeSubtypes.hpp:301
void reserve(int n)
Reserve capacity for n rows.
Definition NodeSubtypes.hpp:322
std::vector< int > link
Diversion link index (-1 = not set).
Definition NodeSubtypes.hpp:305
std::vector< std::string > curve_name
Diversion curve name (deferred resolution, TABULAR only).
Definition NodeSubtypes.hpp:309
void erase_at(int r)
Erase divider row r from every column.
Definition NodeSubtypes.hpp:347
std::vector< int > node_idx
Base NodeData index this row belongs to (the join key).
Definition NodeSubtypes.hpp:292
Structure-of-Arrays storage for all nodes.
Definition NodeData.hpp:130
int count() const noexcept
Number of nodes.
Definition NodeData.hpp:721
std::vector< NodeType > type
Node type for each node.
Definition NodeData.hpp:137
Owns the three node subtype side-tables plus the reverse index map.
Definition NodeSubtypes.hpp:373
int divider_row(int i) const noexcept
Divider side-table row for base node i, or -1 if not a divider (or the side-table is unbuilt)....
Definition NodeSubtypes.hpp:515
DividerData dividers
Definition NodeSubtypes.hpp:376
int storage_row(int i) const noexcept
Storage side-table row for base node i, or -1 if i is not a storage node (or the side-table is unbuil...
Definition NodeSubtypes.hpp:491
int set_node_type(NodeData &nodes, int i, NodeType t)
Set node i to t, creating/removing/moving its subtype row so the side-table stays the single source o...
Definition NodeSubtypes.hpp:422
void erase_node(int i, int n_after)
Drop node i's subtype row and renumber the join keys after a base node erase. Call after NodeData::er...
Definition NodeSubtypes.hpp:470
void rebuild_index(int n_nodes)
Recompute the reverse map (subtype_row) from the side-table rows.
Definition NodeSubtypes.hpp:399
void clear() noexcept
Drop all rows and the reverse map.
Definition NodeSubtypes.hpp:382
OutfallData outfalls
Definition NodeSubtypes.hpp:375
std::vector< int > subtype_row
base node index → row in its subtype table (-1 for junctions).
Definition NodeSubtypes.hpp:379
int outfall_row(int i) const noexcept
Outfall side-table row for base node i, or -1 if not an outfall (or the side-table is unbuilt)....
Definition NodeSubtypes.hpp:503
StorageData storages
Definition NodeSubtypes.hpp:374
Dense SoA for outfall boundary-condition properties (one row per OUTFALL).
Definition NodeSubtypes.hpp:200
std::vector< OutfallType > bc_type
Boundary condition type (FREE/NORMAL/FIXED/TIDAL/TIMESERIES).
Definition NodeSubtypes.hpp:205
void erase_at(int r)
Erase outfall row r from every column.
Definition NodeSubtypes.hpp:267
std::vector< int > link_idx
Cached connected-conduit index (-1 = none).
Definition NodeSubtypes.hpp:218
std::vector< uint8_t > has_flap_gate
Flap gate present (0/1).
Definition NodeSubtypes.hpp:214
void reserve(int n)
Reserve capacity for n rows.
Definition NodeSubtypes.hpp:239
std::vector< int > route_to
Subcatchment index to route discharge to (-1 = none).
Definition NodeSubtypes.hpp:216
int count() const noexcept
Number of outfall rows.
Definition NodeSubtypes.hpp:228
void clear() noexcept
Drop all rows (capacity retained).
Definition NodeSubtypes.hpp:231
std::vector< std::string > param_name
Stage-data table name for TIDAL/TIMESERIES, held for deferred name→index resolution in PostParseResol...
Definition NodeSubtypes.hpp:212
std::vector< double > ramp_2d
Cached wet/dry ramp factor [0,1] for the 2D tailwater override (0 = dry → free discharge,...
Definition NodeSubtypes.hpp:225
int add_default(int i)
Insert a default outfall row for base node i, keeping node_idx ascending; returns the inserted row in...
Definition NodeSubtypes.hpp:250
std::vector< double > param
Fixed stage, or tidal/timeseries table index (per bc_type). For TIDAL/TIMESERIES an unresolved refere...
Definition NodeSubtypes.hpp:209
std::vector< double > head_2d
Cached 2D surface head at the coupling point (sentinel -1e30).
Definition NodeSubtypes.hpp:222
std::vector< double > link_offset
Conduit offset at the outfall end.
Definition NodeSubtypes.hpp:220
std::vector< int > node_idx
Base NodeData index this row belongs to (the join key).
Definition NodeSubtypes.hpp:202
Dense SoA for storage-unit properties (one row per STORAGE node).
Definition NodeSubtypes.hpp:75
void clear() noexcept
Drop all rows (capacity retained).
Definition NodeSubtypes.hpp:120
int add_default(int i)
Insert a default storage row for base node i, keeping node_idx ascending; returns the inserted row in...
Definition NodeSubtypes.hpp:144
int count() const noexcept
Number of storage rows.
Definition NodeSubtypes.hpp:117
void reserve(int n)
Reserve capacity for n rows.
Definition NodeSubtypes.hpp:130
std::vector< double > exfil_imd
Green-Ampt initial moisture deficit for exfiltration (0-1).
Definition NodeSubtypes.hpp:114
std::vector< double > evap_frac
Fraction of potential evaporation realized (0-1).
Definition NodeSubtypes.hpp:104
std::vector< double > exfil_suction
Green-Ampt suction head for exfiltration.
Definition NodeSubtypes.hpp:110
std::vector< double > a
Area coefficient A (legacy a1). FUNCTIONAL: A·d^B; geometric shapes: the linear term of C + A·d + B·d...
Definition NodeSubtypes.hpp:87
std::vector< double > p3
Raw shape parameter 3 — side slope Z (run/rise), or height for PARABOLOID.
Definition NodeSubtypes.hpp:100
std::vector< double > b
Area coefficient B (legacy a2). FUNCTIONAL: the exponent; geometric shapes: the quadratic term.
Definition NodeSubtypes.hpp:90
std::vector< double > seep_rate
Seepage rate (project units/day).
Definition NodeSubtypes.hpp:102
std::vector< int > node_idx
Base NodeData index this row belongs to (the join key).
Definition NodeSubtypes.hpp:77
std::vector< int > curve
Storage curve index into TableData (-1 = not tabulated).
Definition NodeSubtypes.hpp:80
std::vector< double > p2
Raw shape parameter 2 — minor axis / base width W.
Definition NodeSubtypes.hpp:98
std::vector< double > exfil_ksat
Green-Ampt saturated conductivity for exfiltration.
Definition NodeSubtypes.hpp:112
std::vector< double > c
Area coefficient C (legacy a0). Baseline area at zero depth.
Definition NodeSubtypes.hpp:92
std::vector< std::string > curve_name
Curve name for deferred resolution.
Definition NodeSubtypes.hpp:82
std::vector< double > evap_loss
Evaporation loss this timestep (ft3).
Definition NodeSubtypes.hpp:106
std::vector< double > p1
Raw shape parameter 1 — major axis / base length L (geometric shapes only). Kept alongside the derive...
Definition NodeSubtypes.hpp:96
std::vector< double > exfil_loss
Exfiltration loss this timestep (ft3).
Definition NodeSubtypes.hpp:108
std::vector< StorageShape > shape
Surface-area relation. Selects how a/b/c below are interpreted.
Definition NodeSubtypes.hpp:84
void erase_at(int r)
Erase storage row r from every column.
Definition NodeSubtypes.hpp:170