Source code for openswmm.legacy.engine._system

"""Pythonic access to SWMM system-level properties and mass balance totals."""

from typing import TYPE_CHECKING, Dict, Tuple

from ._solver import (
    SWMMObjects,
    SWMMSystemProperties,
    SWMMFlowUnits,
)

if TYPE_CHECKING:
    from ._solver import Solver


[docs] class LegacySystem: """System-level properties, simulation settings, and mass balance totals. Wraps a :class:`Solver` instance and provides convenient property-based access to global simulation parameters and post-simulation mass balance breakdowns. """ __slots__ = ("_solver",) def __init__(self, solver: "Solver") -> None: self._solver = solver def _get(self, prop: SWMMSystemProperties) -> float: return self._solver.get_value(SWMMObjects.SYSTEM, prop, 0) def _set(self, prop: SWMMSystemProperties, value: float) -> None: self._solver.set_value(SWMMObjects.SYSTEM, prop, 0, value) # --- simulation settings (read-only after start) --- @property def flow_units(self) -> SWMMFlowUnits: """Flow units used in this simulation.""" return SWMMFlowUnits(int(self._get(SWMMSystemProperties.FLOW_UNITS))) @property def unit_system(self) -> int: """Unit system (0=US, 1=SI).""" return int(self._get(SWMMSystemProperties.UNIT_SYSTEM)) @property def routing_step(self) -> float: """Routing time step (seconds).""" return self._get(SWMMSystemProperties.ROUTING_STEP) @property def report_step(self) -> float: """Reporting time step (seconds).""" return self._get(SWMMSystemProperties.REPORT_STEP) @property def total_steps(self) -> int: """Total number of routing steps.""" return int(self._get(SWMMSystemProperties.TOTAL_STEPS)) @property def num_threads(self) -> int: """Number of threads for parallel computation.""" return int(self._get(SWMMSystemProperties.NUM_THREADS)) @property def allow_ponding(self) -> bool: """Whether ponding is allowed at nodes.""" return bool(int(self._get(SWMMSystemProperties.ALLOW_PONDING))) # --- error/tolerance settings --- @property def head_tolerance(self) -> float: """Head convergence tolerance (length units).""" return self._get(SWMMSystemProperties.HEAD_TOL) @property def sys_flow_tolerance(self) -> float: """System flow convergence tolerance.""" return self._get(SWMMSystemProperties.SYS_FLOW_TOL) @property def lat_flow_tolerance(self) -> float: """Lateral flow convergence tolerance.""" return self._get(SWMMSystemProperties.LAT_FLOW_TOL) # --- climate ---
[docs] def get_evap_rate(self) -> float: """Return the current climate-derived evaporation rate. The rate the engine would apply in the absence of any PET prescription, including monthly adjustments (read-only). Intended for caller-side composition with L{LegacySubcatchment.set_api_pet}: read this rate, apply your own adjustment logic, and prescribe the result. @return: Evaporation rate in user units (in/day or mm/day). @rtype: float """ return self._get(SWMMSystemProperties.EVAP_RATE)
[docs] def get_temperature(self) -> float: """Return the current air temperature. @return: Air temperature in user units (deg F or deg C). @rtype: float """ return self._get(SWMMSystemProperties.TEMPERATURE)
[docs] def set_api_temperature(self, value: float) -> None: """Prescribe the air temperature used for snowmelt and temperature-derived evaporation. Overrides the climate data-source value (bypassing monthly adjustments) and keeps derived quantities (saturation vapor pressure) consistent. Persists until cleared with L{clear_api_temperature}. @param value: Air temperature in user units (deg F or deg C). @type value: float @return: None @rtype: None """ self._set(SWMMSystemProperties.API_TEMPERATURE, value)
[docs] def get_api_temperature(self) -> float: """Return the prescribed air temperature. @return: Prescribed temperature in user units, or -999 when no prescription is active. @rtype: float """ return self._get(SWMMSystemProperties.API_TEMPERATURE)
[docs] def clear_api_temperature(self) -> None: """Clear any prescribed air temperature, reverting to climate data. @return: None @rtype: None """ self._set(SWMMSystemProperties.API_TEMPERATURE, -1000.0)
[docs] def get_wind_speed(self) -> float: """Return the current wind speed. @return: Wind speed in user units (mph or km/hr). @rtype: float """ return self._get(SWMMSystemProperties.WIND_SPEED)
[docs] def set_api_wind_speed(self, value: float) -> None: """Prescribe the wind speed used in rain-on-snow melt. Overrides the monthly/climate-file value. Persists until cleared with L{clear_api_wind_speed}. @param value: Wind speed in user units (mph or km/hr), >= 0. @type value: float @return: None @rtype: None """ self._set(SWMMSystemProperties.API_WIND_SPEED, value)
[docs] def get_api_wind_speed(self) -> float: """Return the prescribed wind speed. @return: Prescribed wind speed in user units, or a negative value when no prescription is active. @rtype: float """ return self._get(SWMMSystemProperties.API_WIND_SPEED)
[docs] def clear_api_wind_speed(self) -> None: """Clear any prescribed wind speed, reverting to climate data. @return: None @rtype: None """ self._set(SWMMSystemProperties.API_WIND_SPEED, -1.0)
[docs] def set_api_evap_rate(self, value: float) -> None: """Prescribe the system-wide evaporation rate. Replaces the climate-derived rate (after monthly adjustments) for every consumer — subcatchments, LID units, groundwater, conduits and storage nodes. Per-subcatchment L{LegacySubcatchment.set_api_pet} still takes precedence. Persists until cleared with L{clear_api_evap_rate}. @param value: Evaporation rate in user units (in/day or mm/day), >= 0. @type value: float @return: None @rtype: None """ self._set(SWMMSystemProperties.API_EVAP, value)
[docs] def get_api_evap_rate(self) -> float: """Return the prescribed system-wide evaporation rate. @return: Prescribed rate in user units, or a negative value when no prescription is active. @rtype: float """ return self._get(SWMMSystemProperties.API_EVAP)
[docs] def clear_api_evap_rate(self) -> None: """Clear any prescribed evaporation rate, reverting to climate data. @return: None @rtype: None """ self._set(SWMMSystemProperties.API_EVAP, -1.0)
[docs] def set_evap_dry_only(self, flag: bool) -> None: """Set the evaporation DRY_ONLY option at runtime. @param flag: C{True} suppresses evaporation during rainfall. @type flag: bool @return: None @rtype: None """ self._set(SWMMSystemProperties.EVAP_DRY_ONLY, 1.0 if flag else 0.0)
[docs] def get_evap_dry_only(self) -> bool: """Return the evaporation DRY_ONLY option. @return: C{True} if evaporation is suppressed during rainfall. @rtype: bool """ return bool(self._get(SWMMSystemProperties.EVAP_DRY_ONLY))
# --- continuity errors --- @property def runoff_error(self) -> float: """Runoff continuity error (%).""" return self._get(SWMMSystemProperties.RUNOFF_ERROR) @property def flow_error(self) -> float: """Flow routing continuity error (%).""" return self._get(SWMMSystemProperties.FLOW_ERROR) @property def quality_error(self) -> float: """Quality routing continuity error (%).""" return self._get(SWMMSystemProperties.QUAL_ERROR) # --- mass balance totals (call after solver.end()) --- @property def routing_totals(self) -> Dict[str, float]: """System-level flow routing mass balance totals. Returns a dict with keys: dw_inflow, ww_inflow, gw_inflow, ii_inflow, ex_inflow, flooding, outflow, evap_loss, seep_loss, reacted, init_storage, final_storage, pct_error. Mass balance equation:: total_inflow = dw + ww + gw + ii + ex total_outflow = flooding + outflow + evap_loss + seep_loss storage_change = final_storage - init_storage balance = total_inflow - total_outflow - storage_change """ return self._solver.get_routing_totals() @property def runoff_totals(self) -> Dict[str, float]: """System-level surface runoff mass balance totals. Returns a dict with keys: rainfall, evap, infil, runoff, drains, runon, init_storage, final_storage, init_snow_cover, final_snow_cover, snow_removed, pct_error. """ return self._solver.get_runoff_totals() @property def mass_balance_error(self) -> Tuple[float, float, float]: """Mass balance errors as (runoff_err%, flow_err%, quality_err%).""" return self._solver.get_mass_balance_error() def __repr__(self) -> str: return "LegacySystem()"