OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
ForcingData.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
40
41#ifndef OPENSWMM_FORCING_DATA_HPP
42#define OPENSWMM_FORCING_DATA_HPP
43
44#include <cstdint>
45#include <vector>
46
47#include "HeatOverrideData.hpp" // HeatElement — PE4
48
49namespace openswmm {
50
51// ============================================================================
52// Forcing enums (C++ scoped — mirrors C API enums in openswmm_forcing.h)
53// ============================================================================
54
55enum class ForcingMode : int8_t {
56 NONE = 0,
58 ADD = 2
59};
60
61enum class ForcingPersist : int8_t {
62 RESET = 0,
64};
65
66// ============================================================================
67// ForcingData — SoA storage for all forcing channels
68// ============================================================================
69
71
72 // ------ Node forcing (sized to n_nodes) ---------------------------------
73
74 std::vector<ForcingMode> node_lat_inflow_mode;
75 std::vector<double> node_lat_inflow_value;
76 std::vector<ForcingPersist> node_lat_inflow_persist;
77
78 std::vector<ForcingMode> node_head_boundary_mode;
79 std::vector<double> node_head_boundary_value;
80 std::vector<ForcingPersist> node_head_boundary_persist;
81
82 // Node quality: flat 2D [node_idx * n_pollutants + pollutant_idx]
83 std::vector<ForcingMode> node_quality_mode;
84 std::vector<double> node_quality_value;
85 std::vector<ForcingPersist> node_quality_persist;
86
87 // U2 (2026-09-07) — the reserved species, the twins of node_quality:
88 // OVERRIDE sets the PUBLISHED state (degC / hours); ADD injects a rate
89 // into the loader accumulator every engine already reads
90 // (heat_state.node_temp_vol_in in degC·ft3/s,
91 // water_age_state.node_age_vol_in in s·ft3/s). One entry per node.
92 std::vector<ForcingMode> node_temperature_mode;
93 std::vector<double> node_temperature_value;
94 std::vector<ForcingPersist> node_temperature_persist;
95
96 std::vector<ForcingMode> node_age_mode;
97 std::vector<double> node_age_value;
98 std::vector<ForcingPersist> node_age_persist;
99
100 // ------ Link forcing (sized to n_links) ---------------------------------
101
102 std::vector<ForcingMode> link_flow_mode;
103 std::vector<double> link_flow_value;
104 std::vector<ForcingPersist> link_flow_persist;
105
106 std::vector<ForcingMode> link_setting_mode;
107 std::vector<double> link_setting_value;
108 std::vector<ForcingPersist> link_setting_persist;
109
110 std::vector<ForcingMode> link_quality_mode;
111 std::vector<double> link_quality_value;
112 std::vector<ForcingPersist> link_quality_persist;
113
114 // ------ Subcatchment forcing (sized to n_subcatches) --------------------
115
116 std::vector<ForcingMode> subcatch_rainfall_mode;
117 std::vector<double> subcatch_rainfall_value;
118 std::vector<ForcingPersist> subcatch_rainfall_persist;
119
120 std::vector<ForcingMode> subcatch_evap_mode;
121 std::vector<double> subcatch_evap_value;
122 std::vector<ForcingPersist> subcatch_evap_persist;
123
124 std::vector<ForcingMode> subcatch_snowfall_mode;
125 std::vector<double> subcatch_snowfall_value;
126 std::vector<ForcingPersist> subcatch_snowfall_persist;
127
128 // ------ Gage forcing (sized to n_gages) ---------------------------------
129
130 std::vector<ForcingMode> gage_rainfall_mode;
131 std::vector<double> gage_rainfall_value;
132 std::vector<ForcingPersist> gage_rainfall_persist;
133
134 // ------ Climate forcing (scalar — system-wide) ---------------------------
135
139
141 double climate_wind_value = 0.0;
143
145 double climate_evap_value = 0.0;
147
148 // ------ PE4: per-ELEMENT climate (API only; no deck syntax) -------------
149 //
150 // Air temperature, humidity, wind and incoming shortwave are GLOBAL in
151 // every deck and per-element ONLY through this API. The caller is a
152 // coupled driver — an MCP session, a calibration loop, or a HydroCouple
153 // composition where an atmospheric or riparian-shade model owns the
154 // near-surface field — asserting the heterogeneity deliberately, per
155 // step, and answerable for it. That is how the reference works too:
156 // RHEComponent and CSHComponent receive per-element meteorology through
157 // EXCHANGE ITEMS, never through their input files.
158 //
159 // ⚠ These are resolved AT THE FLUX CALL (SurfaceExchange/RadiativeExchange),
160 // never written into ClimateState. That is the whole safety property:
161 // ClimateState is shared with hydrology, snowmelt and evaporation, and a
162 // per-link push must not reach them. A "simplification" that assigned
163 // these into climate_state would silently give the snowpack above a
164 // conduit that conduit's air temperature.
165 //
166 // Scope is LINK and NODE only. Neither has a competing consumer for air
167 // temperature — snowmelt runs on subcatchments, evaporation on
168 // subcatchment and storage surfaces — so no subsystem divergence is
169 // possible. SUBCATCH is refused at the API boundary, naming that reason.
170 //
171 // Sized lazily on first use (the D-PE2 pattern): a model that never
172 // calls these allocates nothing and pays one `.empty()` check per flux.
174 std::vector<ForcingMode> mode;
175 std::vector<double> value;
176 std::vector<ForcingPersist> persist;
177
178 void ensure(std::size_t n) {
179 if (mode.size() == n) return;
180 mode.assign(n, ForcingMode::NONE);
181 value.assign(n, 0.0);
183 }
184
185 double apply(int i, double base) const noexcept {
186 if (i < 0) return base;
187 const auto u = static_cast<std::size_t>(i);
188 if (u >= mode.size() || mode[u] == ForcingMode::NONE) return base;
189 return (mode[u] == ForcingMode::OVERRIDE) ? value[u]
190 : base + value[u];
191 }
193 for (std::size_t i = 0; i < mode.size(); ++i)
196 }
197 void clear() { *this = ElemClimateChannel{}; }
198 };
199
206
208 static int elemSlot(HeatElemKind k) noexcept {
209 if (k == HeatElemKind::LINK) return 0;
210 if (k == HeatElemKind::NODE) return 1;
211 return -1;
212 }
213
214 double elementAirTempF(const HeatElement& e, double base) const noexcept {
215 const int s = elemSlot(e.kind);
216 return (s < 0) ? base : elem_air_temp[s].apply(e.index, base);
217 }
218 double elementHumidity(const HeatElement& e, double base) const noexcept {
219 const int s = elemSlot(e.kind);
220 return (s < 0) ? base : elem_humidity[s].apply(e.index, base);
221 }
222 double elementWindMph(const HeatElement& e, double base) const noexcept {
223 const int s = elemSlot(e.kind);
224 return (s < 0) ? base : elem_wind[s].apply(e.index, base);
225 }
226 double elementShortwave(const HeatElement& e, double base) const noexcept {
227 const int s = elemSlot(e.kind);
228 return (s < 0) ? base : elem_shortwave[s].apply(e.index, base);
229 }
230
231 // ------ Counts (for iteration) ------------------------------------------
232
233 int n_nodes_ = 0;
234 int n_links_ = 0;
236 int n_gages_ = 0;
238
239 // ========================================================================
240 // Methods
241 // ========================================================================
242
246 void resize(int n_nodes, int n_links, int n_subcatches,
247 int n_gages, int n_pollutants) {
248 n_nodes_ = n_nodes;
249 n_links_ = n_links;
250 n_subcatches_ = n_subcatches;
251 n_gages_ = n_gages;
252 n_pollutants_ = n_pollutants;
253
254 auto un = static_cast<std::size_t>(n_nodes);
255 auto ul = static_cast<std::size_t>(n_links);
256 auto us = static_cast<std::size_t>(n_subcatches);
257 auto ug = static_cast<std::size_t>(n_gages);
258 auto unp = static_cast<std::size_t>(n_nodes) *
259 static_cast<std::size_t>(n_pollutants);
260
262 node_lat_inflow_value.assign(un, 0.0);
264
266 node_head_boundary_value.assign(un, 0.0);
268
270 node_quality_value.assign(unp, 0.0);
272
274 node_temperature_value.assign(un, 0.0);
276
278 node_age_value.assign(un, 0.0);
280
282 link_flow_value.assign(ul, 0.0);
284
286 link_setting_value.assign(ul, 0.0);
288
289 auto ulp = static_cast<std::size_t>(n_links) *
290 static_cast<std::size_t>(n_pollutants);
292 link_quality_value.assign(ulp, 0.0);
294
296 subcatch_rainfall_value.assign(us, 0.0);
298
300 subcatch_evap_value.assign(us, 0.0);
302
304 subcatch_snowfall_value.assign(us, 0.0);
306
308 gage_rainfall_value.assign(ug, 0.0);
310 }
311
315 void clear_all() {
316 auto set_none = [](auto& mode_vec) {
317 for (auto& m : mode_vec) m = ForcingMode::NONE;
318 };
319 set_none(node_lat_inflow_mode);
320 set_none(node_head_boundary_mode);
321 set_none(node_quality_mode);
322 set_none(node_temperature_mode);
323 set_none(node_age_mode);
324 set_none(link_flow_mode);
325 set_none(link_setting_mode);
326 set_none(link_quality_mode);
327 set_none(subcatch_rainfall_mode);
328 set_none(subcatch_evap_mode);
329 set_none(subcatch_snowfall_mode);
330 set_none(gage_rainfall_mode);
334 }
335
344 auto clear_resets = [](auto& mode_vec, const auto& persist_vec) {
345 for (std::size_t i = 0; i < mode_vec.size(); ++i) {
346 if (persist_vec[i] == ForcingPersist::RESET)
347 mode_vec[i] = ForcingMode::NONE;
348 }
349 };
354 clear_resets(node_age_mode, node_age_persist);
355 clear_resets(link_flow_mode, link_flow_persist);
368 // PE4: the per-element channels join the SAME sweep, so RESET
369 // semantics cannot drift between the global and element spellings.
370 // RESET is the documented default for a coupled driver: under
371 // PERSIST, a driver that pushes on some steps and not others
372 // silently reuses a stale field that looks like data and is hours
373 // old. Under RESET the value falls back to the global broadcast the
374 // moment the driver stops feeding it.
375 for (int k = 0; k < 2; ++k) {
376 elem_air_temp[k].resetPerStep();
377 elem_humidity[k].resetPerStep();
378 elem_wind[k].resetPerStep();
379 elem_shortwave[k].resetPerStep();
380 }
381 }
382
389 double effective_temperature(double broadcast) const noexcept {
390 switch (climate_temperature_mode) {
392 case ForcingMode::ADD: return broadcast + climate_temperature_value;
393 default: return broadcast;
394 }
395 }
396
403 double effective_wind(double broadcast) const noexcept {
404 switch (climate_wind_mode) {
406 case ForcingMode::ADD: return broadcast + climate_wind_value;
407 default: return broadcast;
408 }
409 }
410
422 double effective_rainfall(std::size_t ui, double gage_rainfall) const noexcept {
423 if (ui >= subcatch_rainfall_mode.size()) return gage_rainfall;
424 switch (subcatch_rainfall_mode[ui]) {
426 case ForcingMode::ADD: return gage_rainfall + subcatch_rainfall_value[ui];
427 default: return gage_rainfall;
428 }
429 }
430
437 double effective_climate_evap(double broadcast) const noexcept {
438 switch (climate_evap_mode) {
440 case ForcingMode::ADD: return broadcast + climate_evap_value;
441 default: return broadcast;
442 }
443 }
444
455 double effective_snowfall(std::size_t ui, double gage_snowfall) const noexcept {
456 if (ui >= subcatch_snowfall_mode.size()) return gage_snowfall;
457 switch (subcatch_snowfall_mode[ui]) {
459 case ForcingMode::ADD: return gage_snowfall + subcatch_snowfall_value[ui];
460 default: return gage_snowfall;
461 }
462 }
463
477 double effective_evap_rate(std::size_t ui, double broadcast_rate) const noexcept {
478 // Total over unallocated forcing: callers (Runoff, Groundwater, LID)
479 // run against hand-built / partially-initialized contexts in tests
480 // and via the builder API before the forcing arrays are sized.
481 if (ui >= subcatch_evap_mode.size()) return broadcast_rate;
482 switch (subcatch_evap_mode[ui]) {
484 case ForcingMode::ADD: return broadcast_rate + subcatch_evap_value[ui];
485 default: return broadcast_rate;
486 }
487 }
488};
489
490} // namespace openswmm
491
492#endif // OPENSWMM_FORCING_DATA_HPP
Plan PE — per-element radiative and bed attributes.
@ NONE
Definition SimulationContext.hpp:180
Definition NodeCoupling.cpp:16
ForcingPersist
Definition ForcingData.hpp:61
@ PERSIST
Keep until explicitly cleared.
Definition ForcingData.hpp:63
@ RESET
Auto-clear after each timestep.
Definition ForcingData.hpp:62
HeatElemKind
What kind of element a flux is being evaluated for (D-PE1).
Definition HeatOverrideData.hpp:75
@ NODE
Definition HeatOverrideData.hpp:76
@ LINK
Definition HeatOverrideData.hpp:77
ForcingMode
Definition ForcingData.hpp:55
@ OVERRIDE
Replace computed value with user value.
Definition ForcingData.hpp:57
@ ADD
Add user value to computed value.
Definition ForcingData.hpp:58
@ NONE
Use model-computed value (no forcing)
Definition ForcingData.hpp:56
Definition ForcingData.hpp:173
void resetPerStep()
Definition ForcingData.hpp:192
std::vector< ForcingPersist > persist
Definition ForcingData.hpp:176
double apply(int i, double base) const noexcept
Apply this channel at i to base; base when unset.
Definition ForcingData.hpp:185
std::vector< ForcingMode > mode
Definition ForcingData.hpp:174
void clear()
Definition ForcingData.hpp:197
std::vector< double > value
Definition ForcingData.hpp:175
void ensure(std::size_t n)
Definition ForcingData.hpp:178
Definition ForcingData.hpp:70
ElemClimateChannel elem_air_temp[2]
deg F
Definition ForcingData.hpp:202
void clear_reset_entries()
Clear only RESET-persistence entries (called at end of each step).
Definition ForcingData.hpp:343
std::vector< double > subcatch_rainfall_value
user units (in/hr or mm/hr)
Definition ForcingData.hpp:117
std::vector< ForcingMode > node_lat_inflow_mode
Definition ForcingData.hpp:74
double effective_wind(double broadcast) const noexcept
Resolve the effective wind speed (mph internal).
Definition ForcingData.hpp:403
std::vector< double > node_temperature_value
Definition ForcingData.hpp:93
ForcingMode climate_evap_mode
Definition ForcingData.hpp:144
void resize(int n_nodes, int n_links, int n_subcatches, int n_gages, int n_pollutants)
Allocate all arrays and initialise to NONE / 0 / RESET.
Definition ForcingData.hpp:246
double climate_evap_value
ft/sec (internal; converted from in/day or mm/day at the C API boundary)
Definition ForcingData.hpp:145
std::vector< double > link_setting_value
0.0–1.0 for pump/orifice/weir
Definition ForcingData.hpp:107
std::vector< ForcingMode > gage_rainfall_mode
Definition ForcingData.hpp:130
int n_pollutants_
Definition ForcingData.hpp:237
std::vector< ForcingPersist > subcatch_evap_persist
Definition ForcingData.hpp:122
std::vector< ForcingMode > link_quality_mode
flattened link × pollutant
Definition ForcingData.hpp:110
void clear_all()
Reset ALL forcing modes to NONE (called on simulation restart).
Definition ForcingData.hpp:315
std::vector< ForcingMode > subcatch_snowfall_mode
Definition ForcingData.hpp:124
int n_links_
Definition ForcingData.hpp:234
std::vector< double > node_age_value
Definition ForcingData.hpp:97
ForcingMode climate_wind_mode
Definition ForcingData.hpp:140
std::vector< double > node_lat_inflow_value
Definition ForcingData.hpp:75
std::vector< ForcingMode > subcatch_rainfall_mode
Definition ForcingData.hpp:116
std::vector< double > link_quality_value
OVERRIDE: concentration; ADD: mass rate (mass/sec)
Definition ForcingData.hpp:111
std::vector< double > subcatch_evap_value
prescribed PET rate, ft/sec (internal units; converted from in/day or mm/day at the C API boundary)
Definition ForcingData.hpp:121
int n_subcatches_
Definition ForcingData.hpp:235
double climate_temperature_value
deg F (internal; converted from deg C at the C API boundary for SI)
Definition ForcingData.hpp:137
double effective_temperature(double broadcast) const noexcept
Resolve the effective air temperature (deg F internal).
Definition ForcingData.hpp:389
ElemClimateChannel elem_wind[2]
mph
Definition ForcingData.hpp:204
static int elemSlot(HeatElemKind k) noexcept
-1 for a kind that carries no per-element climate (SUBCATCH, LID).
Definition ForcingData.hpp:208
std::vector< ForcingMode > link_setting_mode
Definition ForcingData.hpp:106
double effective_snowfall(std::size_t ui, double gage_snowfall) const noexcept
Resolve the effective snowfall for a subcatchment (ft/sec).
Definition ForcingData.hpp:455
std::vector< ForcingMode > node_quality_mode
Definition ForcingData.hpp:83
double elementShortwave(const HeatElement &e, double base) const noexcept
Definition ForcingData.hpp:226
std::vector< ForcingPersist > subcatch_snowfall_persist
Definition ForcingData.hpp:126
ElemClimateChannel elem_humidity[2]
%
Definition ForcingData.hpp:203
double climate_wind_value
mph (internal; converted from km/hr at the C API boundary for SI)
Definition ForcingData.hpp:141
std::vector< double > subcatch_snowfall_value
ft/sec (internal; converted from in/hr or mm/hr at the C API boundary)
Definition ForcingData.hpp:125
std::vector< double > gage_rainfall_value
user units (in/hr or mm/hr)
Definition ForcingData.hpp:131
std::vector< ForcingPersist > gage_rainfall_persist
Definition ForcingData.hpp:132
double elementWindMph(const HeatElement &e, double base) const noexcept
Definition ForcingData.hpp:222
double effective_rainfall(std::size_t ui, double gage_rainfall) const noexcept
Resolve the effective rainfall for a subcatchment.
Definition ForcingData.hpp:422
ElemClimateChannel elem_shortwave[2]
W/m2.
Definition ForcingData.hpp:205
std::vector< ForcingPersist > link_quality_persist
Definition ForcingData.hpp:112
std::vector< ForcingPersist > link_flow_persist
Definition ForcingData.hpp:104
ForcingPersist climate_wind_persist
Definition ForcingData.hpp:142
ForcingMode climate_temperature_mode
Definition ForcingData.hpp:136
std::vector< double > link_flow_value
Definition ForcingData.hpp:103
std::vector< ForcingPersist > node_lat_inflow_persist
Definition ForcingData.hpp:76
double effective_climate_evap(double broadcast) const noexcept
Resolve the effective system-wide evaporation rate (ft/sec).
Definition ForcingData.hpp:437
std::vector< ForcingMode > link_flow_mode
Definition ForcingData.hpp:102
ForcingPersist climate_temperature_persist
Definition ForcingData.hpp:138
std::vector< ForcingMode > node_head_boundary_mode
Definition ForcingData.hpp:78
std::vector< ForcingPersist > node_age_persist
Definition ForcingData.hpp:98
std::vector< ForcingPersist > node_quality_persist
Definition ForcingData.hpp:85
std::vector< ForcingMode > node_temperature_mode
Definition ForcingData.hpp:92
std::vector< ForcingPersist > subcatch_rainfall_persist
Definition ForcingData.hpp:118
double effective_evap_rate(std::size_t ui, double broadcast_rate) const noexcept
Resolve the effective evaporation rate for a subcatchment.
Definition ForcingData.hpp:477
std::vector< ForcingPersist > node_head_boundary_persist
Definition ForcingData.hpp:80
ForcingPersist climate_evap_persist
Definition ForcingData.hpp:146
std::vector< ForcingPersist > link_setting_persist
Definition ForcingData.hpp:108
double elementAirTempF(const HeatElement &e, double base) const noexcept
Definition ForcingData.hpp:214
std::vector< double > node_head_boundary_value
Definition ForcingData.hpp:79
int n_gages_
Definition ForcingData.hpp:236
std::vector< ForcingMode > subcatch_evap_mode
Definition ForcingData.hpp:120
double elementHumidity(const HeatElement &e, double base) const noexcept
Definition ForcingData.hpp:218
std::vector< ForcingMode > node_age_mode
Definition ForcingData.hpp:96
std::vector< ForcingPersist > node_temperature_persist
Definition ForcingData.hpp:94
int n_nodes_
Definition ForcingData.hpp:233
std::vector< double > node_quality_value
mass rate (mass/sec)
Definition ForcingData.hpp:84
Which element a flux evaluator is being called for.
Definition HeatOverrideData.hpp:96