OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
openswmm::transport::heat Namespace Reference

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
 Test current native finite-bed heat family process enable.
 
double groundTemperature (SimulationContext &ctx) noexcept
 Sample the global held deep-ground temperature at native absolute model time.
 
double linkBedAreaM2 (const SimulationContext &ctx, int link) noexcept
 Compute wetted conduit bed contact area from native geometry.
 
double linkFreeSurfaceFt2 (const SimulationContext &ctx, int link) noexcept
 Compute native open-conduit free surface for heat/evaporation binding.
 
double bedExchangeQ (const SedimentConfig &cfg, double bed_m2) noexcept
 Compute total finite-bed solute exchange rate from diffusion and hyporheic velocity.
 
double groundTempFor (const SimulationContext &ctx, const SedimentConfig &sc, double t_gr_global) noexcept
 Choose one element's held deep-ground constant or globally sampled series temperature.
 
void fillChannelThermalGeometry (const SimulationContext &ctx, ChannelThermalRegion &region) noexcept
 Publish native conduit thermal region geometry in SI.
 
BedCoupling bedCouplingFromContact (const SimulationContext &ctx, const SedimentConfig &sc, double bed_m2, double vol_ft3, double t_gr) noexcept
 Construct finite water/bed conductance and capacity from one SI contact footprint.
 
BedCoupling bedCouplingForLink (const SimulationContext &ctx, int link, double vol_ft3, double t_gr) noexcept
 Build a conduit finite-bed pair from current wet perimeter and resolved element material.
 
void seedBedTemperature (SimulationContext &ctx)
 Size and seed native link bed temperatures at their initial owner phase.
 
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
 Sum enabled element latent/sensible and radiative flux under one outward sign convention.
 
void applyHeatFluxes (SimulationContext &ctx, double dt)
 Advance each eligible native water element once against combined atmospheric and finite bed heat.
 
std::vector< std::string > resolveHeatOverrides (SimulationContext &ctx)
 Materialize retained per-element/tag/mesh heat attributes in dense lookup vectors.
 
const RadiativeConfig & radiativeFor (const SimulationContext &ctx, const HeatElement &e) noexcept
 Borrow resolved element radiation parameters with global fallback.
 
const SedimentConfig & sedimentFor (const SimulationContext &ctx, const HeatElement &e) noexcept
 Borrow resolved conduit/mesh-cell sediment parameters with global fallback.
 
double netShortwave (double incoming_wm2, double albedo, double shade_factor) noexcept
 Compute absorbed incident shortwave after albedo and shade.
 
double backLongwave (double t_water_c, double emiss_water) noexcept
 Compute emitted water longwave flux from the Stefan-Boltzmann law.
 
double atmosphericEmissivity (double e_a_kpa, double atm_emiss_coeff, double cloud_factor) noexcept
 Compute Brunt atmospheric emissivity with optional held cloud enhancement.
 
double atmosphericLongwave (double t_air_c, double humidity_pct, double atm_emiss_coeff, double lw_reflection, double sky_view, double cloud_factor) noexcept
 Compute absorbed visible-sky atmospheric longwave into water.
 
double landCoverLongwave (double t_air_c, double emiss_landcover, double sky_view) noexcept
 Compute visible land-cover longwave contribution into water.
 
double netRadiativeFluxOut (double t_water_c, double t_air_c, double humidity_pct, const RadiativeConfig &cfg, double jin_wm2, double cloud_factor, double incoming_longwave) noexcept
 Combine water emission with absorbed shortwave, sky longwave and land-cover longwave.
 
double radiativeFluxOut (const SimulationContext &ctx, const HeatElement &elem, double t_w) noexcept
 Evaluate element radiative flux using the configured global family enable.
 
double radiativeFluxOut (const SimulationContext &ctx, const HeatElement &elem, double t_w, bool enabled) noexcept
 Evaluate held element radiation with an explicit domain enable and no global toggle mutation.
 
double airTempCelsius (const SimulationContext &ctx) noexcept
 Convert held native climate air temperature from Fahrenheit to Celsius.
 
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
 Compute the positive-damping linearized equilibrium temperature.
 
double saturationVapourPressure (double t_c) noexcept
 Compute saturation vapor pressure from the Magnus water-temperature formula.
 
double latentHeatOfVaporization (double t_c) noexcept
 Compute temperature-dependent latent heat of vaporization.
 
double windFunction (double wind_ms, double a, double b) noexcept
 Evaluate the mass-transfer wind function with nonnegative wind speed.
 
double evaporationRate (double t_water_c, double t_air_c, double humidity_pct, double wind_ms, double a, double b) noexcept
 Compute signed vapor-deficit-driven water surface evaporation velocity.
 
double latentFlux (double t_water_c, double t_air_c, double humidity_pct, double wind_ms, double a, double b, double water_density) noexcept
 Convert signed surface evaporation into outward latent heat flux.
 
double bowenRatio (double t_water_c, double t_air_c, double humidity_pct, double pressure_ratio) noexcept
 Compute the dimensionless Bowen sensible-to-latent ratio at held weather.
 
double sensibleFlux (double latent_flux, double bowen_ratio) noexcept
 Form signed outward sensible flux from latent flux and Bowen ratio.
 
double surfaceFluxOut (const SimulationContext &ctx, const HeatElement &elem, double t_w) noexcept
 Evaluate one element's enabled latent+sensible outward atmospheric flux.
 
SolarPosition solarPosition (int day_of_year, double hour_local, double lat_deg, double lon_deg, double tz_hours) noexcept
 Compute Spencer/NOAA sun geometry at a local civil-clock instant.
 
