![]() |
OpenSWMM Engine
6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
|
Classes | |
| struct | BedCoupling |
| One element's bed coupling, fully reduced to SI conductances. More... | |
| struct | PairStep |
| Simultaneous temperature increments for the coupled pair, °C. More... | |
| struct | SolarPosition |
| Sun geometry at one instant. The SPA swap point's return type. More... | |
| struct | SolutePairStep |
| Simultaneous concentration increments for the coupled pair. More... | |
Functions | |
| PairStep | relaxPair (const BedCoupling &g, double t_w, double t_b, double j0, double j1, double h, double area_m2, double dt) noexcept |
| One exact step of the coupled (water, bed) pair. | |
| SolutePairStep | exchangePair (double c_w, double c_b, double vol_w, double vol_b, double q_exch, double dt) noexcept |
| One exact step of the two-body solute exchange. | |
| bool | bedExchangeEnabled (const SimulationContext &ctx) noexcept |
True when [HEAT_FLUXES] SEDIMENT_EXCHANGE is on and heat transport is. | |
| double | groundTemperature (SimulationContext &ctx) noexcept |
| double | linkBedAreaM2 (const SimulationContext &ctx, int link) noexcept |
| double | linkFreeSurfaceFt2 (const SimulationContext &ctx, int link) noexcept |
| double | bedExchangeQ (const SedimentConfig &cfg, double bed_m2) noexcept |
| double | groundTempFor (const SimulationContext &ctx, const SedimentConfig &sc, double t_gr_global) noexcept |
| BedCoupling | bedCouplingFromContact (const SimulationContext &ctx, const SedimentConfig &sc, double bed_m2, double vol_ft3, double t_gr) noexcept |
| The material half of the coupling, geometry already folded in. | |
| BedCoupling | bedCouplingForLink (const SimulationContext &ctx, int link, double vol_ft3, double t_gr) noexcept |
Bed conductances for link j from the deck configuration and the link's current hydraulics. | |
| void | seedBedTemperature (SimulationContext &ctx) |
| Size and seed the bed temperature array, idempotently. | |
| void | applyBedSoluteExchange (SimulationContext &ctx, double *link_conc, int n_arr, int offset, int n_total, double dt) |
Step every conduit's bed/channel solute exchange for one dt. | |
| double | netFluxOut (const SimulationContext &ctx, const HeatElement &elem, double t_w) noexcept |
Net outward surface flux at t_w, summed over every enabled module [W/m²]. | |
| void | applyHeatFluxes (SimulationContext &ctx, double dt) |
| Apply one step of surface heat exchange to every exchanging node and link. | |
| std::vector< std::string > | resolveHeatOverrides (SimulationContext &ctx) |
| Materialise the dense per-element vectors from the parsed rows. | |
| const RadiativeConfig & | radiativeFor (const SimulationContext &ctx, const HeatElement &e) noexcept |
The radiative attributes in force for e. | |
| const SedimentConfig & | sedimentFor (const SimulationContext &ctx, const HeatElement &e) noexcept |
The bed attributes in force for e. | |
| double | netShortwave (double incoming_wm2, double albedo, double shade_factor) noexcept |
| Net absorbed shortwave [W/m²], INTO the water. | |
| double | backLongwave (double t_water_c, double emiss_water) noexcept |
| Back longwave emitted by the water [W/m²], OUT of the water. | |
| double | atmosphericEmissivity (double e_a_kpa, double atm_emiss_coeff, double cloud_factor) noexcept |
| double | atmosphericLongwave (double t_air_c, double humidity_pct, double atm_emiss_coeff, double lw_reflection, double sky_view, double cloud_factor=1.0) noexcept |
| Atmospheric longwave [W/m²], INTO the water. | |
| double | landCoverLongwave (double t_air_c, double emiss_landcover, double sky_view) noexcept |
| Land-cover longwave [W/m²], INTO the water. | |
| double | netRadiativeFluxOut (double t_water_c, double t_air_c, double humidity_pct, const RadiativeConfig &cfg, double jin_wm2, double cloud_factor) noexcept |
| double | radiativeFluxOut (const SimulationContext &ctx, const HeatElement &elem, double t_w) noexcept |
| SolarPosition | solarPosition (int day_of_year, double hour_local, double lat_deg, double lon_deg, double tz_hours) noexcept |
| Sun position by the Spencer (1971) / NOAA formulation. | |
| double | airMass (double zenith_deg) noexcept |
| double | pressureFromElevation (double elevation_m) noexcept |
| Station pressure [mb] from elevation [m], standard atmosphere. | |
| double | birdClearSkyGHI (const SolarPosition &pos, double pressure_mb, const SolarConfig &cfg) noexcept |
| Bird & Hulstrom clear-sky global horizontal irradiance [W/m²]. | |
| double | cloudShortwaveFactor (double cloud_fraction, double k, double n) noexcept |
Cloud attenuation of shortwave, 1 − k·C^n (Kasten–Czeplak). | |
| double | cloudLongwaveFactor (double cloud_fraction, double k_lw) noexcept |
Cloud enhancement of atmospheric emissivity, 1 + k_lw·C² (Bolz). | |
| void | updateSolarForcing (SimulationContext &ctx) noexcept |
Resolve Jin and C for this step into ctx.heat_state. | |
| double | airTempCelsius (const SimulationContext &ctx) noexcept |
| double | relaxT (double j0, double j1, double h, double area_m2, double vol_m3, double dt, double rho, double cp) noexcept |
| One element's temperature change, integrated SEMI-IMPLICITLY [°C]. | |
| double | equilibriumT (double t0_c, double j0, double j1, double h) noexcept |
| double | saturationVapourPressure (double t_c) noexcept |
Saturation vapour pressure over water at t_c degrees Celsius [kPa]. | |
| double | latentHeatOfVaporization (double t_c) noexcept |
| double | windFunction (double wind_ms, double a, double b) noexcept |
Mass-transfer wind function a + b·w, w in m/s [m/s/kPa]. | |
| double | evaporationRate (double t_water_c, double t_air_c, double humidity_pct, double wind_ms, double a, double b) noexcept |
| Evaporative mass-transfer rate [m/s]. Negative under condensation. | |
| double | latentFlux (double t_water_c, double t_air_c, double humidity_pct, double wind_ms, double a, double b, double water_density) noexcept |
| Latent heat flux [W/m²], POSITIVE out of the water. | |
| double | bowenRatio (double t_water_c, double t_air_c, double humidity_pct, double pressure_ratio) noexcept |
| double | sensibleFlux (double latent_flux, double bowen_ratio) noexcept |
| Sensible heat flux [W/m²], POSITIVE out of the water. | |
| double | surfaceFluxOut (const SimulationContext &ctx, const HeatElement &elem, double t_w) noexcept |
This module's contribution to the net outward flux at t_w [W/m²]. | |
Variables | |
| constexpr double | kStefanBoltzmann = 5.67e-8 |
Stefan–Boltzmann constant, W/m²/K⁴ (rhemodel.cpp:46). | |
| constexpr double | kKelvinOffset = 273.15 |
| Celsius → Kelvin offset. | |
| constexpr double | kSolarConstant = 1367.0 |
| Solar constant, W/m² (Bird & Hulstrom 1981; WMO 1982). | |
| constexpr double | kStdPressureMb = 1013.25 |
| Sea-level standard pressure, millibars — Bird's pressure reference. | |
| constexpr double | kBowenCoeff = 0.061 |
| Bowen's coefficient, kPa/°C (CSH §4.5). | |
| constexpr double | kProbeC = 1.0e-3 |
| constexpr double | kSqFtToSqM = 0.09290304 |
ft² → m². Exported with relaxT, which is useless without it. | |
| constexpr double | kCuFtToCuM = 0.028316846592 |
| ft³ → m³. | |
| constexpr double | kMphToMs = 0.44704 |
mph → m/s. ClimateState::wind_speed is mph. | |
|
noexcept |
Kasten–Young relative optical air mass. Returns 0 below the horizon, which is what makes every Bird transmittance collapse to a dark result rather than to a domain error.
|
noexcept |
Air temperature in °C. ClimateState carries °F (a legacy convention); every formulation in this program is Celsius. Exported at H5a so the watershed binding cannot acquire a second, drifting copy of the conversion — there is exactly one place that knows the units.
| void openswmm::transport::heat::applyBedSoluteExchange | ( | SimulationContext & | ctx, |
| double * | link_conc, | ||
| int | n_arr, | ||
| int | offset, | ||
| int | n_total, | ||
| double | dt ) |
Step every conduit's bed/channel solute exchange for one dt.
| link_conc | [link * n_arr + s] channel concentrations, updated in place. The caller owns the layout because the engines hold their channel state differently; what they share is this function and the bed array it moves mass into. |
| n_arr | Species carried by link_conc. |
| offset | Where link_conc's rows start inside the bed store. The bed carries ONE species set — pollutants then MSX — and LEGACY holds those in two arrays (links.conc, msx_link_conc), so it calls twice: (conc, np, 0) and (msx, nm, np). Splitting the bed store instead would give the two halves independent sizing paths and a seam where one resizes and the other does not. |
| n_total | Total species the bed store carries (np + nm). Sizing is by n_total, so the two LEGACY calls agree about the store's shape by construction. |
| void openswmm::transport::heat::applyHeatFluxes | ( | SimulationContext & | ctx, |
| double | dt ) |
Apply one step of surface heat exchange to every exchanging node and link.
Storage nodes (node::getSurfArea) and open conduits (top width × length × barrels) — the engine's own evaporation surfaces, so heat crosses the free surface exactly where water leaves it. Junctions, outfalls, dividers and closed conduits have no free surface and no exchange.
Modifies heat_state.node_temp and link_temp in place. No-op unless HEAT_TRANSPORT is on and at least one flux module is enabled.
|
noexcept |
Brunt atmospheric emissivity [-]. e_a_kpa is converted to Pa inside.
H6a: cloud_factor is Bolz's 1 + k_lw C² from SolarRadiation::cloudLongwaveFactor. It DEFAULTS TO 1.0 and the default is not decorative — every H3 gate calls the two-argument form, so the clear-sky value is unchanged by construction rather than by arithmetic that happens to round the same way.
|
noexcept |
Atmospheric longwave [W/m²], INTO the water.
|
noexcept |
Back longwave emitted by the water [W/m²], OUT of the water.
|
noexcept |
Bed conductances for link j from the deck configuration and the link's current hydraulics.
Contact area is the WETTED PERIMETER times length times barrels — see BedZoneData.hpp divergence 1. Returns a non-viable coupling (which relaxPair treats as a no-op) for a dry link, a non-conduit, or a link whose perimeter cannot be formed. t_gr is passed in rather than resolved here because the TIMESERIES lookup is GLOBAL scope: one lookup per STEP, not one per link. That is updateSolarForcing's pattern and the reason it exists. PE2: the link's own SedimentConfig (per-element overrides included) is resolved INSIDE, so every caller of the link form gets per-element bed attributes without threading them.
|
noexcept |
The material half of the coupling, geometry already folded in.
Split out of bedCouplingForLink for the ARD binding: a CELL has its own contact area and volume but the same material math, and a second hand-written copy of that math is how the two engines' beds would come to disagree about the same sediment. LEGACY and LARD go through bedCouplingForLink (link geometry); ARD computes its per-cell bed_m2/vol_ft3 and calls this directly.
PE2: sc is the SedimentConfig in force for the element — sedimentFor(ctx, elem), or the global for a caller with no element. The check found the handoff's draft resolving the per-element table and never passing it to ANY bed reader: both links of the per-tag gate cooled to the identical bit.
|
noexcept |
True when [HEAT_FLUXES] SEDIMENT_EXCHANGE is on and heat transport is.
|
noexcept |
Total solute exchange discharge across a bed interface of bed_m2, m³/s: D_sed·A/Y + v_hyp·A. One spelling, used by every engine's solute loop.
|
noexcept |
Bird & Hulstrom clear-sky global horizontal irradiance [W/m²].
| pos | Sun geometry from solarPosition. |
| pressure_mb | Station pressure. |
| cfg | Aerosol / water / ozone / ground albedo. |
|
noexcept |
Bowen ratio [-]. Returns 0 when the vapour-pressure deficit vanishes, which is the removable singularity in Br's definition, not an error.
|
noexcept |
Cloud enhancement of atmospheric emissivity, 1 + k_lw·C² (Bolz).
cloud_fraction <= 0, rather than evaluating 1 + k·0², so the clear-sky path is bit-identical to H3 rather than merely equal to it. Plan §2.5 makes that a verify criterion.
|
noexcept |
Cloud attenuation of shortwave, 1 − k·C^n (Kasten–Czeplak).
Clamped at 0: an over-unity k·C^n blocks completely rather than turning into negative insolation — the same guard netShortwave applies to an over-unity shade factor.
|
noexcept |
The equilibrium temperature the linearization relaxes toward, °C — T₀ − J₀/J′. Exported so a gate can assert the no-overshoot property against it directly rather than recomputing the slope by hand. Returns t0_c when J′ ≤ 0, matching relaxT's fallback.
|
noexcept |
Evaporative mass-transfer rate [m/s]. Negative under condensation.
|
noexcept |
One exact step of the two-body solute exchange.
| c_w | Channel concentration. |
| c_b | Bed concentration. |
| vol_w | Channel water volume, m³. |
| vol_b | Bed water volume, m³ (bed volume x porosity is the honest reading; the reference uses the bulk volume and so does this, see the header note on porosity). |
| q_exch | Total exchange discharge, m³/s: D_sed*A/Y + v_hyp*A. |
| dt | Step, seconds. |
vol_w*dc_w + vol_b*dc_b == 0 to relative round-off. See the header on why this is not bit-exact.
|
noexcept |
The deep-ground temperature in force this step, °C — resolves the GROUND_TEMPERATURE TIMESERIES spelling against the simulation clock. Non-const because the timeseries lookup advances the table's own cursor, exactly as table_tseries_lookup_cursor does everywhere else.
|
noexcept |
PE2: the deep-ground temperature in force for ONE element. The global GROUND_TEMPERATURE TIMESERIES wins when configured — the TIMESERIES spelling is GLOBAL-only by grammar (per-element series are PE3/D-PE7) — otherwise the element's own resolved constant. For a model with no overrides sc.ground_temp IS the global constant, so this returns exactly what groundTemperature returned, by construction.
|
noexcept |
Land-cover longwave [W/m²], INTO the water.
|
noexcept |
Latent heat flux [W/m²], POSITIVE out of the water.
|
noexcept |
Latent heat of vaporization at t_c degrees Celsius [J/kg] (Martin & McCutcheon 1998).
|
noexcept |
Bed contact area for link j, m² — wetted perimeter × length × barrels (divergence 1). 0 for a dry link or a non-conduit.
|
noexcept |
Free-surface area for link j, ft² — top width × length × barrels for an OPEN conduit, 0 otherwise. The Routing.cpp:597 evaporation-area expression, exported so the LARD binding does not grow a fourth copy of the XSectParams builder.
|
noexcept |
Net outward surface flux at t_w, summed over every enabled module [W/m²].
Each module's evaluator returns 0 when its own [HEAT_FLUXES] toggle is off, so this sums unconditionally and no caller has to know which families exist. This is the extension point: a new flux family is one added term here and nowhere else.
Shared with the ARD mesh and watershed bindings, which have their own element geometry but must compose the flux identically — the alternative being four hand-rolled copies of the same sum, which is the duplication that produced the defect above.
PE1: elem names WHICH element is being evaluated, so a module can read per-element attributes (shading, sky view, bed properties) instead of one global block. Passing a default- constructed HeatElement yields the global config — which is exactly what every caller got before PE — so a binding that forgets to pass its element degrades to the old behaviour rather than reading element 0's attributes.
|
noexcept |
Net radiative flux [W/m²], positive OUT of the water so it sums with SurfaceExchange's Je + Jc.
H6a adds two optional arguments and changes nothing without them:
jin_wm2 < 0 (the default) means "use `cfg.shortwave_wm2`", the H3 behaviour. A caller with a resolved per-step Jin passes it here. The sentinel is negative rather than NaN because a deck may legally mean 0 W/m² (night), and 0 must not be mistaken for "unset".cloud_factor defaults to 1.0 — see atmosphericEmissivity.
|
noexcept |
Net absorbed shortwave [W/m²], INTO the water.
|
noexcept |
Station pressure [mb] from elevation [m], standard atmosphere.
|
noexcept |
This module's contribution to the net outward flux at t_w [W/m²]. Returns 0 unless HEAT_TRANSPORT is on AND [HEAT_FLUXES] / RADIATIVE_EXCHANGE is enabled, so a caller sums it unconditionally.
D-H5e: this module no longer has a binding of its own. It had one, with its own element traversal and its own explicit conversion, and that is how it came to relax separately from SurfaceExchange toward a different equilibrium — making the answer depend on which module ran last.
|
noexcept |
The radiative attributes in force for e.
Returns the GLOBAL config when no override targets this family, when the element index is out of range, or when the element is not a kind that carries radiative overrides. The empty-vector path returns the very same object the pre-PE engine used, which is what makes a non-PE model bit-identical by construction rather than by tolerance.
|
noexcept |
One exact step of the coupled (water, bed) pair.
| g | Conductances and capacities for this element. |
| t_w | Current channel water temperature, °C. |
| t_b | Current bed temperature, °C. |
| j0 | Net outward SURFACE flux at t_w, W/m² (netFluxOut). |
| j1 | The same flux at t_w + h — the probe relaxT already takes, passed in rather than recomputed so the pair is stepped against exactly the function the single body was. |
| h | Probe offset, °C. <= 0 disables the linearization and takes j0 as a constant forcing. |
| area_m2 | Free-surface area the surface flux acts on, m². May be 0 for a closed conduit — the bed terms still apply. |
| dt | Step, seconds. |
{0,0} when g is not viable.relaxT when g_wb == g_bg == 0, to within 1-2 ULP — the two take different but algebraically identical routes to the same scalar, so the last bit can differ. Measured over dt in {1, 60, 900, 86400}: 1 ULP at every step, well inside the 4 ULP EXPECT_DOUBLE_EQ allows. The reduction is what keeps pre-H6b answers unchanged, and it holds only because the module is OFF by default — a deck without SEDIMENT_EXCHANGE never enters relaxPair at all, so byte-identity is exact for the reason that matters, not for this one.
|
noexcept |
One element's temperature change, integrated SEMI-IMPLICITLY [°C].
Plan D-H5d. The energy balance ρ cp V dT/dt = −A·J(T) is linearized about the current temperature and the resulting linear ODE is integrated exactly:
k = A·J′ / (ρ cp V) [1/s] T_eq = T₀ − J₀/J′ ΔT = (J₀/J′)·expm1(−k·dt)
This replaces a forward-Euler step that diverged to NaN. Heat capacity is ρ cp V, so a thin film has almost none: a 0.52 ft³ film over 27,226 ft² took a +862 °C step in 60 s and the sequence ran 5 → 182 → −1.8e4 → −3.9e9 → inf → NaN. The explicit form had simply stopped representing the ODE.
Three properties earned this scheme its place, and each is gated:
|ΔT| ≤ |T_eq − T₀| always — the step cannot overshoot equilibrium, whatever dt is. That is an assertion needing no reference value, so no deck or timestep change can stale it.dt → 0 (expm1(−x) ≈ −x), so every small-step answer is unchanged from H2/H3.The flux FORMULATIONS of H2 and H3 were never wrong; only the stepping was. CSH runs these same formulas at a 1e-4 s timestep with a selectable ODE solver — we took its physics and not its integrator.
| j0 | Net flux OUT of the water at T₀, W/m² (sum of every enabled module: Je + Jc from here, Jr from RadiativeExchange — all signed positive out, so they add). |
| j1 | The same sum re-evaluated at T₀ + h. |
| h | The probe offset used for j1; kProbeC unless testing. |
| area_m2 | Exchange area. |
| vol_m3 | Element volume — the thermal mass, and the reason this function exists. |
J′ ≤ 0 (anti-damping: the linearized system has no fixed point to relax onto, so there is nothing to be stable about). | std::vector< std::string > openswmm::transport::heat::resolveHeatOverrides | ( | SimulationContext & | ctx | ) |
Materialise the dense per-element vectors from the parsed rows.
Called ONCE from SWMMEngine::open, after every process component has applied — the D-RQ1 timing, so link_names, node_names and the [TAGS] columns are all complete and an unknown element name can be reported with the row that named it.
Precedence is applied by CONSTRUCTION rather than by comparison: rows are swept in scope order (GLOBAL is already in the base config, then TAG, then element), so a later write simply wins. A comparison-based resolver would have to encode the ordering a second time and the two spellings could disagree.
|
noexcept |
Saturation vapour pressure over water at t_c degrees Celsius [kPa].
|
noexcept |
The bed attributes in force for e.
Bed overrides are LINK-scoped only — the bed zone itself is conduits only (BedZoneState), so a node-scoped bed attribute would describe a body that does not exist. A NODE element therefore always reads the global here, and the parser refuses NODE scope on a [SEDIMENT_EXCHANGE] row rather than accepting a row that could never take effect.
| void openswmm::transport::heat::seedBedTemperature | ( | SimulationContext & | ctx | ) |
Size and seed the bed temperature array, idempotently.
There is deliberately no applyBedHeatExchange. A function by that name would be called beside applyHeatFluxes and would step the bed in its own relaxation — which is D-H5e's defect, one phase after D-H5e removed it. The heat coupling instead happens INSIDE applyHeatFluxes's link loop, which calls relaxPair with the real j0/j1/area so the surface families and the bed are one simultaneous step. This function is only the state setup that loop needs first.
|
noexcept |
Sensible heat flux [W/m²], POSITIVE out of the water.
|
noexcept |
Sun position by the Spencer (1971) / NOAA formulation.
| day_of_year | 1–366. |
| hour_local | Local clock hour, decimal (13.5 = 13:30). |
| lat_deg | Latitude, +N. |
| lon_deg | Longitude, +E. |
| tz_hours | Offset from UTC, +E (MST = −7). |
|
noexcept |
This module's contribution to the net outward flux at t_w [W/m²].
Returns 0 unless HEAT_TRANSPORT is on AND [HEAT_FLUXES] SURFACE_EXCHANGE is enabled, so a caller sums it unconditionally and the toggle stays this module's business.
k·dt it landed on whichever module ran last. Every flux family now sums into one J(T) before a single relaxation, in HeatFluxes.cpp.
|
noexcept |
Resolve Jin and C for this step into ctx.heat_state.
Call at the PROLOGUE of every binding that will go on to call netFluxOut. There are four (applyHeatFluxes, the ARD binding, the watershed binding, the LID binding) and they run on two different clocks, so a single call site upstream of all of them does not exist. The function is idempotent within a step and costs ~40 flops, which is why repeating it is cheaper than inventing a scheduler to avoid repeating it.
shortwave_now is never read, and computing it would be the kind of unobserved work lesson 39 is about.
|
noexcept |
Mass-transfer wind function a + b·w, w in m/s [m/s/kPa].
|
inlineconstexpr |
Bowen's coefficient, kPa/°C (CSH §4.5).
|
inlineconstexpr |
ft³ → m³.
|
inlineconstexpr |
Celsius → Kelvin offset.
|
inlineconstexpr |
mph → m/s. ClimateState::wind_speed is mph.
|
inlineconstexpr |
Probe offset for the flux derivative, °C. Small enough that J′ is the local slope, large enough that the difference is not float noise.
|
inlineconstexpr |
Solar constant, W/m² (Bird & Hulstrom 1981; WMO 1982).
|
inlineconstexpr |
ft² → m². Exported with relaxT, which is useless without it.
|
inlineconstexpr |
Sea-level standard pressure, millibars — Bird's pressure reference.
|
inlineconstexpr |
Stefan–Boltzmann constant, W/m²/K⁴ (rhemodel.cpp:46).