OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
TableData.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
47
48#ifndef OPENSWMM_ENGINE_TABLE_DATA_HPP
49#define OPENSWMM_ENGINE_TABLE_DATA_HPP
50
53#include <string>
54#include <vector>
55#include <unordered_map>
56#include <cstdio>
57#include <cstdint>
58#include <cassert>
59#include <algorithm>
60#include <cmath>
61
62namespace openswmm {
63
64// ============================================================================
65// Table types (matching legacy SWMM enums.h)
66// ============================================================================
67
87
88// ============================================================================
89// TableCursor
90// ============================================================================
91
104 int index = 0;
105 int direction = +1;
106
112 void reset() noexcept {
113 index = 0;
114 direction = +1;
115 }
116};
117
118// ============================================================================
119// Table
120// ============================================================================
121
137 std::vector<double> x;
138 std::vector<double> y;
139 int num_cols = 1;
140 std::size_t file_row_start = 0;
141
146 std::size_t num_rows() const noexcept { return x.size(); }
151 bool empty() const noexcept { return x.empty(); }
152};
153
154struct Table {
155 std::string id;
157
163 std::string comment;
164 std::vector<double> x;
165 std::vector<double> y;
167
168 // ---- Time-only (relative) timeseries rows ----
169 // Legacy SWMM seeds every timeseries' lastDate with StartDate+StartTime
170 // (input.c:170), so rows authored WITHOUT a date are elapsed times
171 // anchored at the simulation start, until an explicit date re-anchors
172 // the series. The parser counts those leading date-less rows here and
173 // resolve_cross_references() adds options.start_date to exactly those
174 // rows (recording the applied offset in rel_anchor so re-resolution is
175 // idempotent and a later START_DATE change re-anchors by the delta).
176 // InpWriter subtracts rel_anchor to emit the rows back in their
177 // authored time-only form; rows at index >= n_relative are absolute
178 // date/times and are written with explicit dates.
179 int n_relative = 0;
180 double rel_anchor = 0.0;
181
182 // ---- File-backed time series support ----
183 bool is_file_based = false;
184 std::FILE* file_handle = nullptr;
190 double dx_min = 0.0;
191 double x_min = 0.0;
192 double x_max = 0.0;
193 int num_cols = 1;
194 std::size_t total_rows = 0;
195 std::size_t num_cache_rows = 8192;
197 std::vector<long> row_offsets;
198 std::vector<std::string> column_ids;
199 std::unordered_map<std::string, int> column_map;
200
201 static constexpr std::size_t INDEX_STRIDE = 4096;
202
204 std::size_t size() const noexcept { return x.size(); }
205
207 bool empty() const noexcept { return x.empty(); }
208};
209
210// ============================================================================
211// Cursor-optimized lookup
212// ============================================================================
213
235inline double table_lookup_cursor(Table& tbl, double x_query) noexcept {
236 const int n = static_cast<int>(tbl.x.size());
237 if (n == 0)
238 return 0.0;
239 if (n == 1)
240 return tbl.y[0];
241
242 // Clamp below first entry
243 if (x_query <= tbl.x[0]) {
244 tbl.cursor.index = 0;
245 tbl.cursor.direction = +1;
246 return tbl.y[0];
247 }
248
249 // Clamp above last entry — strictly above only: legacy table_lookup
250 // (src/legacy/engine/table.c:400) interpolates the last segment when
251 // x == x[n-1] (its scan condition is `x <= x2`), so equality must fall
252 // through to the interpolation below for bit parity.
253 if (x_query > tbl.x[n - 1]) {
254 tbl.cursor.index = n - 1;
255 tbl.cursor.direction = -1;
256 return tbl.y[n - 1];
257 }
258
259 // Start from cursor position and seek in the most likely direction
260 int idx = std::clamp(tbl.cursor.index, 0, n - 2);
261
262 // Forward seek
263 while (idx < n - 1 && tbl.x[idx + 1] < x_query) {
264 ++idx;
265 tbl.cursor.direction = +1;
266 }
267
268 // Backward seek — `>=` so a query landing exactly on an interior knot
269 // selects the segment ENDING at that knot, matching legacy table_lookup's
270 // first-match linear scan (`if (x <= x2) return table_interpolate(...)`,
271 // src/legacy/engine/table.c:426).
272 while (idx > 0 && tbl.x[idx] >= x_query) {
273 --idx;
274 tbl.cursor.direction = -1;
275 }
276
277 tbl.cursor.index = idx;
278
279 // PARITY: op-for-op transliteration of legacy table_interpolate
280 // (src/legacy/engine/table.c:54-67): multiply THEN divide —
281 // y1 + (x - x1) * (y2 - y1) / dx
282 // (the previous t=(x-x1)/dx; y1+t*(y2-y1) form rounds differently by
283 // 1 ULP), and the legacy degenerate-segment guard |dx| < 1e-20 → mean.
284 const double x1 = tbl.x[idx], y1 = tbl.y[idx];
285 const double x2 = tbl.x[idx + 1], y2 = tbl.y[idx + 1];
286 const double dx = x2 - x1;
287 if (std::fabs(dx) < 1.0e-20)
288 return (y1 + y2) / 2.;
289 return y1 + (x_query - x1) * (y2 - y1) / dx;
290}
291
309inline double table_tseries_lookup_cursor(Table& tbl, double x_query) noexcept {
310 const int n = static_cast<int>(tbl.x.size());
311 if (n == 0)
312 return 0.0;
313
314 // Before first entry or after last entry → 0 (series not active)
315 if (x_query <= tbl.x[0]) {
316 tbl.cursor.index = 0;
317 tbl.cursor.direction = +1;
318 return (x_query < tbl.x[0]) ? 0.0 : tbl.y[0];
319 }
320 if (x_query >= tbl.x[n - 1]) {
321 tbl.cursor.index = n - 1;
322 tbl.cursor.direction = -1;
323 return (x_query > tbl.x[n - 1]) ? 0.0 : tbl.y[n - 1];
324 }
325
326 // Delegate to cursor-based linear interpolation for the in-range case
327 return table_lookup_cursor(tbl, x_query);
328}
329
344inline double table_step_cursor(Table& tbl, double x_query) noexcept {
345 const int n = static_cast<int>(tbl.x.size());
346 if (n == 0)
347 return 0.0;
348 if (n == 1)
349 return tbl.y[0];
350
351 // Before first entry → 0 (no rain before first recorded value)
352 if (x_query < tbl.x[0]) {
353 tbl.cursor.index = 0;
354 tbl.cursor.direction = +1;
355 return 0.0;
356 }
357
358 // At or past last entry → return last value.
359 // The caller (Gage.cpp) handles the rain interval cutoff and returns 0
360 // after entry_time + rainInterval. This allows the last entry's value to
361 // be used for its full recording interval before going to zero.
362 if (x_query >= tbl.x[n - 1]) {
363 tbl.cursor.index = n - 1;
364 tbl.cursor.direction = -1;
365 return tbl.y[n - 1];
366 }
367
368 // Seek to the interval containing x_query: x[idx] <= x_query < x[idx+1]
369 int idx = std::clamp(tbl.cursor.index, 0, n - 2);
370
371 while (idx < n - 1 && tbl.x[idx + 1] <= x_query) {
372 ++idx;
373 tbl.cursor.direction = +1;
374 }
375 while (idx > 0 && tbl.x[idx] > x_query) {
376 --idx;
377 tbl.cursor.direction = -1;
378 }
379
380 tbl.cursor.index = idx;
381 return tbl.y[idx];
382}
383
384// ============================================================================
385// Storage volume by trapezoidal integration of area curve
386// ============================================================================
387
396inline double table_getStorageVolume(Table& tbl, double depth) noexcept {
397 const int n = static_cast<int>(tbl.x.size());
398 if (n == 0 || depth <= 0.0)
399 return 0.0;
400
401 double x1 = tbl.x[0];
402 double a1 = tbl.y[0];
403
404 // Target below first entry — triangular approximation
405 if (depth <= x1) {
406 if (x1 < 1.0e-6)
407 return 0.0;
408 return (a1 / x1) * depth * depth / 2.0;
409 }
410
411 // Traverse entries using end-area (trapezoidal) method
412 double v = 0.0;
413 double dx = 0.0, dy = 0.0;
414 for (int i = 1; i < n; ++i) {
415 double x2 = tbl.x[i];
416 double a2 = tbl.y[i];
417 if (x2 >= depth) {
418 // Bracketed — interpolate area at target depth.
419 // PARITY: legacy table_getStorageVolume (table.c:624) calls
420 // table_interpolate (table.c:54): multiply THEN divide,
421 // |dx| < 1e-20 degenerate guard → mean of end areas.
422 const double dxi = x2 - x1;
423 double a;
424 if (std::fabs(dxi) < 1.0e-20)
425 a = (a1 + a2) / 2.;
426 else
427 a = a1 + (depth - x1) * (a2 - a1) / dxi;
428 return v + (a1 + a) / 2.0 * (depth - x1);
429 }
430 dx = x2 - x1;
431 dy = a2 - a1;
432 v += (a1 + a2) / 2.0 * dx;
433 x1 = x2;
434 a1 = a2;
435 }
436
437 // Extrapolate beyond last entry
438 if (dx > 1.0e-6) {
439 double s = dy / dx;
440 double a = a1 + s * (depth - x1);
441 if (a < 0.0) {
442 v -= a1 * a1 / s / 2.0;
443 } else {
444 v += (a1 + a) / 2.0 * (depth - x1);
445 }
446 }
447 return v;
448}
449
450// ============================================================================
451// Inverse volume-to-depth for storage curves
452// ============================================================================
453
466inline double table_getStorageDepth(Table& tbl, double volume) noexcept {
467 const int n = static_cast<int>(tbl.x.size());
468 if (n == 0 || volume <= 0.0)
469 return 0.0;
470
471 double x1 = tbl.x[0];
472 double a1 = tbl.y[0];
473 double v_accum = 0.0;
474
475 for (int i = 1; i < n; ++i) {
476 double x2 = tbl.x[i];
477 double a2 = tbl.y[i];
478 double dv = (a1 + a2) / 2.0 * (x2 - x1);
479
480 if (v_accum + dv >= volume) {
481 // Solve the quadratic: target volume falls in this interval.
482 // Area varies linearly: a(d) = a1 + s*(d - x1), s = (a2-a1)/(x2-x1)
483 // Volume: dv = a1*(d-x1) + s*(d-x1)^2/2
484 // Rearrange to standard form and apply quadratic formula.
485 double dx = x2 - x1;
486 double dv_need = volume - v_accum;
487 if (dx < 1.0e-10)
488 return x1;
489
490 double s = (a2 - a1) / dx;
491 double depth;
492 if (std::fabs(s) < 1.0e-10) {
493 // Rectangular slice: dv = a1 * dd
494 depth = (a1 > 0.0) ? x1 + dv_need / a1 : x1;
495 } else {
496 // Quadratic: s/2 * dd^2 + a1 * dd - dv_need = 0
497 double disc = a1 * a1 + 2.0 * s * dv_need;
498 if (disc < 0.0)
499 disc = 0.0;
500 depth = x1 + (-a1 + std::sqrt(disc)) / s;
501 }
502 return std::min(depth, x2);
503 }
504
505 v_accum += dv;
506 x1 = x2;
507 a1 = a2;
508 }
509
510 // Volume exceeds curve range — extrapolate linearly using last slope
511 if (tbl.x.size() >= 2) {
512 int last = n - 1;
513 double dx = tbl.x[last] - tbl.x[last - 1];
514 double da = tbl.y[last] - tbl.y[last - 1];
515 double s = (dx > 1.0e-10) ? da / dx : 0.0;
516 double dv_need = volume - v_accum;
517 double a1_ext = tbl.y[last];
518 double depth;
519 if (std::fabs(s) < 1.0e-10) {
520 depth = (a1_ext > 0.0) ? tbl.x[last] + dv_need / a1_ext : tbl.x[last];
521 } else {
522 double disc = a1_ext * a1_ext + 2.0 * s * dv_need;
523 if (disc < 0.0)
524 disc = 0.0;
525 depth = tbl.x[last] + (-a1_ext + std::sqrt(disc)) / s;
526 }
527 return depth;
528 }
529 return tbl.x[n - 1];
530}
531
532// ============================================================================
533// Generic inverse lookup (y → x)
534// ============================================================================
535
546inline double table_inverseLookup(const Table& tbl, double y_query) noexcept {
547 const int n = static_cast<int>(tbl.x.size());
548 if (n == 0)
549 return 0.0;
550 if (n == 1)
551 return tbl.x[0];
552
553 if (y_query <= tbl.y[0])
554 return tbl.x[0];
555 if (y_query >= tbl.y[n - 1])
556 return tbl.x[n - 1];
557
558 for (int i = 1; i < n; ++i) {
559 if (tbl.y[i] >= y_query) {
560 double dy = tbl.y[i] - tbl.y[i - 1];
561 if (dy <= 0.0)
562 return tbl.x[i - 1];
563 double t = (y_query - tbl.y[i - 1]) / dy;
564 return tbl.x[i - 1] + t * (tbl.x[i] - tbl.x[i - 1]);
565 }
566 }
567 return tbl.x[n - 1];
568}
569
570// ============================================================================
571// Extrapolating lookup (extends beyond table bounds)
572// ============================================================================
573
586inline double table_lookupEx(const Table& tbl, double x_query) noexcept {
587 // PARITY: op-for-op transliteration of legacy table_lookupEx
588 // (src/legacy/engine/table.c:469-505):
589 // - below first entry: x/x1*y1 when x1 > 0 (line through the origin),
590 // else y1 — NOT first-interval slope extrapolation;
591 // - in range: table_interpolate (multiply THEN divide, |dx| < 1e-20
592 // degenerate guard → mean);
593 // - above last entry: extrapolate with the slope `s` of the last
594 // segment seen during the walk (skipping x2 == x1 segments),
595 // clamped at s >= 0.
596 const int n = static_cast<int>(tbl.x.size());
597 if (n == 0)
598 return 0.0;
599
600 double x1 = tbl.x[0];
601 double y1 = tbl.y[0];
602 double s = 0.0;
603 if (x_query <= x1) {
604 if (x1 > 0.0)
605 return x_query / x1 * y1;
606 else
607 return y1;
608 }
609 for (int i = 1; i < n; ++i) {
610 const double x2 = tbl.x[i];
611 const double y2 = tbl.y[i];
612 if (x2 != x1)
613 s = (y2 - y1) / (x2 - x1);
614 if (x_query <= x2) {
615 const double dx = x2 - x1;
616 if (std::fabs(dx) < 1.0e-20)
617 return (y1 + y2) / 2.;
618 return y1 + (x_query - x1) * (y2 - y1) / dx;
619 }
620 x1 = x2;
621 y1 = y2;
622 }
623 if (s < 0.0)
624 s = 0.0;
625 return y1 + s * (x_query - x1);
626}
627
628// ============================================================================
629// Step-function interval lookup (first entry > x)
630// ============================================================================
631
645inline double table_intervalLookup(const Table& tbl, double x_query) noexcept {
646 const int n = static_cast<int>(tbl.x.size());
647 if (n == 0)
648 return 0.0;
649
650 for (int i = 0; i < n; ++i) {
651 if (tbl.x[i] > x_query)
652 return tbl.y[i];
653 }
654 return tbl.y[n - 1];
655}
656
657// ============================================================================
658// Slope at a point
659// ============================================================================
660
672inline double table_getSlope(const Table& tbl, double x_query) noexcept {
673 const int n = static_cast<int>(tbl.x.size());
674 if (n < 2)
675 return 0.0;
676
677 // Use first interval for x below range
678 if (x_query <= tbl.x[0]) {
679 double dx = tbl.x[1] - tbl.x[0];
680 return (dx > 0.0) ? (tbl.y[1] - tbl.y[0]) / dx : 0.0;
681 }
682
683 // Above the last entry the slope is 0: legacy table_getSlope's scan
684 // runs off the end with x1 = x2 = the last entry (dx == 0 → 0.0). A
685 // head-based pump driven past its curve therefore contributes NO dqdh
686 // to its end nodes' surcharge denominator (session81-small-pump-fm-
687 // model's 5002 at 142 ft of head: the last segment's -15.6 GPM/ft
688 // here kept node 16's Picard update damped where legacy let it run).
689 // Exactly AT the last entry legacy breaks on `x <= x2` with the last
690 // segment bracketed, so that case falls through to the loop.
691 if (x_query > tbl.x[n - 1])
692 return 0.0;
693
694 for (int i = 1; i < n; ++i) {
695 if (tbl.x[i] >= x_query) {
696 double dx = tbl.x[i] - tbl.x[i - 1];
697 return (dx > 0.0) ? (tbl.y[i] - tbl.y[i - 1]) / dx : 0.0;
698 }
699 }
700 return 0.0;
701}
702
703// ============================================================================
704// Maximum y in non-decreasing portion
705// ============================================================================
706
716inline double table_getMaxY(const Table& tbl) noexcept {
717 const int n = static_cast<int>(tbl.x.size());
718 if (n == 0)
719 return 0.0;
720 double ymax = tbl.y[0];
721 for (int i = 1; i < n; ++i) {
722 if (tbl.y[i] < ymax)
723 break;
724 ymax = tbl.y[i];
725 }
726 return ymax;
727}
728
729// ============================================================================
730// TableData — SoA collection of all tables
731// ============================================================================
732
741struct TableData {
742 std::vector<Table> tables;
743
762 std::unordered_map<std::string, std::vector<int>, CiHash, CiEqual> by_name;
763
768 std::size_t count() const noexcept { return tables.size(); }
769
779 Table& operator[](int idx) { return tables[static_cast<std::size_t>(idx)]; }
786 const Table& operator[](int idx) const { return tables[static_cast<std::size_t>(idx)]; }
787
799 int add(const std::string& id, TableType type) {
800 Table t;
801 t.id = id;
802 t.type = type;
803 tables.push_back(std::move(t));
804 const int idx = static_cast<int>(tables.size()) - 1;
805 by_name[id].push_back(idx);
806 return idx;
807 }
808
817 by_name.clear();
818 by_name.reserve(tables.size());
819 for (std::size_t i = 0; i < tables.size(); ++i)
820 by_name[tables[i].id].push_back(static_cast<int>(i));
821 }
822
833 int find_by_kind(std::string_view name, bool want_timeseries) const noexcept {
834 const auto it = by_name.find(name);
835 if (it == by_name.end())
836 return -1;
837 for (const int i : it->second) {
838 const bool is_ts = tables[static_cast<std::size_t>(i)].type == TableType::TIMESERIES;
839 if (is_ts == want_timeseries)
840 return i;
841 }
842 return -1;
843 }
844
848 void reset_cursors() noexcept {
849 for (auto& t : tables)
850 t.cursor.reset();
851 }
852};
853
854// ============================================================================
855// Table validation
856// ============================================================================
857
859 bool valid = true;
860 std::vector<std::string> errors;
861 std::vector<std::string> warnings;
862};
863
877
878// ============================================================================
879// File-backed table API
880// ============================================================================
881
896bool table_open_file(Table& tbl, std::size_t boundary_rows = 128);
909std::size_t table_load_cache(Table& tbl, std::size_t start_row);
910
911// ============================================================================
912// Multicolumn lookup API
913// ============================================================================
914
930double table_lookup_column(Table& tbl, int col_idx, double x_query);
946double table_step_column(Table& tbl, int col_idx, double x_query);
947
948} /* namespace openswmm */
949
950#endif /* OPENSWMM_ENGINE_TABLE_DATA_HPP */
Carrier for an external file reference in a SWMM model.
Case-insensitive string helpers matching legacy SWMM name semantics.
Definition ExchangeLaw.hpp:60
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:396
TableType
Type of data stored in a Table.
Definition TableData.hpp:72
@ CURVE_PUMP5
Pump curve type 5 (head vs flow, variable speed)
Definition TableData.hpp:84
@ CURVE_PUMP1
Pump curve type 1 (ON/OFF depth)
Definition TableData.hpp:80
@ TIMESERIES
Rainfall, inflow, or other time-varying values.
Definition TableData.hpp:73
@ CURVE_PUMP4
Pump curve type 4 (depth vs speed)
Definition TableData.hpp:83
@ CURVE_RATING
Outfall/weir rating curve.
Definition TableData.hpp:76
@ CURVE_TIDAL
Tidal stage curve.
Definition TableData.hpp:79
@ CURVE_WEIR
Weir rating curve (legacy CurveTypeWords "WEIR")
Definition TableData.hpp:85
@ CURVE_SHAPE
Cross-section shape curve.
Definition TableData.hpp:77
@ CURVE_STORAGE
Storage node volume-depth curve.
Definition TableData.hpp:74
@ CURVE_DIVERSION
Diversion rating curve.
Definition TableData.hpp:75
@ CURVE_CONTROL
Control rule action curve.
Definition TableData.hpp:78
@ CURVE_PUMP2
Pump curve type 2 (head vs flow)
Definition TableData.hpp:81
@ CURVE_PUMP3
Pump curve type 3 (volume vs time)
Definition TableData.hpp:82
double table_getSlope(const Table &tbl, double x_query) noexcept
Compute the slope (dy/dx) at a given x value.
Definition TableData.hpp:672
double table_getStorageDepth(Table &tbl, double volume) noexcept
Invert a storage area-vs-depth curve to find depth from volume.
Definition TableData.hpp:466
double table_step_cursor(Table &tbl, double x_query) noexcept
Piecewise-constant (step function) table lookup with cursor.
Definition TableData.hpp:344
bool table_open_file(Table &tbl, std::size_t boundary_rows)
Open a legacy streaming table file and prepare sparse index/boundaries.
Definition TableData.cpp:473
double table_lookup_column(Table &tbl, int col_idx, double x_query)
Read and linearly interpolate a selected value column.
Definition TableData.cpp:719
TableValidation validate_table(Table &tbl)
Check table structure and derive its smallest positive x interval.
Definition TableData.cpp:344
double table_getMaxY(const Table &tbl) noexcept
Return the maximum y value in the non-decreasing leading portion.
Definition TableData.hpp:716
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:645
@ TIMESERIES
Data from an in-file [TIMESERIES].
Definition GageData.hpp:58
double table_lookupEx(const Table &tbl, double x_query) noexcept
Linear-interpolating lookup with linear extrapolation outside bounds.
Definition TableData.hpp:586
double table_inverseLookup(const Table &tbl, double y_query) noexcept
Inverse lookup: given y, find corresponding x by linear interpolation.
Definition TableData.hpp:546
double table_lookup_cursor(Table &tbl, double x_query) noexcept
Look up a value in a Table using the bidirectional cursor.
Definition TableData.hpp:235
std::size_t table_load_cache(Table &tbl, std::size_t start_row)
Replace the streaming cache starting at a logical data-row offset.
Definition TableData.cpp:600
double table_tseries_lookup_cursor(Table &tbl, double x_query) noexcept
Time-series linear interpolation with out-of-range → 0 behaviour.
Definition TableData.hpp:309
double table_step_column(Table &tbl, int col_idx, double x_query)
Read and step-hold a selected value column.
Definition TableData.cpp:773
Transparent case-insensitive equality for unordered containers.
Definition StringCase.hpp:101
Transparent case-insensitive hash (FNV-1a over the uppercase fold).
Definition StringCase.hpp:80
Two-string carrier for any external file path that appears in a SWMM .inp file.
Definition FilePathPair.hpp:63
A single time series or rating curve.
Definition TableData.hpp:136
bool empty() const noexcept
Test whether a boundary/cache has no independent-variable rows.
Definition TableData.hpp:151
std::size_t num_rows() const noexcept
Count boundary/cache rows from the independent-variable vector.
Definition TableData.hpp:146
std::size_t file_row_start
Row offset in file (cache only)
Definition TableData.hpp:140
std::vector< double > x
Independent variable values.
Definition TableData.hpp:137
int num_cols
Number of value columns.
Definition TableData.hpp:139
std::vector< double > y
Dependent values (flat: row-major, num_cols per row)
Definition TableData.hpp:138
Bidirectional cursor tracking the last accessed index in a Table.
Definition TableData.hpp:103
int index
Index of the last successful lookup entry.
Definition TableData.hpp:104
int direction
Last seek direction: +1 = forward, -1 = backward.
Definition TableData.hpp:105
void reset() noexcept
Restore the lookup hint to row zero and forward direction.
Definition TableData.hpp:112
SoA collection of all time series and curves in the model.
Definition TableData.hpp:741
std::vector< Table > tables
All tables in index order.
Definition TableData.hpp:742
std::size_t count() const noexcept
Count stored time-series and curve tables.
Definition TableData.hpp:768
std::unordered_map< std::string, std::vector< int >, CiHash, CiEqual > by_name
Case-insensitive name → table indices, ascending.
Definition TableData.hpp:762
int add(const std::string &id, TableType type)
Append an empty table and index its copied name.
Definition TableData.hpp:799
void reset_cursors() noexcept
Reset all cursors (call before re-running a simulation).
Definition TableData.hpp:848
void rebuild_index()
Rebuilds by_name from tables.
Definition TableData.hpp:816
Table & operator[](int idx)
Borrow a mutable stored table.
Definition TableData.hpp:779
int find_by_kind(std::string_view name, bool want_timeseries) const noexcept
Find the first indexed table matching a case-insensitive name and kind.
Definition TableData.hpp:833
const Table & operator[](int idx) const
Borrow an immutable stored table.
Definition TableData.hpp:786
Definition TableData.hpp:154
long data_start_offset
File offset to first data row.
Definition TableData.hpp:196
std::size_t total_rows
Total data rows in file.
Definition TableData.hpp:194
TableBlock cache
Sliding cache window for file lookups.
Definition TableData.hpp:189
std::vector< long > row_offsets
Sparse byte-offset index into file.
Definition TableData.hpp:197
std::string comment
Object comment from the INP file (';'-prefixed lines immediately above the first row of this table),...
Definition TableData.hpp:163
std::size_t num_cache_rows
Cache window size (rows)
Definition TableData.hpp:195
std::unordered_map< std::string, int > column_map
Column name → index.
Definition TableData.hpp:199
TableCursor cursor
Bidirectional lookup cursor.
Definition TableData.hpp:166
TableType type
Table type (TIMESERIES, CURVE_*, etc.)
Definition TableData.hpp:156
bool empty() const noexcept
True if the table has at least one data point.
Definition TableData.hpp:207
double dx_min
Minimum inter-entry x spacing.
Definition TableData.hpp:190
double rel_anchor
start_date offset currently baked into them
Definition TableData.hpp:180
std::string id
Table identifier (from input file)
Definition TableData.hpp:155
double x_max
Maximum x value in file.
Definition TableData.hpp:192
std::vector< double > y
Dependent variable (flow, volume, etc.)
Definition TableData.hpp:165
std::FILE * file_handle
Open file handle (owned)
Definition TableData.hpp:184
FilePathPair file_path
Definition TableData.hpp:185
bool is_file_based
True if data is read from external file.
Definition TableData.hpp:183
TableBlock last_boundary
Last rows from file (for validation)
Definition TableData.hpp:188
int n_relative
Leading rows authored as elapsed time-of-start.
Definition TableData.hpp:179
static constexpr std::size_t INDEX_STRIDE
Rows between offset index entries.
Definition TableData.hpp:201
std::vector< double > x
Independent variable (time, depth, etc.)
Definition TableData.hpp:164
double x_min
Minimum x value in file.
Definition TableData.hpp:191
TableBlock first_boundary
First rows from file (for validation)
Definition TableData.hpp:187
int num_cols
Number of value columns.
Definition TableData.hpp:193
std::size_t size() const noexcept
Number of data points.
Definition TableData.hpp:204
std::vector< std::string > column_ids
Column identifiers.
Definition TableData.hpp:198
Definition TableData.hpp:858
std::vector< std::string > errors
Definition TableData.hpp:860
std::vector< std::string > warnings
Definition TableData.hpp:861
bool valid
Definition TableData.hpp:859