double airMass (double zenith_deg) noexcept
 Compute Kasten-Young relative optical air mass.
 
double pressureFromElevation (double elevation_m) noexcept
 Estimate standard-atmosphere station pressure from elevation.
 
double birdClearSkyGHI (const SolarPosition &pos, double pressure_mb, const SolarConfig &cfg) noexcept
 Estimate clear-sky global horizontal irradiance with Bird-Hulstrom terms.
 
double cloudShortwaveFactor (double cloud_fraction, double k, double n) noexcept
 Compute the cloud attenuation multiplier max(0,1-k*C^n).
 
double cloudLongwaveFactor (double cloud_fraction, double k_lw) noexcept
 Compute the cloud atmospheric-emissivity multiplier 1+k_lw*C^2.
 
void updateSolarForcing (SimulationContext &ctx, double seconds=1)
 Update shared heat shortwave/cloud forcing with mesh radiation disabled.
 

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 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.
 
constexpr double kSolarConstant
 Solar constant, W/m² (Bird & Hulstrom 1981; WMO 1982).
 
constexpr double kStdPressureMb
 Sea-level standard pressure, millibars — Bird's pressure reference.
 

Function Documentation

◆ airMass()

double openswmm::climate::airMass ( double zenith_deg)
noexcept

Compute Kasten-Young relative optical air mass.

Parameters
zenith_degFinite solar zenith in degrees, normally [0,180].
Returns
Dimensionless path factor, or zero for zenith>=90 or a nonpositive/NaN denominator. Does not normalize angles or validate negative zenith inputs.

◆ airTempCelsius()

double openswmm::transport::heat::airTempCelsius ( const SimulationContext & ctx)
noexcept

Convert held native climate air temperature from Fahrenheit to Celsius.

Parameters
[in]ctxStable climate_state.temperature in degF.
Returns
(F-32)*5/9 in degC, without finite repair or element override lookup. Read-only/no allocation.

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.

Here is the caller graph for this function:

◆ applyBedSoluteExchange()

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.

Parameters
[in,out]ctxPrepared native hydraulics/material overrides and finite bed state; may seed bed temperature and resize/reset combined bed native concentration array for n_links*n_total.
[in,out]link_concOptional writable contiguous node-major-style link array [link*n_arr+s] in native concentration units; null returns. Caller guarantees at least n_links*n_arr elements, nonaliasing with bed storage (which may reallocate), stable through call.
[in]dtPositive finite accepted solute interval s; nonpositive/NaN returns.
Note
Requires enabled bed heat family, positive n_arr, nonnegative offset and n_total>=offset+n_arr with arithmetic products representable. Header describes combined bed row mapping. Shape mismatch zero-initializes all bed concentrations, not just selected slice. Skips mesh-contact-owned links, dry/no-wet-bed/nonpositive-exchange/contact-volume; remaining species use exact paired exchange with bulk bed_m2*thickness m3 and water ft3 converted to m3. Does not move water, book a global native mass ledger or advance clock. Progressive concentration changes/allocations can throw without rollback; exclusive matching link/bed native owner and once-only accepted-step invocation required.
  • Additional argument 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.
    Parameters
    n_arrSpecies carried by link_conc.
    offsetWhere 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_totalTotal 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.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ applyHeatFluxes()

void openswmm::transport::heat::applyHeatFluxes ( SimulationContext & ctx,
double dt )

Advance each eligible native water element once against combined atmospheric and finite bed heat.

Apply one step of surface heat exchange to every exchanging node and link.

Parameters
[in,out]ctxPrepared native node/link hydraulic geometry and temperatures, heat/material configuration, weather/providers, bed state and tables. Mutates temperature arrays, bed seeds, held ground/solar forcing and relevant series/geometry cursors; borrowed owners remain live/stable.
[in]dtPositive finite accepted heat interval s; nonpositive/NaN returns. Caller chooses interval consistent with hydraulic volume/heat-owner chronology.
Note
Disabled heat/no flux families returns. Seeds bed/ground forcing once, prepares solar forcing, then relaxes positive-volume storage-node free surfaces and conduit water. Open conduit surface uses native top width*length*barrels; closed conduits still exchange with finite bed. Positive-volume links marked mesh_contact_link are skipped because shared mesh owner handles them. Viable bed water pair uses one coupled relaxation; otherwise positive surface uses one summed-flux relaxT. Converts ft2/ft3 to m2/m3 once, density kg/m3 and cp J/(kg*K); temperatures remain degC. No water movement/engine clock advance or general finite/shape validation. Temperature/bed/forcing changes are progressive; allocation/delegated lookup may throw without rollback. Exclusive native thermal/context/table-cursor ownership and exactly-once accepted-step invocation required.

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.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ atmosphericEmissivity()

double openswmm::transport::heat::atmosphericEmissivity ( double e_a_kpa,
double atm_emiss_coeff,
double cloud_factor )
noexcept

Compute Brunt atmospheric emissivity with optional held cloud enhancement.

Parameters
[in]e_a_kpaAir vapor pressure kPa, negative floored0 before sqrt and converted to Pa.
[in]atm_emiss_coeffDimensionless clear-sky intercept.
[in]cloud_factorDimensionless Bolz cloud multiplier, normally>=1; exactly1 bypasses multiply.
Returns
(coefficient+.0027*sqrt(max(0,e_a)*1000))*cloud_factor, dimensionless, not clamped to[0,1].
Note
Qualified finite input required; pure/no allocation/physics or clock mutation.

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.

Here is the caller graph for this function:

◆ atmosphericLongwave()

double openswmm::transport::heat::atmosphericLongwave ( double t_air_c,
double humidity_pct,
double atm_emiss_coeff,
double lw_reflection,
double sky_view,
double cloud_factor )
noexcept

