OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
ModelSupplement.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2#pragma once
3
4#include "GpkgUtils.hpp"
9#include "../Tokenizer.hpp"
10#include <set>
11#include <sstream>
12#include <iomanip>
13
14namespace openswmm::gpkg {
15inline void checked_model_step(sqlite3* db, sqlite3_stmt* statement) {
16 const int rc = sqlite3_step(statement);
17 if (rc != SQLITE_DONE) {
18 const std::string error = sqlite3_errmsg(db);
19 sqlite3_reset(statement);
20 throw GpkgError(error, rc);
21 }
22}
23
24
25// Relational columns remain authoritative. Native sections supply only fields
26// the relational schema cannot represent, so GIS edits to existing tables still
27// take effect. Numeric sections use the same internal-unit convention as tables.
28inline void write_model_supplement(sqlite3* db, const SimulationContext& ctx,
29 const std::string& simulation) {
30 exec(db, "CREATE TABLE IF NOT EXISTS model_supplement (simulation_id TEXT PRIMARY KEY, "
31 "version INTEGER NOT NULL, sections TEXT NOT NULL)");
32 exec(db, "CREATE TABLE IF NOT EXISTS model_properties (simulation_id TEXT NOT NULL, "
33 "scope TEXT NOT NULL, object_id TEXT NOT NULL, property TEXT NOT NULL, value TEXT NOT NULL, "
34 "PRIMARY KEY(simulation_id,scope,object_id,property))");
35 auto clear = prepare(db, "DELETE FROM model_properties WHERE simulation_id=?");
36 bind_text(clear.get(), 1, simulation);
37 if (sqlite3_step(clear.get()) != SQLITE_DONE) throw GpkgError(sqlite3_errmsg(db));
38 auto property = prepare(db, "INSERT INTO model_properties VALUES (?,?,?,?,?)");
39 auto put = [&](const std::string& scope, const std::string& id,
40 const std::string& key, const auto& value) {
41 std::ostringstream out;
42 out << std::setprecision(17) << value;
43 sqlite3_reset(property.get());
44 bind_text(property.get(), 1, simulation); bind_text(property.get(), 2, scope);
45 bind_text(property.get(), 3, id); bind_text(property.get(), 4, key);
46 bind_text(property.get(), 5, out.str());
47 if (sqlite3_step(property.get()) != SQLITE_DONE) throw GpkgError(sqlite3_errmsg(db));
48 };
49 auto comments = [&](const char* scope, const auto& names, const auto& values) {
50 for (int i = 0; i < names.size(); ++i)
51 if (static_cast<std::size_t>(i) < values.size())
52 put(scope, names.name_of(i), "comment", values[i]);
53 };
54 put("model", "", "title_count", ctx.title_notes.size());
55 for (std::size_t i=0; i<ctx.title_notes.size(); ++i)
56 put("title", std::to_string(i), "line", ctx.title_notes[i]);
57 comments("node", ctx.node_names, ctx.nodes.comments);
58 comments("link", ctx.link_names, ctx.links.comments);
59 comments("subcatch", ctx.subcatch_names, ctx.subcatches.comments);
60 comments("gage", ctx.gage_names, ctx.gages.comments);
61 comments("pollutant", ctx.pollutant_names, ctx.pollutants.comments);
62 for (int i = 0; i < ctx.n_tables(); ++i)
63 put("table", ctx.tables[i].id, "comment", ctx.tables[i].comment);
64 for (int i = 0; i < ctx.gage_names.size(); ++i)
65 put("gage", ctx.gage_names.name_of(i), "scale",
66 i < static_cast<int>(ctx.gages.scale_factor.size()) ? ctx.gages.scale_factor[i] : 1.0);
67 for (int i = 0; i < ctx.n_subcatches(); ++i) {
68 const auto& s = ctx.subcatches;
69 std::string name = i < static_cast<int>(s.snowpack_name.size()) ? s.snowpack_name[i] : "";
70 if (i < static_cast<int>(s.snowpack.size()) && s.snowpack[i] >= 0)
71 name = ctx.snowpack_names.name_of(s.snowpack[i]);
72 put("subcatch", ctx.subcatch_names.name_of(i), "snowpack", name);
73 }
74 for (int i = 0; i < ctx.n_pollutants(); ++i)
75 put("pollutant", ctx.pollutant_names.name_of(i), "mwt",
76 i < static_cast<int>(ctx.pollutants.mwt.size()) ? ctx.pollutants.mwt[i] : 1.0);
77 for (int i = 0; i < ctx.n_links(); ++i) {
78 const int row = ctx.link_subtypes.weir_row(i);
79 if (row < 0) continue;
80 const auto& w = ctx.link_subtypes.weirs;
81 const auto& id = ctx.link_names.name_of(i);
82 put("weir", id, "cd2", w.cd2[row]);
83 put("weir", id, "end_contractions", w.end_contractions[row]);
84 put("weir", id, "can_surcharge", int(w.can_surcharge[row]));
85 put("weir", id, "road_width", w.road_width[row]);
86 put("weir", id, "road_surface", int(w.road_surface[row]));
87 put("weir", id, "cd_curve", w.cd_curve[row] >= 0 ? ctx.tables[w.cd_curve[row]].id : "");
88 }
89
90 for (const auto& [id, text] : native_model::configurations(ctx))
91 put("component", id, "configuration", text);
92
93 std::set<std::string> options;
94 auto keys = prepare(db, "SELECT key FROM options WHERE simulation_id=?");
95 bind_text(keys.get(), 1, simulation);
96 while (sqlite3_step(keys.get()) == SQLITE_ROW) options.insert(column_text(keys.get(), 0));
97 // Core sections already represented in normalized tables.
98 const std::set<std::string> relational = {
99 "TITLE", "EVAPORATION", "ADJUSTMENTS", "RAINGAGES", "SUBCATCHMENTS", "SUBAREAS", "INFILTRATION",
100 "JUNCTIONS", "VIRTUAL_JUNCTIONS", "INLET_JUNCTIONS", "OUTFALLS", "DIVIDERS", "STORAGE",
101 "CONDUITS", "PUMPS", "ORIFICES", "WEIRS", "OUTLETS", "XSECTIONS", "LOSSES",
102 "TRANSECTS", "STREETS", "INLETS", "INLET_USAGE", "2D_NODE_COUPLING",
103 "POLLUTANTS", "LANDUSES", "BUILDUP", "WASHOFF", "COVERAGES", "LOADINGS", "TREATMENT",
104 "INFLOWS", "DWF", "RDII", "HYDROGRAPHS", "PATTERNS", "TIMESERIES", "CURVES",
105 "CONTROLS", "REPORT", "MAP", "COORDINATES", "VERTICES", "POLYGONS", "SYMBOLS",
106 "LID_USAGE", "LID_NODES", "LID_NODE_OUTLETS", "LID_LAYER_TREATMENT", "LID_RICHARDS",
107 "2D_VERTICES", "2D_TRIANGLES", "2D_QUADS", "2D_VERTEX_NODE_MAP", "2D_TRIANGLE_NODE_MAP",
108 "2D_BOUNDARY_CONDITIONS", "2D_EDGE_CONVEYANCE", "FILES"};
109 const char* destination = sqlite3_db_filename(db, "main");
110 std::istringstream native(inp_writer::modelText(ctx, destination && *destination ? destination : ctx.inp_file_path, true));
111 std::string line, section, previous;
112 std::ostringstream extra;
113 while (std::getline(native, line)) {
114 if (!line.empty() && line.front() == '[') {
115 section = input::Tokenizer::to_upper(line.substr(1, line.find(']') - 1));
116 continue;
117 }
118 auto words = input::Tokenizer::tokenize(line);
119 if (words.empty() || relational.contains(section)) continue;
120 if (section == "OPTIONS") {
121 const std::string key = words[0];
122 // Dates are represented together in the relational option values.
123 if (options.contains(key) || key == "FLOW_ROUTING" || key == "FORCE_MAIN_EQUATION" ||
124 key == "START_TIME" || key == "END_TIME" || key.starts_with("REPORT_START_")) continue;
125 } else if (section == "2D_OPTIONS") {
126 if (options.contains("2D_" + words[0])) continue;
127 } else if (section == "TEMPERATURE") {
128 if (words[0] == "TIMESERIES" || words[0] == "FILE" || words[0] == "WINDSPEED" ||
129 words[0] == "SNOWMELT" || words[0] == "ADC") continue;
130 } else if (section == "SNOWPACKS") {
131 if (words.size() < 2 || words[1] != "UEB") continue;
132 } else if (section == "LID_CONTROLS") {
133 if (words.size() < 2 || (words[1] != "REMOVALS" && words[1] != "DRAIN")) continue;
134 }
135 if (section != previous) { extra << '\n' << '[' << section << "]\n"; previous = section; }
136 extra << line << '\n';
137 }
138 auto row = prepare(db, "INSERT OR REPLACE INTO model_supplement VALUES (?,1,?)");
139 bind_text(row.get(), 1, simulation); bind_text(row.get(), 2, extra.str());
140 if (sqlite3_step(row.get()) != SQLITE_DONE) throw GpkgError(sqlite3_errmsg(db));
141}
142
143inline bool has_model_supplement(sqlite3* db, const std::string& simulation) {
144 auto exists = prepare(db, "SELECT 1 FROM sqlite_master WHERE type='table' AND name='model_supplement'");
145 if (sqlite3_step(exists.get()) != SQLITE_ROW) return false;
146 auto row = prepare(db, "SELECT 1 FROM model_supplement WHERE simulation_id=?");
147 bind_text(row.get(), 1, simulation);
148 return sqlite3_step(row.get()) == SQLITE_ROW;
149}
150
151inline void read_model_supplement(sqlite3* db, SimulationContext& ctx,
152 const std::string& simulation) {
153 if (!has_model_supplement(db, simulation)) return;
154 auto row = prepare(db, "SELECT version,sections FROM model_supplement WHERE simulation_id=?");
155 bind_text(row.get(), 1, simulation);
156 if (sqlite3_step(row.get()) != SQLITE_ROW) return;
157 if (column_int(row.get(), 0) != 1) throw GpkgError("Unsupported model supplement version");
158 DefaultInputPlugin parser;
159 parser.registerContextSections(ctx);
160 std::istringstream input(column_text(row.get(), 1));
161 std::string line, section;
162 std::vector<std::string> body;
163 auto flush = [&] {
164 if (section.empty()) return;
165 if (section.starts_with("REACTION_")) ctx.embedded_component_sections.emplace_back(section, body);
166 else if (parser.registry().has(section)) {
167 const double reportStart = ctx.options.report_start;
168 parser.registry().dispatch(section, ctx, body);
169 // The OPTIONS handler defaults an absent report-start line to START_DATE;
170 // relational dates are authoritative for a partial section replay.
171 if (section == "OPTIONS") ctx.options.report_start = reportStart;
172 }
173 else if (section == "2D_OPTIONS" && !ctx.twod_io.options)
174 ctx.warnings.push_back("[2D_OPTIONS] ignored: no 2D options storage is attached");
175 else throw GpkgError("Unsupported saved model section: " + section);
176 body.clear();
177 };
178 while (std::getline(input, line)) {
179 if (!line.empty() && line.front() == '[') {
180 flush(); section = line.substr(1, line.find(']') - 1);
181 } else body.push_back(line);
182 }
183 flush();
184 auto props = prepare(db, "SELECT scope,object_id,property,value FROM model_properties WHERE simulation_id=?");
185 bind_text(props.get(), 1, simulation);
186 auto comment = [](auto& values, int index, const std::string& value) {
187 if (index >= 0) { if (values.size() <= std::size_t(index)) values.resize(index + 1); values[index] = value; }
188 };
189 std::map<std::string, std::string> configs;
190 std::map<int, std::string> title;
191 bool hasTitle = false;
192 while (sqlite3_step(props.get()) == SQLITE_ROW) {
193 const auto scope = column_text(props.get(), 0), id = column_text(props.get(), 1),
194 key = column_text(props.get(), 2), value = column_text(props.get(), 3);
195 if (scope == "model" && key == "title_count") hasTitle = true;
196 else if (scope == "title") title[std::stoi(id)] = value;
197 else if (scope == "component") { configs[id] = value; }
198 else if (scope == "node") comment(ctx.nodes.comments, ctx.node_names.find(id), value);
199 else if (scope == "link") comment(ctx.links.comments, ctx.link_names.find(id), value);
200 else if (scope == "table") { int i = ctx.find_table_any(id); if (i >= 0) ctx.tables[i].comment = value; }
201 else if (scope == "gage") {
202 int i = ctx.gage_names.find(id); if (i < 0) continue;
203 if (key == "comment") comment(ctx.gages.comments, i, value);
204 else if (key == "scale") ctx.gages.scale_factor[i] = std::stod(value);
205 } else if (scope == "subcatch") {
206 int i = ctx.subcatch_names.find(id); if (i < 0) continue;
207 if (key == "comment") comment(ctx.subcatches.comments, i, value);
208 else if (key == "snowpack") {
209 comment(ctx.subcatches.snowpack_name, i, value);
210 ctx.subcatches.snowpack[i] = ctx.snowpack_names.find(value);
211 }
212 } else if (scope == "pollutant") {
213 int i = ctx.pollutant_names.find(id); if (i < 0) continue;
214 if (key == "comment") comment(ctx.pollutants.comments, i, value);
215 else if (key == "mwt") ctx.pollutants.mwt[i] = std::stod(value);
216 } else if (scope == "weir") {
217 int i = ctx.link_names.find(id); int r = i < 0 ? -1 : ctx.link_subtypes.weir_row(i);
218 if (r < 0) continue;
219 auto& w = ctx.link_subtypes.weirs;
220 if (key == "cd2") w.cd2[r] = std::stod(value);
221 else if (key == "end_contractions") w.end_contractions[r] = std::stod(value);
222 else if (key == "can_surcharge") w.can_surcharge[r] = std::stoi(value);
223 else if (key == "road_width") w.road_width[r] = std::stod(value);
224 else if (key == "road_surface") w.road_surface[r] = std::stoi(value);
225 else if (key == "cd_curve") w.cd_curve[r] = ctx.find_table_any(value);
226 }
227 }
228 if (hasTitle) {
229 ctx.title_notes.clear();
230 for (const auto& [index, line] : title) ctx.title_notes.push_back(line);
231 }
233}
234} // namespace openswmm::gpkg
DefaultInputPlugin — reads/writes SWMM .inp files.
RAII wrappers and utilities for SQLite operations in GeoPackage I/O.
Write a SimulationContext to a SWMM .inp file.
The central, reentrant simulation context for the new engine.
Multi-delimiter tokenizer for SWMM input files.
Default input plugin that reads/writes SWMM .inp files.
Definition DefaultInputPlugin.hpp:52
void registerContextSections(SimulationContext &ctx)
Definition DefaultInputPlugin.cpp:72
input::SectionRegistry & registry() noexcept
Borrow the owned mutable handler registry for caller-managed extensions.
Definition DefaultInputPlugin.hpp:115
int size() const noexcept
Count names in the ordered vector.
Definition NameIndex.hpp:196
int find(std::string_view name) const noexcept
Look up a borrowed name without allocating a temporary key.
Definition NameIndex.hpp:136
const std::string & name_of(int idx) const
Borrow a registered name by its ordered index.
Definition NameIndex.hpp:186
Runtime-error carrier for owned GeoPackage/SQLite diagnostic text.
Definition GpkgUtils.hpp:45
void dispatch(std::string_view tag, SimulationContext &ctx, const std::vector< std::string > &lines) const
Invoke a custom callback first, otherwise a built-in, otherwise no-op.
Definition SectionRegistry.cpp:71
bool has(std::string_view tag) const
Test whether either normalized handler map contains a tag.
Definition SectionRegistry.cpp:58
static std::vector< std::string > tokenize(std::string_view line)
Split a line into owned tokens using whitespace and positional comma rules.
Definition Tokenizer.cpp:87
static std::string to_upper(std::string_view s)
Copy bytes and uppercase the copy using the current C locale.
Definition Tokenizer.cpp:221
Definition ExternalContentReader.cpp:50
void read_model_supplement(sqlite3 *db, SimulationContext &ctx, const std::string &simulation)
Definition ModelSupplement.hpp:151
void exec(sqlite3 *db, const std::string &sql)
Execute SQL text and discard all returned rows.
Definition GpkgUtils.hpp:198
bool has_model_supplement(sqlite3 *db, const std::string &simulation)
Definition ModelSupplement.hpp:143
StmtPtr prepare(sqlite3 *db, const std::string &sql)
Prepare the first SQL statement without stepping it.
Definition GpkgUtils.hpp:171
void checked_model_step(sqlite3 *db, sqlite3_stmt *statement)
Definition ModelSupplement.hpp:15
std::string column_text(sqlite3_stmt *stmt, int col)
Copy current-row text using NUL-terminated SQLite conversion.
Definition GpkgUtils.hpp:292
int column_int(sqlite3_stmt *stmt, int col)
Read current-row SQLite numeric conversion as int.
Definition GpkgUtils.hpp:318
void write_model_supplement(sqlite3 *db, const SimulationContext &ctx, const std::string &simulation)
Definition ModelSupplement.hpp:28
void bind_text(sqlite3_stmt *stmt, int col, const std::string &val)
Bind a copied SQL text parameter, discarding SQLite status.
Definition GpkgUtils.hpp:220
std::string modelText(const SimulationContext &ctx, const std::string &anchor, bool internal_units)
Definition InpWriter.cpp:5012
Definition CatchmentHandler.cpp:70
std::map< std::string, std::string > configurations(const SimulationContext &ctx)
Definition NativeModelBundle.hpp:12
void restoreConfigurations(SimulationContext &ctx, const std::map< std::string, std::string > &content)
Definition NativeModelBundle.hpp:32
std::vector< std::string > comments
Object comment from the INP file (';'-prefixed lines immediately above this gage's data row),...
Definition GageData.hpp:314
std::vector< double > scale_factor
Rainfall scaling factor (1.0 = no scaling).
Definition GageData.hpp:218
std::vector< std::string > comments
Object comment from the INP file (lines with a single ';' prefix immediately above this object's data...
Definition NodeData.hpp:592
std::vector< std::string > comments
Object comment from the INP file (';'-prefixed lines immediately above this pollutant's data row),...
Definition PollutantData.hpp:152
std::vector< double > mwt
Molecular weight (g/mol). Used for unit conversions.
Definition PollutantData.hpp:87
twoD::SolverOptions2D * options
Definition SimulationContext.hpp:1008
LinkSubtypes link_subtypes
Relational per-subtype link side-tables (Phase 6) — the link analogue of node_subtypes....
Definition SimulationContext.hpp:588
NameIndex subcatch_names
Subcatchment name → subcatchment index.
Definition SimulationContext.hpp:736
NameIndex node_names
Node name → node index.
Definition SimulationContext.hpp:724
NodeData nodes
All node state and properties.
Definition SimulationContext.hpp:563
TableData tables
All time series and rating curves.
Definition SimulationContext.hpp:714
NameIndex snowpack_names
Definition SimulationContext.hpp:864
GageData gages
All rain gage state and properties.
Definition SimulationContext.hpp:600
NameIndex gage_names
Rain gage name → gage index.
Definition SimulationContext.hpp:742
std::string inp_file_path
Path to the input .inp file (empty if not opened from a file).
Definition SimulationContext.hpp:2039
int n_tables() const noexcept
Number of tables (time series + curves).
Definition SimulationContext.hpp:2377
std::vector< std::string > title_notes
Project title and notes (from [TITLE] section).
Definition SimulationContext.hpp:425
std::vector< std::string > warnings
Accumulated warning messages written to report file.
Definition SimulationContext.hpp:1107
LinkData links
All link state and properties.
Definition SimulationContext.hpp:580
int n_pollutants() const noexcept
Number of pollutants.
Definition SimulationContext.hpp:2369
std::vector< std::pair< std::string, std::vector< std::string > > > embedded_component_sections
Embedded component sections found in the legacy .inp ([REACTION_*] today; other component families as...
Definition SimulationContext.hpp:964
int n_subcatches() const noexcept
Number of subcatchments.
Definition SimulationContext.hpp:2363
int n_links() const noexcept
Number of links.
Definition SimulationContext.hpp:2360
NameIndex link_names
Link name → link index.
Definition SimulationContext.hpp:730
struct openswmm::SimulationContext::TwoDModelIO twod_io
int find_table_any(std::string_view name) const noexcept
Find a named table with time-series precedence.
Definition SimulationContext.hpp:2419
NameIndex pollutant_names
Pollutant name → pollutant index.
Definition SimulationContext.hpp:748
SubcatchData subcatches
All subcatchment state and properties.
Definition SimulationContext.hpp:594
SimulationOptions options
Parsed simulation options (from [OPTIONS] section).
Definition SimulationContext.hpp:452
PollutantData pollutants
Pollutant definitions and per-object quality state.
Definition SimulationContext.hpp:606
double report_start
Report start date/time.
Definition SimulationOptions.hpp:192
std::vector< std::string > snowpack_name
Snow pack name string for deferred resolution.
Definition SubcatchData.hpp:91
std::vector< int > snowpack
Snowpack index for this subcatchment (-1 = none).
Definition SubcatchData.hpp:578
std::vector< std::string > comments
Object comment from the INP file (';'-prefixed lines immediately above this subcatchment's data row),...
Definition SubcatchData.hpp:466
std::vector< double > cd2
End-section discharge coeff (legacy cDisch2)
Definition LinkSubtypes.hpp:470
Central, reentrant simulation context.
Definition SimulationContext.hpp:390