Heat transport (fluxes, solar, cloud, source temperature)#

Note

Engine: OpenSWMM 6 — refactored.

solver.heat is the editable view over the model.heat component: the [HEAT_FLUXES] module toggles, the [RADIATIVE_FLUXES] scalar parameters, H6a’s [SOLAR_RADIATION] and [CLOUD_COVER] sections, and the [HEAT_SOURCES] inlet-temperature table. Every refusal below mirrors the deck parser exactly, so a value the .inp rejects is a value this API rejects.

Reference: openswmm_heat.h.

Warning

Out-of-range values are REFUSED, not clamped, and a refused write does not take effect. Read back after a write only if you want to confirm; do not assume a clamp happened.


Quickstart#

from openswmm.engine import (
    Solver, HeatFluxModule, HeatShortwaveMode,
    HeatRadiativeParam, HeatSolarParam, HeatCloudParam, HeatSourceKind,
)

with Solver("model.inp") as s:
    print(s.heat.enabled)                       # [OPTIONS] HEAT_TRANSPORT

    s.heat.modules[HeatFluxModule.RADIATIVE_EXCHANGE] = True
    s.heat.radiative[HeatRadiativeParam.ALBEDO] = 0.06

    # COMPUTED needs an explicit site first — gate on solar_sited.
    s.heat.solar[HeatSolarParam.LATITUDE] = 40.76
    s.heat.solar[HeatSolarParam.LONGITUDE] = -111.89
    assert s.heat.solar_sited
    s.heat.shortwave_mode = HeatShortwaveMode.COMPUTED

    s.heat.cloud[HeatCloudParam.FRACTION] = 0.4   # fraction, not percent

    s.heat.sources[HeatSourceKind.DWF] = 18.0     # degC
    s.heat.node_overrides.set(HeatSourceKind.DWF, "J1", 21.5)

    s.run()
    print(s.heat.current_shortwave, s.heat.cloud.current)

Note

Edits are live: the flux modules re-read the configuration every step, so a mid-run write takes effect on the next routing step.


Flux modules#

Heat.modules is a keyed, iterable mapping of HeatFluxModule to bool; the three modules — SURFACE_EXCHANGE (latent + sensible), RADIATIVE_EXCHANGE (shortwave + longwave) and LAYER_CONDUCTION (LID vertical conduction) — toggle independently.


Radiative parameters and the shortwave mode#

Heat.radiative maps HeatRadiativeParam to float. Every entry except SHORTWAVE is a fraction restricted to [0, 1].

Parameter

Units / range

SHORTWAVE

Incoming shortwave, W/m². Non-negative.

ALBEDO

Water reflectance Rs.

SHADE_FACTOR / SKY_VIEW / EMISS_WATER / EMISS_LANDCOVER / ATM_EMISS_COEFF / LW_REFLECTION

Shading, sky view, the two emissivities, the Brunt coefficient and longwave reflection.

Heat.shortwave_mode selects one of three HeatShortwaveMode spellings, which are mutually exclusive in effect — exactly one is read, and there is no precedence ladder.

  • CONSTANT reads HeatRadiativeParam.SHORTWAVE.

  • TIMESERIES reads the record bound by Heat.set_shortwave_timeseries() (which also switches the mode).

  • COMPUTED runs a Spencer/NOAA solar position through a Bird clear-sky model, using [SOLAR_RADIATION].

Two refusals matter here:

  • Writing HeatRadiativeParam.SHORTWAVE while the mode is not CONSTANT is refused. A constant is not read in the other two modes, so storing one there would look configured while changing nothing. Switch the mode first.

  • Selecting HeatShortwaveMode.COMPUTED is refused unless both latitude and longitude have been set explicitly. The engine will not borrow the [TEMPERATURE] snowmelt latitude, which defaults to 0 and would silently model equatorial noon.

Switching modes does not erase the other modes’ stored settings. A constant stays stored while a timeseries is active, and vice versa, so an editor can offer three radio buttons without destroying what the user typed under the other two.

Heat.current_shortwave is the resolved incoming shortwave at the current step in W/m², cloud already applied. It is read-only state, not configuration: it is 0.0 before the first step and whenever radiative exchange is off.


Solar siting#

Heat.solar maps HeatSolarParam to float and is consulted under COMPUTED only. LATITUDE is degrees [-90, 90], LONGITUDE degrees [-180, 180], ELEVATION metres [-500, 9000] (below sea level is legal), plus the Bird atmosphere terms TURBIDITY_380, TURBIDITY_500, PRECIP_WATER and OZONE.

Warning

HeatSolarParam.GROUND_ALBEDO is the land albedo used by the Bird model. It is not the water reflectance — that is HeatRadiativeParam.ALBEDO. They are separate values with separate meanings, and writing one does not change the other.

Heat.solar_sited is True once latitude and longitude have both been written explicitly. Gate a COMPUTED control on this rather than discovering the refusal after the fact.


Cloud cover#

Heat.cloud maps HeatCloudParam to float. Writing any of them marks cloud cover configured, which cloud.configured reports. FRACTION is a fraction in [0, 1] — not a percent — and the Kasten–Czeplak / Bolz coefficients must be non-negative.

s.heat.cloud.set_timeseries("CLOUD_OBS")   # bind a [TIMESERIES]
print(s.heat.cloud.configured, s.heat.cloud.current)
s.heat.cloud.clear()                       # back to clear sky

cloud.clear() restores the exact clear-sky longwave path, not an approximation of it. cloud.current is the cloud fraction in effect at the current step — read-only state, zero before the first step.


Heat sources and node overrides#

Heat.sources maps HeatSourceKind to a global inlet temperature in degC. The table is a fixed enum extent of seven entries, so len(s.heat.sources) never depends on what the model configured.

s.heat.sources[HeatSourceKind.RDII] = 12.0
s.heat.sources.is_configured(HeatSourceKind.RDII)   # True
s.heat.sources.clear(HeatSourceKind.RDII)           # back to the 20 degC default

# Resolution the engine itself uses: override if present, else global.
s.heat.sources.effective(HeatSourceKind.DWF, "J1")

Temperatures outside [-50, 100] degC are refused — the parser’s own range. An unconfigured source reads the 20 degC default; sources.is_configured() is what tells the two apart, and sources.clear() returns a source to the default so the writer emits no row for it (node overrides are left alone).

Heat.node_overrides is a row-indexed sequence of HeatNodeOverride named tuples:

s.heat.node_overrides.set(HeatSourceKind.EXTERNAL_INFLOW, "J1", 25.0)
for row in s.heat.node_overrides:
    print(row.source, row.node_index, row.temp_c)
s.heat.node_overrides.remove(0)

Warning

Only HeatSourceKind.DWF and HeatSourceKind.EXTERNAL_INFLOW accept node scope. Any other source is refused, not silently deferred to the global value.

Setting the same (source, node) pair twice is an update, not a duplicate row. node_overrides.remove() shifts later rows down, so re-read len() when iterating by index.


See also#

Timeseries names#

heat.shortwave_timeseries and heat.cloud.timeseries return the assigned series name, or an empty string when no series is bound. Use the corresponding set_shortwave_timeseries(name) and cloud.set_timeseries(name) methods to assign an existing series. Shortwave values are W/m²; cloud values are fractions in [0, 1].