Compute absorbed visible-sky atmospheric longwave into water.

Atmospheric longwave [W/m²], INTO the water.

Parameters
[in]t_air_cQualified air degC.
[in]humidity_pctRelative humidity percent, normally[0,100].
[in]atm_emiss_coeffDimensionless Brunt intercept.
[in]lw_reflectionLongwave reflected fraction, normally[0,1].
[in]sky_viewVisible-sky hemisphere fraction, normally[0,1].
[in]cloud_factorHeld dimensionless cloud emissivity multiplier, default1 for clear sky.
Returns
sigma*TairK^4*emissivity*(1-reflection)*sky_view W/m2 into water. No independent weather/range/finite validation, owner/clock mutation or allocation.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ backLongwave()

double openswmm::transport::heat::backLongwave ( double t_water_c,
double emiss_water )
noexcept

Compute emitted water longwave flux from the Stefan-Boltzmann law.

Back longwave emitted by the water [W/m²], OUT of the water.

Parameters
[in]t_water_cQualified water degC, converted to K.
[in]emiss_waterDimensionless water emissivity, normally[0,1].
Returns
emissivity*sigma*(T+273.15)^4 W/m2 positive out of water. No finite/range checks or canonical energy/clock mutation; pure/allocation-free.
Here is the caller graph for this function:

◆ bedCouplingForLink()

BedCoupling openswmm::transport::heat::bedCouplingForLink ( const SimulationContext & ctx,
int link,
double vol_ft3,
double t_gr )
noexcept

Build a conduit finite-bed pair from current wet perimeter and resolved element material.

Bed conductances for link j from the deck configuration and the link's current hydraulics.

Parameters
[in]ctxStable prepared link/native geometry/global+resolved bed overrides/water constants.
[in]linkValid zero-based link index.
[in]vol_ft3Accepted positive native conduit water volume ft3.
[in]t_grAlready sampled global deep-ground degC, used when global series configured.
Returns
Constructed SI coupling, zero/nonviable for nonpositive water/no wet footprint/invalid capacity. Uses element sedimentFor and groundTempFor, no weather refresh or physical advance.
Note
Read-only/no allocation; caller retains stable material/geometry and validates profile.

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.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ bedCouplingFromContact()

BedCoupling openswmm::transport::heat::bedCouplingFromContact ( const SimulationContext & ctx,
const SedimentConfig & sc,
double bed_m2,
double vol_ft3,
double t_gr )
noexcept

Construct finite water/bed conductance and capacity from one SI contact footprint.

The material half of the coupling, geometry already folded in.

Parameters
[in]ctxStable positive SI water density kg/m3 and specific heat J/(kg*K).
[in]scQualified SI thermal diffusivity m2/s, thickness/deep depth m, solid density kg/m3, specific heat J/(kg*K), hyporheic velocity m/s and held ground parameters.
[in]bed_m2Nonnegative contact area m2.
[in]vol_ft3Positive native water volume ft3, converted once to m3 for heat capacity.
[in]t_grHeld selected deep-ground temperature degC.
Returns
Owned coupling with water/bed J/K capacities, W/K conductive+advective water/bed and conductive bed/ground links, ground degC. Zero coupling for nonpositive/NaN area/water/thickness. Bed capacity uses bulk footprint*thickness, not porosity. Nonpositive deep depth makes ground link0.
Note
No general finite/coefficient validation, allocation, state/clock mutation; caller checks viability and physical profile before adopting relaxPair results.

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.

Here is the caller graph for this function:

◆ bedExchangeEnabled()

bool openswmm::transport::heat::bedExchangeEnabled ( const SimulationContext & ctx)
noexcept

Test current native finite-bed heat family process enable.

True when [HEAT_FLUXES] SEDIMENT_EXCHANGE is on and heat transport is.

Parameters
[in]ctxStable global process/family flags.
Returns
heat_transport&&sediment_exchange, independent of current wetness/bed vector sizing.
Note
Read-only/no allocation; does not prepare, seed or qualify any physical owner.
Here is the caller graph for this function:

◆ bedExchangeQ()

double openswmm::transport::heat::bedExchangeQ ( const SedimentConfig & cfg,
double bed_m2 )
noexcept

Compute total finite-bed solute exchange rate from diffusion and hyporheic velocity.

Parameters
[in]cfgQualified SI sediment config, diffusivity m2/s, thickness m and hyporheic velocity m/s.
[in]bed_m2Nonnegative contact area m2.
Returns
Ds*A/thickness+velocity*A m3/s, zero for nonpositive/NaN area or thickness. No independent coefficient finite/sign validation; pure/no allocation/water-native-clock mutation.

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.

Here is the caller graph for this function:

◆ birdClearSkyGHI()

double openswmm::climate::birdClearSkyGHI ( const SolarPosition & pos,
double pressure_mb,
const SolarConfig & cfg )
noexcept

Estimate clear-sky global horizontal irradiance with Bird-Hulstrom terms.

Parameters
posCompatible solarPosition result with nonnegative cosine and valid zenith/eccentricity. The cosine and zenith must describe the same instant.
pressure_mbFinite nonnegative station pressure in millibars.
cfgPhysical aerosol optical depths, precipitable water/ozone centimetres and land ground-albedo fraction. Caller validates finite model ranges.
Returns
Nonnegative irradiance W/m2, zero without positive cosine/air mass. A nonpositive reflection denominator omits that correction. No diagnostics or complete input-domain validation are supplied by this noexcept numerical helper.

◆ bowenRatio()

