OpenSWMM Engine  6.0.0-alpha.1
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.1)
Loading...
Searching...
No Matches
TableData.hpp
Go to the documentation of this file.
1
32#ifndef OPENSWMM_ENGINE_TABLE_DATA_HPP
33#define OPENSWMM_ENGINE_TABLE_DATA_HPP
34
35#include <string>
36#include <vector>
37#include <unordered_map>
38#include <cstdio>
39#include <cstdint>
40#include <cassert>
41#include <algorithm>
42#include <cmath>
43
44namespace openswmm {
45
46// ============================================================================
47// Table types (matching legacy SWMM enums.h)
48// ============================================================================
49
54enum class TableType : int {
55 TIMESERIES = 0,
56 CURVE_STORAGE = 1,
57 CURVE_DIVERSION = 2,
58 CURVE_RATING = 3,
59 CURVE_SHAPE = 4,
60 CURVE_CONTROL = 5,
61 CURVE_TIDAL = 6,
62 CURVE_PUMP1 = 7,
63 CURVE_PUMP2 = 8,
64 CURVE_PUMP3 = 9,
65 CURVE_PUMP4 = 10,
66 CURVE_PUMP5 = 11
67};
68
69// ============================================================================
70// TableCursor
71// ============================================================================
72
85 int index = 0;
86 int direction = +1;
87
88 void reset() noexcept { index = 0; direction = +1; }
89};
90
91// ============================================================================
92// Table
93// ============================================================================
94
110 std::vector<double> x;
111 std::vector<double> y;
112 int num_cols = 1;
113 std::size_t file_row_start = 0;
114
115 std::size_t num_rows() const noexcept { return x.size(); }
116 bool empty() const noexcept { return x.empty(); }
117};
118
119struct Table {
120 std::string id;
122
128 std::string comment;
129 std::vector<double> x;
130 std::vector<double> y;
132
133 // ---- File-backed time series support ----
134 bool is_file_based = false;
135 std::FILE* file_handle = nullptr;
136 std::string file_path;
140 double dx_min = 0.0;
141 double x_min = 0.0;
142 double x_max = 0.0;
143 int num_cols = 1;
144 std::size_t total_rows = 0;
145 std::size_t num_cache_rows = 8192;
147 std::vector<long> row_offsets;
148 std::vector<std::string> column_ids;
149 std::unordered_map<std::string, int> column_map;
150
151 static constexpr std::size_t INDEX_STRIDE = 4096;
152
154 std::size_t size() const noexcept { return x.size(); }
155
157 bool empty() const noexcept { return x.empty(); }
158};
159
160// ============================================================================
161// Cursor-optimized lookup
162// ============================================================================
163
185inline double table_lookup_cursor(Table& tbl, double x_query) noexcept {
186 const int n = static_cast<int>(tbl.x.size());
187 if (n == 0) return 0.0;
188 if (n == 1) return tbl.y[0];
189
190 // Clamp below first entry
191 if (x_query <= tbl.x[0]) {
192 tbl.cursor.index = 0;
193 tbl.cursor.direction = +1;
194 return tbl.y[0];
195 }
196
197 // Clamp above last entry
198 if (x_query >= tbl.x[n - 1]) {
199 tbl.cursor.index = n - 1;
200 tbl.cursor.direction = -1;
201 return tbl.y[n - 1];
202 }
203
204 // Start from cursor position and seek in the most likely direction
205 int idx = std::clamp(tbl.cursor.index, 0, n - 2);
206
207 // Forward seek
208 while (idx < n - 1 && tbl.x[idx + 1] < x_query) {
209 ++idx;
210 tbl.cursor.direction = +1;
211 }
212
213 // Backward seek
214 while (idx > 0 && tbl.x[idx] > x_query) {
215 --idx;
216 tbl.cursor.direction = -1;
217 }
218
219 tbl.cursor.index = idx;
220
221 // Linear interpolation between x[idx] and x[idx+1]
222 const double dx = tbl.x[idx + 1] - tbl.x[idx];
223 if (dx <= 0.0) return tbl.y[idx]; // guard against duplicate x values
224 const double t = (x_query - tbl.x[idx]) / dx;
225 return tbl.y[idx] + t * (tbl.y[idx + 1] - tbl.y[idx]);
226}
227
245inline double table_tseries_lookup_cursor(Table& tbl, double x_query) noexcept {
246 const int n = static_cast<int>(tbl.x.size());
247 if (n == 0) return 0.0;
248
249 // Before first entry or after last entry → 0 (series not active)
250 if (x_query <= tbl.x[0]) {
251 tbl.cursor.index = 0;
252 tbl.cursor.direction = +1;
253 return (x_query < tbl.x[0]) ? 0.0 : tbl.y[0];
254 }
255 if (x_query >= tbl.x[n - 1]) {
256 tbl.cursor.index = n - 1;
257 tbl.cursor.direction = -1;
258 return (x_query > tbl.x[n - 1]) ? 0.0 : tbl.y[n - 1];
259 }
260
261 // Delegate to cursor-based linear interpolation for the in-range case
262 return table_lookup_cursor(tbl, x_query);
263}
264
279inline double table_step_cursor(Table& tbl, double x_query) noexcept {
280 const int n = static_cast<int>(tbl.x.size());
281 if (n == 0) return 0.0;
282 if (n == 1) return tbl.y[0];
283
284 // Before first entry → 0 (no rain before first recorded value)
285 if (x_query < tbl.x[0]) {
286 tbl.cursor.index = 0;
287 tbl.cursor.direction = +1;
288 return 0.0;
289 }
290
291 // At or past last entry → return last value.
292 // The caller (Gage.cpp) handles the rain interval cutoff and returns 0
293 // after entry_time + rainInterval. This allows the last entry's value to
294 // be used for its full recording interval before going to zero.
295 if (x_query >= tbl.x[n - 1]) {
296 tbl.cursor.index = n - 1;
297 tbl.cursor.direction = -1;
298 return tbl.y[n - 1];
299 }
300
301 // Seek to the interval containing x_query: x[idx] <= x_query < x[idx+1]
302 int idx = std::clamp(tbl.cursor.index, 0, n - 2);
303
304 while (idx < n - 1 && tbl.x[idx + 1] <= x_query) {
305 ++idx;
306 tbl.cursor.direction = +1;
307 }
308 while (idx > 0 && tbl.x[idx] > x_query) {
309 --idx;
310 tbl.cursor.direction = -1;
311 }
312
313 tbl.cursor.index = idx;
314 return tbl.y[idx];
315}
316
317// ============================================================================
318// Storage volume by trapezoidal integration of area curve
319// ============================================================================
320
329inline double table_getStorageVolume(Table& tbl, double depth) noexcept {
330 const int n = static_cast<int>(tbl.x.size());
331 if (n == 0 || depth <= 0.0) return 0.0;
332
333 double x1 = tbl.x[0];
334 double a1 = tbl.y[0];
335
336 // Target below first entry — triangular approximation
337 if (depth <= x1) {
338 if (x1 < 1.0e-6) return 0.0;
339 return (a1 / x1) * depth * depth / 2.0;
340 }
341
342 // Traverse entries using end-area (trapezoidal) method
343 double v = 0.0;
344 double dx = 0.0, dy = 0.0;
345 for (int i = 1; i < n; ++i) {
346 double x2 = tbl.x[i];
347 double a2 = tbl.y[i];
348 if (x2 >= depth) {
349 // Bracketed — interpolate area at target depth
350 double frac = (x2 > x1) ? (depth - x1) / (x2 - x1) : 0.0;
351 double a = a1 + frac * (a2 - a1);
352 return v + (a1 + a) / 2.0 * (depth - x1);
353 }
354 dx = x2 - x1;
355 dy = a2 - a1;
356 v += (a1 + a2) / 2.0 * dx;
357 x1 = x2;
358 a1 = a2;
359 }
360
361 // Extrapolate beyond last entry
362 if (dx > 1.0e-6) {
363 double s = dy / dx;
364 double a = a1 + s * (depth - x1);
365 if (a < 0.0) {
366 v -= a1 * a1 / s / 2.0;
367 } else {
368 v += (a1 + a) / 2.0 * (depth - x1);
369 }
370 }
371 return v;
372}
373
374// ============================================================================
375// Inverse volume-to-depth for storage curves
376// ============================================================================
377
390inline double table_getStorageDepth(Table& tbl, double volume) noexcept {
391 const int n = static_cast<int>(tbl.x.size());
392 if (n == 0 || volume <= 0.0) return 0.0;
393
394 double x1 = tbl.x[0];
395 double a1 = tbl.y[0];
396 double v_accum = 0.0;
397
398 for (int i = 1; i < n; ++i) {
399 double x2 = tbl.x[i];
400 double a2 = tbl.y[i];
401 double dv = (a1 + a2) / 2.0 * (x2 - x1);
402
403 if (v_accum + dv >= volume) {
404 // Solve the quadratic: target volume falls in this interval.
405 // Area varies linearly: a(d) = a1 + s*(d - x1), s = (a2-a1)/(x2-x1)
406 // Volume: dv = a1*(d-x1) + s*(d-x1)^2/2
407 // Rearrange to standard form and apply quadratic formula.
408 double dx = x2 - x1;
409 double dv_need = volume - v_accum;
410 if (dx < 1.0e-10) return x1;
411
412 double s = (a2 - a1) / dx;
413 double depth;
414 if (std::fabs(s) < 1.0e-10) {
415 // Rectangular slice: dv = a1 * dd
416 depth = (a1 > 0.0) ? x1 + dv_need / a1 : x1;
417 } else {
418 // Quadratic: s/2 * dd^2 + a1 * dd - dv_need = 0
419 double disc = a1 * a1 + 2.0 * s * dv_need;
420 if (disc < 0.0) disc = 0.0;
421 depth = x1 + (-a1 + std::sqrt(disc)) / s;
422 }
423 return std::min(depth, x2);
424 }
425
426 v_accum += dv;
427 x1 = x2;
428 a1 = a2;
429 }
430
431 // Volume exceeds curve range — extrapolate linearly using last slope
432 if (tbl.x.size() >= 2) {
433 int last = n - 1;
434 double dx = tbl.x[last] - tbl.x[last - 1];
435 double da = tbl.y[last] - tbl.y[last - 1];
436 double s = (dx > 1.0e-10) ? da / dx : 0.0;
437 double dv_need = volume - v_accum;
438 double a1_ext = tbl.y[last];
439 double depth;
440 if (std::fabs(s) < 1.0e-10) {
441 depth = (a1_ext > 0.0) ? tbl.x[last] + dv_need / a1_ext : tbl.x[last];
442 } else {
443 double disc = a1_ext * a1_ext + 2.0 * s * dv_need;
444 if (disc < 0.0) disc = 0.0;
445 depth = tbl.x[last] + (-a1_ext + std::sqrt(disc)) / s;
446 }
447 return depth;
448 }
449 return tbl.x[n - 1];
450}
451
452// ============================================================================
453// Generic inverse lookup (y → x)
454// ============================================================================
455
466inline double table_inverseLookup(const Table& tbl, double y_query) noexcept {
467 const int n = static_cast<int>(tbl.x.size());
468 if (n == 0) return 0.0;
469 if (n == 1) return tbl.x[0];
470
471 if (y_query <= tbl.y[0]) return tbl.x[0];
472 if (y_query >= tbl.y[n - 1]) return tbl.x[n - 1];
473
474 for (int i = 1; i < n; ++i) {
475 if (tbl.y[i] >= y_query) {
476 double dy = tbl.y[i] - tbl.y[i - 1];
477 if (dy <= 0.0) return tbl.x[i - 1];
478 double t = (y_query - tbl.y[i - 1]) / dy;
479 return tbl.x[i - 1] + t * (tbl.x[i] - tbl.x[i - 1]);
480 }
481 }
482 return tbl.x[n - 1];
483}
484
485// ============================================================================
486// Extrapolating lookup (extends beyond table bounds)
487// ============================================================================
488
501inline double table_lookupEx(const Table& tbl, double x_query) noexcept {
502 const int n = static_cast<int>(tbl.x.size());
503 if (n == 0) return 0.0;
504 if (n == 1) return tbl.y[0];
505
506 // Below first entry: extrapolate using first interval slope
507 if (x_query <= tbl.x[0]) {
508 double dx = tbl.x[1] - tbl.x[0];
509 if (dx <= 0.0) return tbl.y[0];
510 double s = (tbl.y[1] - tbl.y[0]) / dx;
511 return tbl.y[0] + s * (x_query - tbl.x[0]);
512 }
513
514 // Above last entry: extrapolate using last interval slope
515 if (x_query >= tbl.x[n - 1]) {
516 double dx = tbl.x[n - 1] - tbl.x[n - 2];
517 if (dx <= 0.0) return tbl.y[n - 1];
518 double s = (tbl.y[n - 1] - tbl.y[n - 2]) / dx;
519 return tbl.y[n - 1] + s * (x_query - tbl.x[n - 1]);
520 }
521
522 // In-range: linear interpolation
523 for (int i = 1; i < n; ++i) {
524 if (tbl.x[i] >= x_query) {
525 double dx = tbl.x[i] - tbl.x[i - 1];
526 if (dx <= 0.0) return tbl.y[i - 1];
527 double t = (x_query - tbl.x[i - 1]) / dx;
528 return tbl.y[i - 1] + t * (tbl.y[i] - tbl.y[i - 1]);
529 }
530 }
531 return tbl.y[n - 1];
532}
533
534// ============================================================================
535// Step-function interval lookup (first entry > x)
536// ============================================================================
537
551inline double table_intervalLookup(const Table& tbl, double x_query) noexcept {
552 const int n = static_cast<int>(tbl.x.size());
553 if (n == 0) return 0.0;
554
555 for (int i = 0; i < n; ++i) {
556 if (tbl.x[i] > x_query) return tbl.y[i];
557 }
558 return tbl.y[n - 1];
559}
560
561// ============================================================================
562// Slope at a point
563// ============================================================================
564
576inline double table_getSlope(const Table& tbl, double x_query) noexcept {
577 const int n = static_cast<int>(tbl.x.size());
578 if (n < 2) return 0.0;
579
580 // Use first interval for x below range
581 if (x_query <= tbl.x[0]) {
582 double dx = tbl.x[1] - tbl.x[0];
583 return (dx > 0.0) ? (tbl.y[1] - tbl.y[0]) / dx : 0.0;
584 }
585
586 // Use last interval for x above range
587 if (x_query >= tbl.x[n - 1]) {
588 double dx = tbl.x[n - 1] - tbl.x[n - 2];
589 return (dx > 0.0) ? (tbl.y[n - 1] - tbl.y[n - 2]) / dx : 0.0;
590 }
591
592 for (int i = 1; i < n; ++i) {
593 if (tbl.x[i] >= x_query) {
594 double dx = tbl.x[i] - tbl.x[i - 1];
595 return (dx > 0.0) ? (tbl.y[i] - tbl.y[i - 1]) / dx : 0.0;
596 }
597 }
598 return 0.0;
599}
600
601// ============================================================================
602// Maximum y in non-decreasing portion
603// ============================================================================
604
614inline double table_getMaxY(const Table& tbl) noexcept {
615 const int n = static_cast<int>(tbl.x.size());
616 if (n == 0) return 0.0;
617 double ymax = tbl.y[0];
618 for (int i = 1; i < n; ++i) {
619 if (tbl.y[i] < ymax) break;
620 ymax = tbl.y[i];
621 }
622 return ymax;
623}
624
625// ============================================================================
626// TableData — SoA collection of all tables
627// ============================================================================
628
637struct TableData {
638 std::vector<Table> tables;
639
640 std::size_t count() const noexcept { return tables.size(); }
641
642 Table& operator[](int idx) { return tables[static_cast<std::size_t>(idx)]; }
643 const Table& operator[](int idx) const { return tables[static_cast<std::size_t>(idx)]; }
644
649 int add(const std::string& id, TableType type) {
650 tables.push_back({id, type, {}, {}, {}});
651 return static_cast<int>(tables.size()) - 1;
652 }
653
657 void reset_cursors() noexcept {
658 for (auto& t : tables) t.cursor.reset();
659 }
660};
661
662// ============================================================================
663// Table validation
664// ============================================================================
665
667 bool valid = true;
668 std::vector<std::string> errors;
669 std::vector<std::string> warnings;
670};
671
673
674// ============================================================================
675// File-backed table API
676// ============================================================================
677
678bool table_open_file(Table& tbl, std::size_t boundary_rows = 128);
679std::size_t table_load_cache(Table& tbl, std::size_t start_row);
680
681// ============================================================================
682// Multicolumn lookup API
683// ============================================================================
684
685double table_lookup_column(Table& tbl, int col_idx, double x_query);
686double table_step_column(Table& tbl, int col_idx, double x_query);
687
688} /* namespace openswmm */
689
690#endif /* OPENSWMM_ENGINE_TABLE_DATA_HPP */
Definition NodeCoupling.cpp:15
double table_getStorageVolume(Table &tbl, double depth) noexcept
Compute storage volume by trapezoidal integration of an area-vs-depth curve, matching legacy table_ge...
Definition TableData.hpp:329
TableType
Type of data stored in a Table.
Definition TableData.hpp:54
@ CURVE_PUMP5
Pump curve type 5 (head vs flow, variable speed)
@ CURVE_PUMP1
Pump curve type 1 (ON/OFF depth)
@ CURVE_PUMP4
Pump curve type 4 (depth vs speed)
@ CURVE_RATING
Outfall/weir rating curve.
@ CURVE_TIDAL
Tidal stage curve.
@ CURVE_SHAPE
Cross-section shape curve.
@ CURVE_STORAGE
Storage node volume-depth curve.
@ CURVE_DIVERSION
Diversion rating curve.
@ CURVE_CONTROL
Control rule action curve.
@ CURVE_PUMP2
Pump curve type 2 (head vs flow)
@ CURVE_PUMP3
Pump curve type 3 (volume vs time)
double table_getSlope(const Table &tbl, double x_query) noexcept
Compute the slope (dy/dx) at a given x value.
Definition TableData.hpp:576
double table_getStorageDepth(Table &tbl, double volume) noexcept
Invert a storage area-vs-depth curve to find depth from volume.
Definition TableData.hpp:390
double table_step_cursor(Table &tbl, double x_query) noexcept
Piecewise-constant (step function) table lookup with cursor.
Definition TableData.hpp:279
double table_lookup_column(Table &tbl, int col_idx, double x_query)
Definition TableData.cpp:641
TableValidation validate_table(Table &tbl)
Definition TableData.cpp:282
double table_getMaxY(const Table &tbl) noexcept
Return the maximum y value in the non-decreasing leading portion.
Definition TableData.hpp:614
bool table_open_file(Table &tbl, std::size_t boundary_rows)
Definition TableData.cpp:401
double table_intervalLookup(const Table &tbl, double x_query) noexcept
Step-function lookup: return y for the first x entry > x_query.
Definition TableData.hpp:551
@ TIMESERIES
Data from an in-file [TIMESERIES].
double table_lookupEx(const Table &tbl, double x_query) noexcept
Linear-interpolating lookup with linear extrapolation outside bounds.
Definition TableData.hpp:501
double table_inverseLookup(const Table &tbl, double y_query) noexcept
Inverse lookup: given y, find corresponding x by linear interpolation.
Definition TableData.hpp:466
double table_lookup_cursor(Table &tbl, double x_query) noexcept
Look up a value in a Table using the bidirectional cursor.
Definition TableData.hpp:185
std::size_t table_load_cache(Table &tbl, std::size_t start_row)
Definition TableData.cpp:526
double table_tseries_lookup_cursor(Table &tbl, double x_query) noexcept
Time-series linear interpolation with out-of-range → 0 behaviour.
Definition TableData.hpp:245
double table_step_column(Table &tbl, int col_idx, double x_query)
Definition TableData.cpp:694
A single time series or rating curve.
Definition TableData.hpp:109
bool empty() const noexcept
Definition TableData.hpp:116
std::size_t num_rows() const noexcept
Definition TableData.hpp:115
std::size_t file_row_start
Row offset in file (cache only)
Definition TableData.hpp:113
std::vector< double > x
Independent variable values.
Definition TableData.hpp:110
int num_cols
Number of value columns.
Definition TableData.hpp:112
std::vector< double > y
Dependent values (flat: row-major, num_cols per row)
Definition TableData.hpp:111
Bidirectional cursor tracking the last accessed index in a Table.
Definition TableData.hpp:84
int index
Index of the last successful lookup entry.
Definition TableData.hpp:85
int direction
Last seek direction: +1 = forward, -1 = backward.
Definition TableData.hpp:86
void reset() noexcept
Definition TableData.hpp:88
SoA collection of all time series and curves in the model.
Definition TableData.hpp:637
std::vector< Table > tables
All tables in index order.
Definition TableData.hpp:638
std::size_t count() const noexcept
Definition TableData.hpp:640
int add(const std::string &id, TableType type)
Add a new empty table with the given ID and type.
Definition TableData.hpp:649
void reset_cursors() noexcept
Reset all cursors (call before re-running a simulation).
Definition TableData.hpp:657
Table & operator[](int idx)
Definition TableData.hpp:642
const Table & operator[](int idx) const
Definition TableData.hpp:643
Definition TableData.hpp:119
long data_start_offset
File offset to first data row.
Definition TableData.hpp:146
std::size_t total_rows
Total data rows in file.
Definition TableData.hpp:144
TableBlock cache
Sliding cache window for file lookups.
Definition TableData.hpp:139
std::vector< long > row_offsets
Sparse byte-offset index into file.
Definition TableData.hpp:147
std::string comment
Object comment from the INP file (';'-prefixed lines immediately above the first row of this table),...
Definition TableData.hpp:128
std::size_t num_cache_rows
Cache window size (rows)
Definition TableData.hpp:145
std::unordered_map< std::string, int > column_map
Column name → index.
Definition TableData.hpp:149
TableCursor cursor
Bidirectional lookup cursor.
Definition TableData.hpp:131
TableType type
Table type (TIMESERIES, CURVE_*, etc.)
Definition TableData.hpp:121
bool empty() const noexcept
True if the table has at least one data point.
Definition TableData.hpp:157
double dx_min
Minimum inter-entry x spacing.
Definition TableData.hpp:140
std::string file_path
Path to external data file.
Definition TableData.hpp:136
std::string id
Table identifier (from input file)
Definition TableData.hpp:120
double x_max
Maximum x value in file.
Definition TableData.hpp:142
std::vector< double > y
Dependent variable (flow, volume, etc.)
Definition TableData.hpp:130
std::FILE * file_handle
Open file handle (owned)
Definition TableData.hpp:135
bool is_file_based
True if data is read from external file.
Definition TableData.hpp:134
TableBlock last_boundary
Last rows from file (for validation)
Definition TableData.hpp:138
static constexpr std::size_t INDEX_STRIDE
Rows between offset index entries.
Definition TableData.hpp:151
std::vector< double > x
Independent variable (time, depth, etc.)
Definition TableData.hpp:129
double x_min
Minimum x value in file.
Definition TableData.hpp:141
TableBlock first_boundary
First rows from file (for validation)
Definition TableData.hpp:137
int num_cols
Number of value columns.
Definition TableData.hpp:143
std::size_t size() const noexcept
Number of data points.
Definition TableData.hpp:154
std::vector< std::string > column_ids
Column identifiers.
Definition TableData.hpp:148
Definition TableData.hpp:666
std::vector< std::string > errors
Definition TableData.hpp:668
std::vector< std::string > warnings
Definition TableData.hpp:669
bool valid
Definition TableData.hpp:667