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
116 std::vector<ForcingMode> link_seepage_mode;
117 std::vector<double> link_seepage_value;
118 std::vector<ForcingPersist> link_seepage_persist;
119
120 std::vector<ForcingMode> link_quality_mode;
121 std::vector<double> link_quality_value;
122 std::vector<ForcingPersist> link_quality_persist;
123
124 // ------ Subcatchment forcing (sized to n_subcatches) --------------------
125
126 std::vector<ForcingMode> subcatch_rainfall_mode;
127 std::vector<double> subcatch_rainfall_value;
128 std::vector<ForcingPersist> subcatch_rainfall_persist;
129
130 std::vector<ForcingMode> subcatch_evap_mode;
131 std::vector<double>
133 std::vector<ForcingPersist> subcatch_evap_persist;
134
135 std::vector<ForcingMode> subcatch_snowfall_mode;
136 std::vector<double>
138 std::vector<ForcingPersist> subcatch_snowfall_persist;
139
140 // ------ Gage forcing (sized to n_gages) ---------------------------------
141
142 std::vector<ForcingMode> gage_rainfall_mode;
143 std::vector<double> gage_rainfall_value;
144 std::vector<ForcingPersist> gage_rainfall_persist;
145
146 // ------ Climate forcing (scalar — system-wide) ---------------------------
147
150 0.0;
152
155 0.0;
157
160 0.0;
162
163 // ------ PE4: per-ELEMENT climate (API only; no deck syntax) -------------
164 //
165 // Air temperature, humidity, wind and incoming shortwave are GLOBAL in
166 // every deck and per-element ONLY through this API. The caller is a
167 // coupled driver — an MCP session, a calibration loop, or a HydroCouple
168 // composition where an atmospheric or riparian-shade model owns the
169 // near-surface field — asserting the heterogeneity deliberately, per
170 // step, and answerable for it. That is how the reference works too:
171 // RHEComponent and CSHComponent receive per-element meteorology through
172 // EXCHANGE ITEMS, never through their input files.
173 //
174 // ⚠ These are resolved AT THE FLUX CALL (SurfaceExchange/RadiativeExchange),
175 // never written into ClimateState. That is the whole safety property:
176 // ClimateState is shared with hydrology, snowmelt and evaporation, and a
177 // per-link push must not reach them. A "simplification" that assigned
178 // these into climate_state would silently give the snowpack above a
179 // conduit that conduit's air temperature.
180 //
181 // Scope is LINK and NODE only. Neither has a competing consumer for air
182 // temperature — snowmelt runs on subcatchments, evaporation on
183 // subcatchment and storage surfaces — so no subsystem divergence is
184 // possible. SUBCATCH is refused at the API boundary, naming that reason.
185 //
186 // Sized lazily on first use (the D-PE2 pattern): a model that never
187 // calls these allocates nothing and pays one `.empty()` check per flux.
189 std::vector<ForcingMode> mode;
190 std::vector<double> value;
191 std::vector<ForcingPersist> persist;
192
201 void ensure(std::size_t n) {
202 if (mode.size() == n)
203 return;
204 mode.assign(n, ForcingMode::NONE);
205 value.assign(n, 0.0);
207 }
208
218 double apply(int i, double base) const noexcept {
219 if (i < 0)
220 return base;
221 const auto u = static_cast<std::size_t>(i);
222 if (u >= mode.size() || mode[u] == ForcingMode::NONE)
223 return base;
224 return (mode[u] == ForcingMode::OVERRIDE) ? value[u] : base + value[u];
225 }
226
232 for (std::size_t i = 0; i < mode.size(); ++i)
235 }
236
241 void clear() { *this = ElemClimateChannel{}; }
242 };
243
250
257 static int elemSlot(HeatElemKind k) noexcept {
258 if (k == HeatElemKind::LINK)
259 return 0;
260 if (k == HeatElemKind::NODE)
261 return 1;
262 return -1;
263 }
264
276 double elementAirTempF(const HeatElement& e, double base) const noexcept {
277 const int s = elemSlot(e.kind);
278 return (s < 0) ? base : elem_air_temp[s].apply(e.index, base);
279 }
280
291 double elementHumidity(const HeatElement& e, double base) const noexcept {
292 const int s = elemSlot(e.kind);
293 return (s < 0) ? base : elem_humidity[s].apply(e.index, base);
294 }
295
306 double elementWindMph(const HeatElement& e, double base) const noexcept {
307 const int s = elemSlot(e.kind);
308 return (s < 0) ? base : elem_wind[s].apply(e.index, base);
309 }
310
321 double elementShortwave(const HeatElement& e, double base) const noexcept {
322 const int s = elemSlot(e.kind);
323 return (s < 0) ? base : elem_shortwave[s].apply(e.index, base);
324 }
325
326 // ------ Counts (for iteration) ------------------------------------------
327
328 int n_nodes_ = 0;
329 int n_links_ = 0;
331 int n_gages_ = 0;
333
334 // ========================================================================
335 // Methods
336 // ========================================================================
337
351 void resize(int n_nodes, int n_links, int n_subcatches, int n_gages, int n_pollutants) {
352 n_nodes_ = n_nodes;
353 n_links_ = n_links;
354 n_subcatches_ = n_subcatches;
355 n_gages_ = n_gages;
356 n_pollutants_ = n_pollutants;
357
358 auto un = static_cast<std::size_t>(n_nodes);
359 auto ul = static_cast<std::size_t>(n_links);
360 auto us = static_cast<std::size_t>(n_subcatches);
361 auto ug = static_cast<std::size_t>(n_gages);
362 auto unp = static_cast<std::size_t>(n_nodes) * static_cast<std::size_t>(n_pollutants);
363
365 node_lat_inflow_value.assign(un, 0.0);
367
369 node_head_boundary_value.assign(un, 0.0);
371
373 node_quality_value.assign(unp, 0.0);
375
377 node_temperature_value.assign(un, 0.0);
379
381 node_age_value.assign(un, 0.0);
383
385 link_flow_value.assign(ul, 0.0);
387
389 link_setting_value.assign(ul, 0.0);
391
392 link_seepage_mode.assign(ul, ForcingMode::NONE); // G-X4
393 link_seepage_value.assign(ul, 0.0);
395
396 auto ulp = static_cast<std::size_t>(n_links) * static_cast<std::size_t>(n_pollutants);
398 link_quality_value.assign(ulp, 0.0);
400
402 subcatch_rainfall_value.assign(us, 0.0);
404
406 subcatch_evap_value.assign(us, 0.0);
408
410 subcatch_snowfall_value.assign(us, 0.0);
412
414 gage_rainfall_value.assign(ug, 0.0);
416 }
417
424 void clear_all() {
425 auto set_none = [](auto& mode_vec) {
426 for (auto& m : mode_vec)
428 };
429 set_none(node_lat_inflow_mode);
430 set_none(node_head_boundary_mode);
431 set_none(node_quality_mode);
432 set_none(node_temperature_mode);
433 set_none(node_age_mode);
434 set_none(link_flow_mode);
435 set_none(link_setting_mode);
436 set_none(link_seepage_mode); // G-X4
437 set_none(link_quality_mode);
438 set_none(subcatch_rainfall_mode);
439 set_none(subcatch_evap_mode);
440 set_none(subcatch_snowfall_mode);
441 set_none(gage_rainfall_mode);
445 }
446
455 auto clear_resets = [](auto& mode_vec, const auto& persist_vec) {
456 for (std::size_t i = 0; i < mode_vec.size(); ++i) {
457 if (persist_vec[i] == ForcingPersist::RESET)
458 mode_vec[i] = ForcingMode::NONE;
459 }
460 };
465 clear_resets(node_age_mode, node_age_persist);
466 clear_resets(link_flow_mode, link_flow_persist);
468 clear_resets(link_seepage_mode, link_seepage_persist); // G-X4
480 // PE4: the per-element channels join the SAME sweep, so RESET
481 // semantics cannot drift between the global and element spellings.
482 // RESET is the documented default for a coupled driver: under
483 // PERSIST, a driver that pushes on some steps and not others
484 // silently reuses a stale field that looks like data and is hours
485 // old. Under RESET the value falls back to the global broadcast the
486 // moment the driver stops feeding it.
487 for (int k = 0; k < 2; ++k) {
488 elem_air_temp[k].resetPerStep();
489 elem_humidity[k].resetPerStep();
490 elem_wind[k].resetPerStep();
491 elem_shortwave[k].resetPerStep();
492 }
493 }
494
501 double effective_temperature(double broadcast) const noexcept {
502 switch (climate_temperature_mode) {
505 case ForcingMode::ADD:
506 return broadcast + climate_temperature_value;
507 default:
508 return broadcast;
509 }
510 }
511
518 double effective_wind(double broadcast) const noexcept {
519 switch (climate_wind_mode) {
521 return climate_wind_value;
522 case ForcingMode::ADD:
523 return broadcast + climate_wind_value;
524 default:
525 return broadcast;
526 }
527 }
528
540 double effective_rainfall(std::size_t ui, double gage_rainfall) const noexcept {
541 if (ui >= subcatch_rainfall_mode.size())
542 return gage_rainfall;
543 switch (subcatch_rainfall_mode[ui]) {
545 return subcatch_rainfall_value[ui];
546 case ForcingMode::ADD:
547 return gage_rainfall + subcatch_rainfall_value[ui];
548 default:
549 return gage_rainfall;
550 }
551 }
552
559 double effective_climate_evap(double broadcast) const noexcept {
560 switch (climate_evap_mode) {
562 return climate_evap_value;
563 case ForcingMode::ADD:
564 return broadcast + climate_evap_value;
565 default:
566 return broadcast;
567 }
568 }
569
580 double effective_snowfall(std::size_t ui, double gage_snowfall) const noexcept {
581 if (ui >= subcatch_snowfall_mode.size())
582 return gage_snowfall;
583 switch (subcatch_snowfall_mode[ui]) {
585 return subcatch_snowfall_value[ui];
586 case ForcingMode::ADD:
587 return gage_snowfall + subcatch_snowfall_value[ui];
588 default:
589 return gage_snowfall;
590 }
591 }
592
606 double effective_evap_rate(std::size_t ui, double broadcast_rate) const noexcept {
607 // Total over unallocated forcing: callers (Runoff, Groundwater, LID)
608 // run against hand-built / partially-initialized contexts in tests
609 // and via the builder API before the forcing arrays are sized.
610 if (ui >= subcatch_evap_mode.size())
611 return broadcast_rate;
612 switch (subcatch_evap_mode[ui]) {
614 return subcatch_evap_value[ui];
615 case ForcingMode::ADD:
616 return broadcast_rate + subcatch_evap_value[ui];
617 default:
618 return broadcast_rate;
619 }
620 }
621};
622
623} // namespace openswmm
624
625#endif // OPENSWMM_FORCING_DATA_HPP
Plan PE — per-element radiative and bed attributes.
@ NONE
Definition SimulationContext.hpp:183
Definition ExchangeLaw.hpp:60
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:188
void resetPerStep()
Disable only channel entries marked RESET.
Definition ForcingData.hpp:231
std::vector< ForcingPersist > persist
Definition ForcingData.hpp:191
double apply(int i, double base) const noexcept
Resolve an element override/addition against a supplied baseline.
Definition ForcingData.hpp:218
std::vector< ForcingMode > mode
Definition ForcingData.hpp:189
void clear()
Release all owned element-climate channel arrays.
Definition ForcingData.hpp:241
std::vector< double > value
Definition ForcingData.hpp:190
void ensure(std::size_t n)
Allocate a channel shape or preserve an already matching shape.
Definition ForcingData.hpp:201
Definition ForcingData.hpp:70
std::vector< ForcingPersist > link_seepage_persist
Definition ForcingData.hpp:118
ElemClimateChannel elem_air_temp[2]
deg F
Definition ForcingData.hpp:246
void clear_reset_entries()
Clear only RESET-persistence entries (called at end of each step).
Definition ForcingData.hpp:454
std::vector< double > subcatch_rainfall_value
user units (in/hr or mm/hr)
Definition ForcingData.hpp:127
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:518
std::vector< double > node_temperature_value
Definition ForcingData.hpp:93
ForcingMode climate_evap_mode
Definition ForcingData.hpp:158
void resize(int n_nodes, int n_links, int n_subcatches, int n_gages, int n_pollutants)
Replace object-channel arrays with NONE/zero/RESET defaults.
Definition ForcingData.hpp:351
double climate_evap_value
ft/sec (internal; converted from in/day or mm/day at the C API boundary)
Definition ForcingData.hpp:159
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:142
int n_pollutants_
Definition ForcingData.hpp:332
std::vector< ForcingMode > link_seepage_mode
Definition ForcingData.hpp:116
std::vector< ForcingPersist > subcatch_evap_persist
Definition ForcingData.hpp:133
std::vector< ForcingMode > link_quality_mode
flattened link × pollutant
Definition ForcingData.hpp:120
void clear_all()
Disable object and global-climate forcing modes.
Definition ForcingData.hpp:424
std::vector< ForcingMode > subcatch_snowfall_mode
Definition ForcingData.hpp:135
int n_links_
Definition ForcingData.hpp:329
std::vector< double > link_seepage_value
Definition ForcingData.hpp:117
std::vector< double > node_age_value
Definition ForcingData.hpp:97
ForcingMode climate_wind_mode
Definition ForcingData.hpp:153
std::vector< double > node_lat_inflow_value
Definition ForcingData.hpp:75
std::vector< ForcingMode > subcatch_rainfall_mode
Definition ForcingData.hpp:126
std::vector< double > link_quality_value
OVERRIDE: concentration; ADD: mass rate (mass/sec)
Definition ForcingData.hpp:121
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:132
int n_subcatches_
Definition ForcingData.hpp:330
double climate_temperature_value
deg F (internal; converted from deg C at the C API boundary for SI)
Definition ForcingData.hpp:149
double effective_temperature(double broadcast) const noexcept
Resolve the effective air temperature (deg F internal).
Definition ForcingData.hpp:501
ElemClimateChannel elem_wind[2]
mph
Definition ForcingData.hpp:248
static int elemSlot(HeatElemKind k) noexcept
Map a heat element category to the per-element climate slot.
Definition ForcingData.hpp:257
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:580
std::vector< ForcingMode > node_quality_mode
Definition ForcingData.hpp:83
double elementShortwave(const HeatElement &e, double base) const noexcept
Resolve incoming-shortwave forcing for one heat element.
Definition ForcingData.hpp:321
std::vector< ForcingPersist > subcatch_snowfall_persist
Definition ForcingData.hpp:138
ElemClimateChannel elem_humidity[2]
%
Definition ForcingData.hpp:247
double climate_wind_value
mph (internal; converted from km/hr at the C API boundary for SI)
Definition ForcingData.hpp:154
std::vector< double > subcatch_snowfall_value
ft/sec (internal; converted from in/hr or mm/hr at the C API boundary)
Definition ForcingData.hpp:137
std::vector< double > gage_rainfall_value
user units (in/hr or mm/hr)
Definition ForcingData.hpp:143
std::vector< ForcingPersist > gage_rainfall_persist
Definition ForcingData.hpp:144
double elementWindMph(const HeatElement &e, double base) const noexcept
Resolve wind-speed forcing for one heat element.
Definition ForcingData.hpp:306
double effective_rainfall(std::size_t ui, double gage_rainfall) const noexcept
Resolve the effective rainfall for a subcatchment.
Definition ForcingData.hpp:540
ElemClimateChannel elem_shortwave[2]
W/m2.
Definition ForcingData.hpp:249
std::vector< ForcingPersist > link_quality_persist
Definition ForcingData.hpp:122
std::vector< ForcingPersist > link_flow_persist
Definition ForcingData.hpp:104
ForcingPersist climate_wind_persist
Definition ForcingData.hpp:156
ForcingMode climate_temperature_mode
Definition ForcingData.hpp:148
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:559
std::vector< ForcingMode > link_flow_mode
Definition ForcingData.hpp:102
ForcingPersist climate_temperature_persist
Definition ForcingData.hpp:151
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:128
double effective_evap_rate(std::size_t ui, double broadcast_rate) const noexcept
Resolve the effective evaporation rate for a subcatchment.
Definition ForcingData.hpp:606
std::vector< ForcingPersist > node_head_boundary_persist
Definition ForcingData.hpp:80
ForcingPersist climate_evap_persist
Definition ForcingData.hpp:161
std::vector< ForcingPersist > link_setting_persist
Definition ForcingData.hpp:108
double elementAirTempF(const HeatElement &e, double base) const noexcept
Resolve air-temperature forcing for one heat element.
Definition ForcingData.hpp:276
std::vector< double > node_head_boundary_value
Definition ForcingData.hpp:79
int n_gages_
Definition ForcingData.hpp:331
std::vector< ForcingMode > subcatch_evap_mode
Definition ForcingData.hpp:130
double elementHumidity(const HeatElement &e, double base) const noexcept
Resolve humidity forcing for one heat element.
Definition ForcingData.hpp:291
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:328
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:95