double openswmm::transport::heat::bowenRatio ( double t_water_c,
double t_air_c,
double humidity_pct,
double pressure_ratio )
noexcept

Compute the dimensionless Bowen sensible-to-latent ratio at held weather.

Parameters
[in]t_water_cWater degC.
[in]t_air_cAir degC.
[in]humidity_pctRelative humidity percent, normally[0,100].
[in]pressure_ratioDimensionless atmospheric pressure ratio.
Returns
kBowenCoeff*pressure_ratio*(Twater-Tair)/vapor_deficit, or0 when absolute deficit<1e-12kPa.
Note
No finite/range validation; near-zero return implements current singular fallback. Pure/no allocation.

Bowen ratio [-]. Returns 0 when the vapour-pressure deficit vanishes, which is the removable singularity in Br's definition, not an error.

Here is the call graph for this function:

◆ cloudLongwaveFactor()

double openswmm::climate::cloudLongwaveFactor ( double cloud_fraction,
double k_lw )
noexcept

Compute the cloud atmospheric-emissivity multiplier 1+k_lw*C^2.

Parameters
cloud_fractionCloud fraction; nonpositive or NaN returns literal one; positive values above one are capped at one.
k_lwFinite dimensionless enhancement coefficient, normally nonnegative.
Returns
Dimensionless multiplier without an output clamp. Literal clear-sky one avoids evaluating coefficient arithmetic for a cloud-free input.
Here is the caller graph for this function:

◆ cloudShortwaveFactor()

double openswmm::climate::cloudShortwaveFactor ( double cloud_fraction,
double k,
double n )
noexcept

Compute the cloud attenuation multiplier max(0,1-k*C^n).

Parameters
cloud_fractionCloud fraction; nonpositive or NaN returns literal one; positive values above one are capped at one.
kFinite dimensionless attenuation coefficient, normally nonnegative.
nFinite exponent compatible with the positive cloud fraction.
Returns
Dimensionless factor; lower-bounded at zero but not capped at one when negative k is supplied. No other coefficient validation occurs.

◆ equilibriumT()

double openswmm::transport::heat::equilibriumT ( double t0_c,
double j0,
double j1,
double h )
noexcept

Compute the positive-damping linearized equilibrium temperature.

Parameters
[in]t0_cInitial water degC.
[in]j0Outward flux W/m2 at initial temperature.
[in]j1Outward flux at t0_c+h.
[in]hPositive finite probe offset degC.
Returns
t0_c-j0/((j1-j0)/h), or t0_c for nonpositive/NaN probe or derivative. No finite-result repair; qualified inputs avoid singular physics. Pure, allocation-free, no owner/clock mutation.

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.

◆ evaporationRate()

double openswmm::transport::heat::evaporationRate ( double t_water_c,
double t_air_c,
double humidity_pct,
double wind_ms,
double a,
double b )
noexcept

Compute signed vapor-deficit-driven water surface evaporation velocity.

Evaporative mass-transfer rate [m/s]. Negative under condensation.

Parameters
[in]t_water_cQualified water degC.
[in]t_air_cQualified air degC.
[in]humidity_pctRelative humidity percent, normally[0,100]; not clamped here.
[in]wind_msWind m/s, negative floored0.
[in]aMass-transfer intercept m/s/kPa.
[in]bMass-transfer wind slope (m/s/kPa)/(m/s).
Returns
f(w)*(es(Twater)-RH/100*es(Tair)) m/s; positive evaporation, negative condensation.
Note
Pure/allocation-free; no temperature/weather/finite validation or canonical water withdrawal.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ exchangePair()

SolutePairStep openswmm::transport::heat::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.

Both concentrations must use the same native units; no unit conversion or physical inventory mutation occurs. Positive finite volumes m3, exchange m3/s and dt s are required; each nonpositive/NaN argument returns zero. Exactly decays concentration difference and pairs increments by volume shares; computed nonfinite share returns zero silently, not an error diagnostic. No independent concentration positivity/finite validation or clock mutation; pure/allocation-free.

Parameters
c_wChannel concentration.
c_bBed concentration.
vol_wChannel water volume, m³.
vol_bBed 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_exchTotal exchange discharge, m³/s: D_sed*A/Y + v_hyp*A.
dtStep, seconds.
Returns
Increments satisfying vol_w*dc_w + vol_b*dc_b == 0 to relative round-off. See the header on why this is not bit-exact.
Here is the caller graph for this function:

◆ fillChannelThermalGeometry()

void openswmm::transport::heat::fillChannelThermalGeometry ( const SimulationContext & ctx,
ChannelThermalRegion & region )
noexcept

Publish native conduit thermal region geometry in SI.

Shared LEGACY/LARD geometry adapter, in SI. ARD supplies its actual cell geometry.

Parameters
[in]ctxStable native link/subtype/shape/length/depth/barrel metadata.
[in,out]regionIts link selects valid conduit; receives length/depth m, free surface/bed m2. Invalid link/non-conduit returns leaving all existing geometry unchanged.
Note
Positive length preferred else mod_length; direct length/depth values are not clamped here, area helpers apply their own positivity tests. No allocation/physics/clock change; noexcept valid geometry queries and exclusive region output required.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ groundTemperature()

double openswmm::transport::heat::groundTemperature ( SimulationContext & ctx)
noexcept

Sample the global held deep-ground temperature at native absolute model time.

Parameters
[in,out]ctxStable sediment configuration/table store/current_date; lookup can advance selected table's mutable cursor without changing table ordinates or engine clock.
Returns
degC from in-range configured ground series, else configured constant. Does not require a positive series value or independently validate finite output.
Note
noexcept dependency lookup must not throw; exclusive cursor access, no physical heat advance.

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.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ groundTempFor()

double openswmm::transport::heat::groundTempFor ( const SimulationContext & ctx,
const SedimentConfig & sc,
double t_gr_global )
noexcept

Choose one element's held deep-ground constant or globally sampled series temperature.

Parameters
[in]ctxStable global sediment.ground_ts_index configuration.
[in]scResolved per-element sediment block with ground_temp degC.
[in]t_gr_globalPreviously sampled global ground temperature degC.
Returns
Global sample if global index>=0 (even if out of range), else element constant. No sampling, validation, allocation, owner or clock mutation; caller resolves global series once per interval.

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.

Here is the caller graph for this function:

◆ landCoverLongwave()

double openswmm::transport::heat::landCoverLongwave ( double t_air_c,
double emiss_landcover,
double sky_view )
noexcept

Compute visible land-cover longwave contribution into water.

Land-cover longwave [W/m²], INTO the water.

Parameters
[in]t_air_cRadiating land-cover temperature degC; parameter historical name reflects fallback air temperature, caller may supply configured distinct land-cover temperature.
[in]emiss_landcoverDimensionless emissivity, normally[0,1].
[in]sky_viewVisible-sky fraction, so land cover uses complement1-sky_view.
Returns
emissivity*sigma*(1-sky_view)*Tk^4 W/m2 into water. Pure/no allocation/state change; physical/finite input qualification belongs to caller.
Here is the caller graph for this function:

◆ latentFlux()

double openswmm::transport::heat::latentFlux ( double t_water_c,
double t_air_c,
double humidity_pct,
double wind_ms,
double a,
double b,
double water_density )
noexcept

Convert signed surface evaporation into outward latent heat flux.

Latent heat flux [W/m²], POSITIVE out of the water.

Parameters
[in]t_water_cWater degC for vapor pressure and latent heat.
[in]t_air_cAir degC.
[in]humidity_pctRelative humidity percent.
[in]wind_msWind m/s, negative floored0.
[in]aWind-function intercept m/s/kPa.
[in]bWind-function slope (m/s/kPa)/(m/s).
[in]water_densityPositive water density kg/m3.
Returns
rho*Le(Twater)*evaporationRate in W/m2, positive cooling, negative condensation heating.
Note
Pure/no allocation; physical range/finite checks and accepted evaporation-water pairing are caller responsibilities. No canonical water/heat or engine-clock mutation.
Here is the call graph for this function:

◆ latentHeatOfVaporization()

double openswmm::transport::heat::latentHeatOfVaporization ( double t_c)
noexcept

Compute temperature-dependent latent heat of vaporization.

Parameters
[in]t_cQualified water temperature degC.
Returns
1000*(2499-2.36*T) J/kg, without sign/finite clamp. Pure/allocation-free, no state mutation.

Latent heat of vaporization at t_c degrees Celsius [J/kg] (Martin & McCutcheon 1998).

Here is the caller graph for this function:

◆ linkBedAreaM2()

double openswmm::transport::heat::linkBedAreaM2 ( const SimulationContext & ctx,
int link )
noexcept

Compute wetted conduit bed contact area from native geometry.

Parameters
[in]ctxStable valid conduit subtype mapping, native dimensions/depth/length/barrels.
[in]linkValid zero-based native link index, unchecked before several array accesses.
Returns
(wet cross-section area/hydraulic radius)*positive length*barrels converted ft2 to m2, zero for non-conduit/nonpositive length/depth/area/radius/result. Positive original length preferred, else mod_length. Closed conduit still has wet perimeter; no free-surface requirement.
Note
No general finite/shape validation, allocation or physical/clock mutation; valid geometry query dependencies must be nonthrowing for noexcept contract.

Bed contact area for link j, m² — wetted perimeter × length × barrels (divergence 1). 0 for a dry link or a non-conduit.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ linkFreeSurfaceFt2()

double openswmm::transport::heat::linkFreeSurfaceFt2 ( const SimulationContext & ctx,
int link )
noexcept

Compute native open-conduit free surface for heat/evaporation binding.

Parameters
[in]ctxStable prepared subtype/shape/geometry, length and barrel metadata.
[in]linkValid zero-based native link index, unchecked.
Returns
Top width*length*barrels ft2; zero for non-conduit/closed/nonpositive length/depth/width. Original positive length preferred, else mod_length. No array/finite validation or allocation; read-only/nonthrowing geometry dependencies, no physical/clock mutation.

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.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ netFluxOut()

double openswmm::transport::heat::netFluxOut ( const SimulationContext & ctx,
const HeatElement & elem,
double t_w )
noexcept

Sum enabled element latent/sensible and radiative flux under one outward sign convention.

Net outward surface flux at t_w, summed over every enabled module [W/m²].

Parameters
[in]ctxStable heat/weather/element-override context; providers nonthrowing and already sampled.
[in]elemElement identity selecting prepared local attributes/weather, or global default.
[in]t_wQualified water degC, passed unchanged to both flux families.
Returns
Surface+radiative W/m2, positive heat out of water; each disabled family contributes0.
Note
Bed contact is a separate finite second-body operator, not an additive atmospheric term. Pure/read-only query, no physical/clock mutation or weather refresh; no finite/result validation.

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.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ netRadiativeFluxOut()

double openswmm::transport::heat::netRadiativeFluxOut ( double t_water_c,
double t_air_c,
double humidity_pct,
const RadiativeConfig & cfg,
double jin_wm2,
double cloud_factor,
double incoming_longwave )
noexcept

Combine water emission with absorbed shortwave, sky longwave and land-cover longwave.

Parameters
[in]t_water_cQualified water degC.
[in]t_air_cHeld air degC.
[in]humidity_pctHeld relative humidity percent.
[in]cfgStable element radiative parameters; NaN landcover_temp selects air fallback, otherwise configured degC is used directly. Fractions/emissivities must already qualify.
[in]jin_wm2Held incident W/m2; negative sentinel uses cfg.shortwave_wm2, zero is valid night.
[in]cloud_factorHeld dimensionless atmospheric enhancement, used for estimated sky flux.
[in]incoming_longwaveNonnegative measured/shared sky W/m2 before reflection/sky-view; negative sentinel estimates sky from weather. Measured branch bypasses Brunt/cloud estimate.
Returns
Water back radiation minus absorbed shortwave/sky/landcover, signed W/m2 positive cooling.
Note
Keeps all absorbed solar in water in this evaluator; bed/penetration sharing belongs to its physical owner. No finite-result/physical bound repair, allocation or state/calendar mutation.

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.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ netShortwave()

double openswmm::transport::heat::netShortwave ( double incoming_wm2,
double albedo,
double shade_factor )
noexcept

Compute absorbed incident shortwave after albedo and shade.

Net absorbed shortwave [W/m²], INTO the water.

Parameters
[in]incoming_wm2Qualified nonnegative incident solar W/m2.
[in]albedoDimensionless reflected fraction, normally[0,1], not clamped.
[in]shade_factorDimensionless shade fraction; values>1 fully shade via max(0,1-shade).
Returns
(1-albedo)*incoming*max(0,1-shade) W/m2 positive into water. Negative shade can amplify forcing; no finite/range validation. Pure/no allocation/physical state or clock change.
Here is the caller graph for this function:

◆ pressureFromElevation()

double openswmm::climate::pressureFromElevation ( double elevation_m)
noexcept

Estimate standard-atmosphere station pressure from elevation.

Parameters
elevation_mFinite metres above sea level; negative elevations allowed. Keep 1-2.25577e-5*elevation_m nonnegative for the power-law domain.
Returns
Pressure in millibars; no range clamp, finite check or error diagnostic.

◆ radiativeFluxOut() [1/2]

double openswmm::transport::heat::radiativeFluxOut ( const SimulationContext & ctx,
const HeatElement & elem,
double t_w )
noexcept

Evaluate element radiative flux using the configured global family enable.

Parameters
[in]ctxStable held heat/weather/override context, queried without refresh.
[in]elemBorrowed native element/global identity for local parameter/forcing selection.
[in]t_wQualified water degC.
Returns
Explicit-enable overload result with ctx.heat_config.radiative_exchange; positive W/m2 out.
Note
Heat transport gate, override/forcing precedence, finite-value assumptions and noexcept provider restrictions of delegated overload apply; no physical/calendar mutation.

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. 
Here is the call graph for this function:
Here is the caller graph for this function:

◆ radiativeFluxOut() [2/2]

double openswmm::transport::heat::radiativeFluxOut ( const SimulationContext & ctx,
const HeatElement & elem,
double t_w,
bool enabled )
noexcept

Evaluate held element radiation with an explicit domain enable and no global toggle mutation.

Same evaluator with an explicit domain enable; no global mutation.

Parameters
[in]ctxStable heat/climate config, prepared solar/cloud/shared longwave and override/provider bindings. Accessors must remain live and nonthrowing through this noexcept query.
[in]elemElement/global identity selecting cached radiative block and local weather/shortwave.
[in]t_wQualified water degC.
[in]enabledDomain-selected radiation enable, independent of global family flag.
Returns
Zero unless heat_transport and enabled; otherwise signed outward W/m2 from local/global parameters, element air/humidity/shortwave forcing, cloud enhancement and optional shared longwave.
Note
Element shortwave overrides global held Jin before shade/albedo; local air degF converts to Celsius. radiativeFor only reads already-resolved dense overrides with global fallback. No weather-step refresh, water/energy transfer or clock advance; no finite validation. Unexpected throwing cache/provider/allocation under noexcept terminates; stable serial cache binding required.
Here is the call graph for this function:

◆ radiativeFor()

const RadiativeConfig & openswmm::transport::heat::radiativeFor ( const SimulationContext & ctx,
const HeatElement & e )
noexcept

Borrow resolved element radiation parameters with global fallback.

The radiative attributes in force for e.

Parameters
[in]ctxStable already-resolved dense radiative overrides and global base, alive through use.
[in]eElement kind/index; nonnegative in-range LINK/NODE/CELL2D selects corresponding vector.
Returns
Nonowned const reference to matching dense row, else exact global config object. Invalid/ unsupported kinds/indices fall back, no diagnostic. Reference invalidated by context/cache edits, resolution/move/destruction.
Note
Does not inspect resolved flag, rebuild cache, allocate, validate, sample forcing or mutate physical state/clock. Caller ensures prepared cache is current and stable, serial authoring access.

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.

Here is the caller graph for this function:

◆ relaxPair()

PairStep openswmm::transport::heat::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.

Parameters
[in]dtPositive finite accepted duration s; nonpositive/NaN returns zero increments.
Returns
Owned degC increments for water/bed, or both0 for nonviable capacities/nonpositive duration or nonfinite computed increments. Does not signal refusal separately from legitimate zero change.
Note
g fields require finite nonnegative W/K conductances, positive J/K capacities and qualified held deep-ground degC; viable() itself checks only capacity positivity. Uses positive finite surface flux derivative only (otherwise held j0), area m2 may0 for closed conduit. Isolated water/bed with no ground/surface forcing uses an energy-paired exponential difference; general2x2 held linear system uses phi functions/repeated-eigenvalue expansion. No canonical state mutation, allocation, clock advance or global rollback; caller adopts both accepted increments and owns physical ledger.
Parameters
gConductances and capacities for this element.
t_wCurrent channel water temperature, °C.
t_bCurrent bed temperature, °C.
j0Net outward SURFACE flux at t_w, W/m² (netFluxOut).
j1The 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.
hProbe offset, °C. <= 0 disables the linearization and takes j0 as a constant forcing.
area_m2Free-surface area the surface flux acts on, m². May be 0 for a closed conduit — the bed terms still apply.
  • Additional argument dt: Step, seconds.
Returns
Increments for both bodies. {0,0} when g is not viable.
Note
Reduces to 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.
Here is the caller graph for this function:

◆ relaxT()

double openswmm::transport::heat::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].

Parameters
[in]dtPositive accepted interval s; nonpositive/NaN returns0.
[in]rhoWater density kg/m3; rho*cp*vol_m3 must be positive finite for qualified physics.
[in]cpWater specific heat J/(kg*K).
Note
Inputs are read-only and borrowed only as values. Positive damping uses exact linearized exponential relaxation, with finite-result check; invalid/nonpositive probe/derivative/coefficient or nonfinite relaxed result falls back to explicit increment. This fallback is not guaranteed finite, admissible or non-overshooting. Nonpositive/NaN heat capacity/area returns0; no independent upper/finite check on all inputs. No water/energy owner/clock mutation or allocation.

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.
  • No iteration, hence no cap to tune and no failure mode when the cap is reached.
  • Degrades to forward Euler as 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.

Parameters
j0Net 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).
j1The same sum re-evaluated at T₀ + h.
hThe probe offset used for j1; kProbeC unless testing.
area_m2Exchange area.
vol_m3Element volume — the thermal mass, and the reason this function exists.
Returns
ΔT in °C. Zero for a volume-less or area-less element. Falls back to the explicit step when J′ ≤ 0 (anti-damping: the linearized system has no fixed point to relax onto, so there is nothing to be stable about).
Here is the caller graph for this function:

◆ resolveHeatOverrides()

std::vector< std::string > openswmm::transport::heat::resolveHeatOverrides ( SimulationContext & ctx)

Materialize retained per-element/tag/mesh heat attributes in dense lookup vectors.

Materialise the dense per-element vectors from the parsed rows.

Parameters
[in,out]ctxFully parsed matching native names/tags and optional mesh, global radiative/ sediment config and retained override rows; receives resolved flag and dense family vectors.
Returns
Owned missing element/tag/invalid one-based CELL2D/no-2D-build diagnostics, empty on these resolution checks succeeding. Does not itself validate every attribute value or runtime heat profile.
Note
Sets resolved=true before work, even on diagnostic return. Allocates only families/scopes requested by current rows using global base, applies MESH_TAG then CELL2D; native TAG then explicit NODE/LINK with later row wins. GLOBAL already incorporated by parser and is skipped. Native tag matches exact strings; sediment TAG searches links only, radiative TAG links+nodes. CELL2D string strictly parses positive one-based index then stores dense zero-based lookup. Does not clear unused/stale dense caches when rows removed or empty; caller resets/invalidates those through authoring lifecycle before re-resolution. Allocation can leave partial caches and resolved flag, no rollback. No physical water/heat/native inventory/clock advance; exclusive setup and all borrowed model identity vectors stable through call.

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.

Returns
Diagnostics; non-empty means the model must not open. Unknown element names are FATAL (D-PE5): a silently ignored override produces a plausible answer, which is the failure mode lessons 10/20 are about.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ saturationVapourPressure()

double openswmm::transport::heat::saturationVapourPressure ( double t_c)
noexcept

Compute saturation vapor pressure from the Magnus water-temperature formula.

Saturation vapour pressure over water at t_c degrees Celsius [kPa].

Parameters
[in]t_cQualified finite water/air temperature degC, away from -237.3 singular denominator.
Returns
0.61275*exp(17.27*T/(237.3+T)) kPa; no domain/finite/range repair.
Note
Pure/allocation-free, no state/clock effect; caller validates physical applicability.
Here is the caller graph for this function:

◆ sedimentFor()

const SedimentConfig & openswmm::transport::heat::sedimentFor ( const SimulationContext & ctx,
const HeatElement & e )
noexcept

Borrow resolved conduit/mesh-cell sediment parameters with global fallback.

The bed attributes in force for e.

Parameters
[in]ctxStable already-resolved dense sediment overrides/global base, alive through use.
[in]eNonnegative in-range LINK or CELL2D selects matching dense row; NODE/global/invalid falls back to global block.
Returns
Nonowned const config reference; cache/context replacement/resolution invalidates views.
Note
No cache rebuild/resolved-flag test, allocation, validation or physical/clock mutation. Caller qualifies family applicability/ownership and retains stable prepared lookup during use.

Dense bed overrides support LINK and CELL2D scopes for their respective finite physical owners. NODE and unsupported/out-of-range identities read the global configuration. The accessor does not create a bed or qualify runtime ownership.

Here is the caller graph for this function:

◆ seedBedTemperature()

void openswmm::transport::heat::seedBedTemperature ( SimulationContext & ctx)

Size and seed native link bed temperatures at their initial owner phase.

Size and seed the bed temperature array, idempotently.

Parameters
[in,out]ctxRetained bed state and current native link count/global sediment config.
Note
Uses global explicit initial degC if configured, else global ground constant (not current series/per-element override). Shape mismatch resets entire vector and seeded flag, then seeds every entry; matching already-seeded owner retained unchanged. Does not filter active/wet/conduit links. Allocation may throw; exclusive initial/rebuild lifecycle required, no physical interval or engine clock advance. Calling after count change intentionally replaces evolved bed temperatures.

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.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sensibleFlux()

double openswmm::transport::heat::sensibleFlux ( double latent_flux,
double bowen_ratio )
noexcept

Form signed outward sensible flux from latent flux and Bowen ratio.

Sensible heat flux [W/m²], POSITIVE out of the water.

Parameters
[in]latent_fluxSigned latent W/m2.
[in]bowen_ratioDimensionless held ratio.
Returns
Product W/m2. Pure/no allocation; no sign/finite checks or physical/clock mutation.

◆ solarPosition()

SolarPosition openswmm::climate::solarPosition ( int day_of_year,
double hour_local,
double lat_deg,
double lon_deg,
double tz_hours )
noexcept

Compute Spencer/NOAA sun geometry at a local civil-clock instant.

Parameters
day_of_yearCalendar ordinal in [1,366]; the fit uses a 365-day denominator.
hour_localFinite decimal local hour, normally [0,24).
lat_degLatitude degrees north in [-90,90].
lon_degFinite longitude degrees east, normally [-180,180].
tz_hoursFinite civil offset hours east of UTC; no daylight-saving lookup.
Returns
Degree-valued declination/hour-angle/zenith/elevation, equation of time in minutes, nonnegative zenith cosine and dimensionless distance correction. Hour angle wraps to [-180,180]. Inputs are not validated; nonfinite hour/longitude can make wrapping nonterminating. This approximation is not a high-precision SPA solver.

◆ surfaceFluxOut()

double openswmm::transport::heat::surfaceFluxOut ( const SimulationContext & ctx,
const HeatElement & elem,
double t_w )
noexcept

Evaluate one element's enabled latent+sensible outward atmospheric flux.

This module's contribution to the net outward flux at t_w [W/m²].

Parameters
[in]ctxStable heat/process configuration, native climate and already prepared element forcing providers/caches; query callbacks must be nonthrowing for this noexcept path.
[in]elemBorrowed node/link/subcatch/global identity selecting held weather overrides.
[in]t_wQualified water temperature degC.
Returns
0 unless heat transport+surface exchange enabled; otherwise signed W/m2 positive cooling.
Note
Reads element air degF/humidity percent/wind mph with climate fallback, converts weather to SI and delegates pure flux sum with configured coefficients/density/pressure ratio. Does not sample a new weather calendar, apply a water sink, alter thermal energy or advance clock. Stable exclusive/ const-consistent provider binding; no general finite/physical validation or allocation by helper.

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.

Why this module no longer has a binding of its own (D-H5e)
It did, and so did RadiativeExchange, and each relaxed FULLY toward its own equilibrium. Under forward Euler the two increments were linear and added exactly; under relaxation they do NOT commute, so with both modules on the pair overshot the true combined equilibrium and the answer depended on module order — at large 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.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ updateSolarForcing()

void openswmm::climate::updateSolarForcing ( SimulationContext & ctx,
double seconds = 1 )

Update shared heat shortwave/cloud forcing with mesh radiation disabled.

Parameters
[in,out]ctxInitialized heat/climate/snow state at current_date; serializes table cursors and shared output state with other forcing readers/writers.
secondsInterval seconds forwarded to the snow resolver when authored radiation/MET_FILE is present; default one. Otherwise legacy heat uses an instant. Equivalent to the three-argument overload with mesh_enabled=false; same failure and mutation behavior. Call before flux evaluation on each consuming clock.
Here is the caller graph for this function:

◆ windFunction()

double openswmm::transport::heat::windFunction ( double wind_ms,
double a,
double b )
noexcept

Evaluate the mass-transfer wind function with nonnegative wind speed.

Mass-transfer wind function a + b·w, w in m/s [m/s/kPa].

Parameters
[in]wind_msWind m/s, negative floored0; no general NaN/finite repair.
[in]aIntercept m/s/kPa.
[in]bSlope in (m/s/kPa)/(m/s).
Returns
a+b*max(wind_ms,0), m/s/kPa. No coefficient positivity validation/allocation/state mutation.
Here is the caller graph for this function:

Variable Documentation

◆ kBowenCoeff

double openswmm::transport::heat::kBowenCoeff = 0.061
inlineconstexpr

Bowen's coefficient, kPa/°C (CSH §4.5).

◆ kCuFtToCuM

double openswmm::transport::heat::kCuFtToCuM = 0.028316846592
inlineconstexpr

ft³ → m³.

◆ kKelvinOffset

double openswmm::transport::heat::kKelvinOffset = 273.15
inlineconstexpr

Celsius → Kelvin offset.

◆ kMphToMs

double openswmm::transport::heat::kMphToMs = 0.44704
inlineconstexpr

mph → m/s. ClimateState::wind_speed is mph.

◆ kProbeC

double openswmm::transport::heat::kProbeC = 1.0e-3
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.

◆ kSolarConstant

double openswmm::climate::kSolarConstant
inlineconstexpr

Solar constant, W/m² (Bird & Hulstrom 1981; WMO 1982).

◆ kSqFtToSqM

double openswmm::transport::heat::kSqFtToSqM = 0.09290304
inlineconstexpr

ft² → m². Exported with relaxT, which is useless without it.

◆ kStdPressureMb

double openswmm::climate::kStdPressureMb
inlineconstexpr

Sea-level standard pressure, millibars — Bird's pressure reference.

◆ kStefanBoltzmann

double openswmm::transport::heat::kStefanBoltzmann = 5.67e-8
inlineconstexpr

Stefan–Boltzmann constant, W/m²/K⁴ (rhemodel.cpp:46).