diff --git a/python/epaswmm/solver/solver.pxd b/python/epaswmm/solver/solver.pxd index 254479a0..4eeb7dab 100644 --- a/python/epaswmm/solver/solver.pxd +++ b/python/epaswmm/solver/solver.pxd @@ -63,9 +63,15 @@ cdef extern from "swmm5.h": swmm_SUBCATCH_INFIL # Infiltration swmm_SUBCATCH_RUNOFF # Runoff swmm_SUBCATCH_RPTFLAG # Reporting flag + swmm_SUBCATCH_WIDTH # Width + swmm_SUBCATCH_SLOPE # Slope + swmm_SUBCATCH_CURB_LENGTH # Curb length + swmm_SUBCATCH_API_RAINFALL # API rainfall + swmm_SUBCATCH_API_SNOWFALL # API snowfall swmm_SUBCATCH_POLLUTANT_BUILDUP # Pollutant buildup - swmm_SUBCATCH_POLLUTANT_PONDED_CONCENTRATION # Pollutant ponded concentration - swmm_SUBCATCH_POLLUTANT_RUNOFF_CONCENTRATION # Pollutant runoff concentration + swmm_SUBCATCH_EXTERNAL_POLLUTANT_BUILDUP # External pollutant buildup + swmm_SUBCATCH_POLLUTANT_RUNOFF_CONCENTRATION # Pollutant ponded concentration + swmm_SUBCATCH_POLLUTANT_PONDED_CONCENTRATION # Pollutant runoff concentration swmm_SUBCATCH_POLLUTANT_TOTAL_LOAD # Pollutant total load # SWMM Node properties @@ -80,8 +86,11 @@ cdef extern from "swmm5.h": swmm_NODE_INFLOW # Total inflow swmm_NODE_OVERFLOW # Flooding swmm_NODE_RPTFLAG # Reporting flag + swmm_NODE_SURCHARGE_DEPTH # Surcharge depth + swmm_NODE_PONDED_AREA # Ponded area + swmm_NODE_INITIAL_DEPTH # Initial depth swmm_NODE_POLLUTANT_CONCENTRATION # Pollutant concentration - swmm_NODE_POLLUTANT_INFLOW_CONCENTRATION # Pollutant inflow concentration + swmm_NODE_POLLUTANT_LATMASS_FLUX # Pollutant lateral mass flux # SWMM Link properties ctypedef enum swmm_LinkProperty: @@ -100,8 +109,18 @@ cdef extern from "swmm5.h": swmm_LINK_VELOCITY # Velocity swmm_LINK_TOPWIDTH # Top width swmm_LINK_RPTFLAG # Reporting flag + swmm_LINK_OFFSET1 # Inlet offset + swmm_LINK_OFFSET2 # Outlet offset + swmm_LINK_INITIAL_FLOW # Initial flow + swmm_LINK_FLOW_LIMIT # Flow limit + swmm_LINK_INLET_LOSS # Inlet loss + swmm_LINK_OUTLET_LOSS # Outlet loss + swmm_LINK_AVERAGE_LOSS # Average depth + swmm_LINK_SEEPAGE_RATE # Seepage rate + swmm_LINK_HAS_FLAPGATE # Flap gate swmm_LINK_POLLUTANT_CONCENTRATION # Pollutant concentration swmm_LINK_POLLUTANT_LOAD # Pollutant load + swmm_LINK_POLLUTANT_LATMASS_FLUX # Pollutant lateral mass flux # SWMM System properties ctypedef enum swmm_SystemProperty: @@ -125,7 +144,7 @@ cdef extern from "swmm5.h": swmm_IGNORERAINFALL # Flag indicating whether rainfall is ignored. swmm_IGNORERDII # Flag indicating whether RDII is ignored. swmm_IGNORESNOWMELT # Flag indicating whether snowmelt is ignored. - swmm_IGNOREGWATER # Flag indicating whether groundwater is ignored. + swmm_IGNOREGROUNDWATER # Flag indicating whether groundwater is ignored. swmm_IGNOREROUTING # Flag indicating whether routing is ignored. swmm_IGNOREQUALITY # Flag indicating whether water quality is ignored. swmm_RULESTEP # The rule step size. @@ -168,6 +187,7 @@ cdef extern from "swmm5.h": ERR_API_TIME_PERIOD # Invalid time period ERR_API_HOTSTART_FILE_OPEN # Error opening hotstart file ERR_API_HOTSTART_FILE_FORMAT # Invalid hotstart file format + ERR_API_IS_RUNNING # Simulation is already running # SWMM API function return simulation progress ctypedef void (*progress_callback)(double progress); @@ -279,12 +299,27 @@ cdef extern from "swmm5.h": # param: index: object index cdef double swmm_getValue(int property, int index) + + # Retrieves the value of a property for an object of a given type and index + # param: objType: object type + # param: property: property type + # param: index: object index + # param: subIndex: sub-index + cdef double swmm_getValueExpanded(int objType, int property, int index, int subIndex) + # Sets the value of a property for an object of a given type and index # param: property: property type # param: index: object index # param: value: property value cdef int swmm_setValue(int property, int index, double value) + # Sets the value of a property for an object of a given type and index + # param: objType: object type + # param: property: property type + # param: index: object index + # param: value: property value + cdef int swmm_setValueExpanded(int objType, int property, int index, int subindex, double value) + # Retrieves the value of a property for an object of a given type and index # param: property: property type # param: index: object index diff --git a/python/epaswmm/solver/solver.pyi b/python/epaswmm/solver/solver.pyi new file mode 100644 index 00000000..24cd564b --- /dev/null +++ b/python/epaswmm/solver/solver.pyi @@ -0,0 +1,659 @@ +""" +This type stub file was generated by cyright. +""" + +from enum import Enum +from typing import Any, Callable, Tuple + +class SWMMObjects(Enum): + """ + Enumeration of SWMM objects. + + :ivar SUBCATCH: Subcatchment object + :type SUBCATCH: int + :ivar NODE: Node object + :type NODE: int + :ivar LINK: Link object + :type LINK: int + :ivar AQUIFER: Aquifer object + :type AQUIFER: int + :ivar SNOWPACK: Snowpack object + :type SNOWPACK: int + :ivar UNIT_HYDROGRAPH: Unit hydrograph object + :type UNIT_HYDROGRAPH: int + :ivar LID: LID object + :type LID: int + :ivar STREET: Street object + :type STREET: int + :ivar INLET: Inlet object + :type INLET: int + :ivar TRANSECT: Transect object + :type TRANSECT: int + :ivar XSECTION_SHAPE: Cross-section shape object + :type XSECTION_SHAPE: int + :ivar CONTROL_RULE: Control rule object + :type CONTROL_RULE: int + :ivar POLLUTANT: Pollutant object + :type POLLUTANT: int + :ivar LANDUSE: Land use object + :type LANDUSE: int + :ivar CURVE: Curve object + :type CURVE: int + :ivar TIMESERIES: Time series object + :type TIMESERIES: int + :ivar TIME_PATTERN: Time pattern object + :type TIME_PATTERN: int + :ivar SYSTEM: System object + :type SYSTEM: int + """ + RAIN_GAGE = ... + SUBCATCH = ... + NODE = ... + LINK = ... + AQUIFER = ... + SNOWPACK = ... + UNIT_HYDROGRAPH = ... + LID = ... + STREET = ... + INLET = ... + TRANSECT = ... + XSECTION_SHAPE = ... + CONTROL_RULE = ... + POLLUTANT = ... + LANDUSE = ... + CURVE = ... + TIMESERIES = ... + TIME_PATTERN = ... + SYSTEM = ... + + +class SWMMNodeTypes(Enum): + """ + Enumeration of SWMM node types. + + :ivar JUNCTION: Junction node + :type JUNCTION: int + :ivar OUTFALL: Outfall node + :type OUTFALL: int + :ivar STORAGE: Storage node + :type STORAGE: int + :ivar DIVIDER: Divider node + :type DIVIDER: int + """ + JUNCTION = ... + OUTFALL = ... + STORAGE = ... + DIVIDER = ... + + +class SWMMRainGageProperties(Enum): + """ + Enumeration of SWMM raingage properties. + + :ivar GAGE_TOTAL_PRECIPITATION: Total precipitation + :type GAGE_TOTAL_PRECIPITATION: int + :ivar GAGE_SNOW_DEPTH: Snow depth + :type GAGE_SNOW_DEPTH: int + :ivar GAGE_SNOWFALL: Snowfall + :type GAGE_SNOWFALL: int + """ + GAGE_TOTAL_PRECIPITATION = ... + GAGE_RAINFALL = ... + GAGE_SNOWFALL = ... + + +class SWMMSubcatchmentProperties(Enum): + """ + Enumeration of SWMM subcatchment properties. + + :ivar AREA: Area + :type AREA: int + :ivar RAINGAGE: Raingage + :type RAINGAGE: int + :ivar RAINFALL: Rainfall + :type RAINFALL: int + :ivar EVAPORATION: Evaporation + :type EVAPORATION: int + :ivar INFILTRATION: Infiltration + :type INFILTRATION: int + :ivar RUNOFF: Runoff + :type RUNOFF: int + :ivar REPORT_FLAG: Report flag + :type REPORT_FLAG: int + :ivar POLLUTANT_BUILDUP: Pollutant buildup + :type POLLUTANT_BUILDUP: int + :ivar POLLUTANT_PONDED_CONCENTRATION: Pollutant ponded concentration + :type POLLUTANT_PONDED_CONCENTRATION: int + :ivar POLLUTANT_TOTAL_LOAD: Pollutant total load + :type POLLUTANT_TOTAL_LOAD: int + """ + AREA = ... + RAINGAGE = ... + RAINFALL = ... + EVAPORATION = ... + INFILTRATION = ... + RUNOFF = ... + REPORT_FLAG = ... + WIDTH = ... + POLLUTANT_BUILDUP = ... + POLLUTANT_PONDED_CONCENTRATION = ... + POLLUTANT_RUNOFF_CONCENTRATION = ... + + +class SWMMNodeProperties(Enum): + """ + Enumeration of SWMM node properties. + + :ivar TYPE: Node type + :type TYPE: int + :ivar ELEVATION: Elevation + :type ELEVATION: int + :ivar MAX_DEPTH: Maximum depth + :type MAX_DEPTH: int + :ivar DEPTH: Depth + :type DEPTH: int + :ivar HYDRAULIC_HEAD: Hydraulic head + :type HYDRAULIC_HEAD: int + :ivar VOLUME: Volume + :type VOLUME: int + :ivar LATERAL_INFLOW: Lateral inflow + :type LATERAL_INFLOW: int + :ivar TOTAL_INFLOW: Total inflow + :type TOTAL_INFLOW: int + :ivar FLOODING: Flooding + :type FLOODING: int + :ivar REPORT_FLAG: Report flag + :type REPORT_FLAG: int + """ + TYPE = ... + ELEVATION = ... + MAX_DEPTH = ... + DEPTH = ... + HYDRAULIC_HEAD = ... + VOLUME = ... + LATERAL_INFLOW = ... + TOTAL_INFLOW = ... + FLOODING = ... + REPORT_FLAG = ... + POLLUTANT_CONCENTRATION = ... + POLLUTANT_INFLOW_CONCENTRATION = ... + + +class SWMMLinkProperties(Enum): + """ + Enumeration of SWMM link properties. + + :ivar TYPE: Link type + :type TYPE: int + :ivar OFFSET1: Offset 1 + :type OFFSET1: int + :ivar OFFSET2: Offset 2 + :type OFFSET2: int + :ivar DIAMETER: Diameter + :type DIAMETER: int + :ivar LENGTH: Length + :type LENGTH: int + :ivar ROUGHNESS: Roughness + :type ROUGHNESS: int + :ivar INLET_HEIGHT: Inlet height + :type INLET_HEIGHT: int + :ivar OUTLET_HEIGHT: Outlet height + :type OUTLET_HEIGHT: int + :ivar INIT_FLOW: Initial flow + :type INIT_FLOW: int + :ivar FLOW_LIMIT: Flow limit + :type FLOW_LIMIT: int + :ivar REPORT_FLAG: Report flag + :type REPORT_FLAG: int + """ + TYPE = ... + START_NODE = ... + END_NODE = ... + LENGTH = ... + SLOPE = ... + FULL_DEPTH = ... + FULL_FLOW = ... + SETTING = ... + TIME_OPEN = ... + TIME_CLOSED = ... + FLOW = ... + DEPTH = ... + VELOCITY = ... + TOP_WIDTH = ... + REPORT_FLAG = ... + POLLUTANT_CONCENTRATION = ... + POLLUTANT_LOAD = ... + + +class SWMMSystemProperties(Enum): + """ + Enumeration of SWMM system properties. + """ + START_DATE = ... + CURRENT_DATE = ... + ELAPSED_TIME = ... + ROUTING_STEP = ... + MAX_ROUTING_STEP = ... + REPORT_STEP = ... + TOTAL_STEPS = ... + NO_REPORT_FLAG = ... + FLOW_UNITS = ... + END_DATE = ... + REPORT_START_DATE = ... + UNIT_SYSTEM = ... + SURCHARGE_METHOD = ... + ALLOW_PONDING = ... + INTERTIAL_DAMPING = ... + NORMAL_FLOW_LIMITED = ... + SKIP_STEADY_STATE = ... + IGNORE_RAINFALL = ... + IGNORE_RDII = ... + IGNORE_SNOWMELT = ... + IGNORE_GROUNDWATER = ... + IGNORE_ROUTING = ... + IGNORE_QUALITY = ... + RULE_STEP = ... + SWEEP_START = ... + SWEEP_END = ... + MAX_TRIALS = ... + NUM_THREADS = ... + MIN_ROUTE_STEP = ... + LENGTHENING_STEP = ... + START_DRY_DAYS = ... + COURANT_FACTOR = ... + MIN_SURF_AREA = ... + MIN_SLOPE = ... + RUNOFF_ERROR = ... + FLOW_ERROR = ... + QUAL_ERROR = ... + HEAD_TOL = ... + SYS_FLOW_TOL = ... + LAT_FLOW_TOL = ... + + +class SWMMFlowUnits(Enum): + """ + Enumeration of SWMM flow units. + + :ivar CFS: Cubic feet per second + :type CFS: int + :ivar GPM: Gallons per minute + :type GPM: int + :ivar MGD: Million gallons per day + :type MGD: int + :ivar CMS: Cubic meters per second + :type CMS: int + :ivar LPS: Liters per second + :type LPS: int + :ivar MLD: Million liters per day + :type MLD: int + """ + CFS = ... + GPM = ... + MGD = ... + CMS = ... + LPS = ... + MLD = ... + + +class SWMMAPIErrors(Enum): + """ + Enumeration of SWMM API errors. + + :ivar PROJECT_NOT_OPENED: Project not opened + :type PROJECT_NOT_OPENED: int + :ivar SIMULATION_NOT_STARTED: Simulation not started + :type SIMULATION_NOT_STARTED: int + :ivar SIMULATION_NOT_ENDED: Simulation not ended + :type SIMULATION_NOT_ENDED: int + """ + PROJECT_NOT_OPENED = ... + SIMULATION_NOT_STARTED = ... + SIMULATION_NOT_ENDED = ... + OBJECT_TYPE = ... + OBJECT_INDEX = ... + OBJECT_NAME = ... + PROPERTY_TYPE = ... + PROPERTY_VALUE = ... + TIME_PERIOD = ... + HOTSTART_FILE_OPEN = ... + HOTSTART_FILE_FORMAT = ... + + +def run_solver(inp_file: str, rpt_file: str, out_file: str, swmm_progress_callback: Callable[[float], None] = ...) -> int: + """ + Run a SWMM simulation with a progress callback. + + :param inp_file: Input file name + :param rpt_file: Report file name + :param out_file: Output file name + :param progress_callback: Progress callback function + :type progress_callback: callable + :return: Error code (0 if successful) + """ + ... + +def decode_swmm_datetime(swmm_datetime: float) -> Any: + """ + Decode a SWMM datetime into a datetime object. + + :param swmm_datetime: SWMM datetime float value + :type swmm_datetime: float + :return: datetime object + :rtype: datetime + """ + ... + +def encode_swmm_datetime(dt: Any) -> float: + """ + Encode a datetime object into a SWMM datetime float value. + + :param dt: datetime object + :type dt: datetime + :return: SWMM datetime float value + :rtype: float + """ + ... + +def version() -> int: + """ + Get the SWMM version. + + :return: SWMM version + :rtype: str + """ + ... + +def get_error_message(error_code: int) -> str: + """ + Get the error message for a SWMM error code. + + :param error_code: Error code + :type error_code: int + :return: Error message + :rtype: str + """ + ... + +class SolverState(Enum): + """ + An enumeration to represent the state of the solver. + """ + CREATED = ... + OPEN = ... + STARTED = ... + FINISHED = ... + ENDED = ... + REPORTED = ... + CLOSED = ... + + +class CallbackType(Enum): + """ + An enumeration to represent the type of callback. + """ + BEFORE_INITIALIZE = ... + BEFORE_OPEN = ... + AFTER_OPEN = ... + BEFORE_START = ... + AFTER_START = ... + BEFORE_STEP = ... + AFTER_STEP = ... + BEFORE_END = ... + AFTER_END = ... + BEFORE_REPORT = ... + AFTER_REPORT = ... + BEFORE_CLOSE = ... + AFTER_CLOSE = ... + + +class SWMMSolverException(Exception): + """ + Exception class for SWMM output file processing errors. + """ + def __init__(self, message: str) -> None: + """ + Constructor to initialize the exception message. + + :param message: Error message. + :type message: str + """ + ... + + + +class Solver: + """ + A class to represent a SWMM solver. + """ + def __enter__(self): # -> Self@Solver: + """ + Enter method for context manager. + """ + ... + + def __exit__(self, exc_type, exc_value, traceback): # -> None: + """ + Exit method for context manager. + """ + ... + + def __iter__(self): # -> Self@Solver: + """ + Iterator method for the solver. + """ + ... + + def __next__(self): # -> double: + """ + Next method for the solver. + """ + ... + + @property + def start_datetime(self) -> Any: + """ + Get the start date of the simulation. + + :return: Start date + :rtype: datetime + """ + ... + + @start_datetime.setter + def start_datetime(self, sim_start_datetime: Any) -> None: + """ + Initialize the solver. + + :param sim_start_datetime: Start date of the simulation + :return: Error code (0 if successful) + """ + ... + + @property + def end_datetime(self) -> Any: + """ + Get the end date of the simulation. + + :return: End date + :rtype: datetime + """ + ... + + @end_datetime.setter + def end_datetime(self, sim_end_datetime: Any) -> None: + """ + Set the end date of the simulation. + + :param sim_end_datetime: End date of the simulation + :return: Error code (0 if successful) + """ + ... + + @property + def current_datetime(self) -> Any: + """ + Get the current date of the simulation. + + :return: Current date + :rtype: datetime + """ + ... + + def get_object_count(self, object_type: SWMMObjects) -> int: + """ + Get the count of a SWMM object type. + + :param object_type: SWMM object type + :type object_type: SWMMObjects + :return: Object count + :rtype: int + """ + ... + + def get_object_name(self, object_type: SWMMObjects, index: int) -> str: + """ + Get the name of a SWMM object. + + :param object_type: SWMM object type + :type object_type: SWMMObjects + :param index: Object index + :type index: int + :return: Object name + :rtype: str + """ + ... + + def set_value(self, property_type: SWMMObjects, index: int, value: double) -> None: + """ + Set a SWMM system property value. + + :param property_type: System property type + :type property_type: SWMMSystemProperties + :param value: Property value + :type value: double + """ + ... + + def get_value(self, property_type: SWMMObjects, index: int): # -> double: + """ + Get a SWMM system property value. + + :param property_type: System property type + :type property_type: SWMMSystemProperties + :return: Property value + :rtype: double + """ + ... + + @property + def stride_step(self) -> int: + """ + Get the stride step of the simulation. + + :return: Stride step + :rtype: int + """ + ... + + @stride_step.setter + def stride_step(self, value: int): # -> None: + """ + Set the stride time step of the simulation. + + :param value: Stride step in seconds + :type value: int + """ + ... + + @property + def solver_state(self) -> SolverState: + """ + Get the state of the solver. + + :return: Solver state + :rtype: SolverState + """ + ... + + def add_callback(self, callback_type: CallbackType, callback: Callable[[Solver], None]) -> None: + """ + Add a callback to the solver. + + :param callback_type: Type of callback + :type callback_type: CallbackType + :param callback: Callback function + :type callback: callable + """ + ... + + def add_progress_callback(self, callback: Callable[[double], None]) -> None: + """ + Add a progress callback to the solver. + + :param callback: Progress callback function + :type callback: callable + """ + ... + + def initialize(self) -> Any: + """ + Initialize the solver. + + :param inp_file: Input file name + :param rpt_file: Report file name + :param out_file: Output file name + + """ + ... + + def step(self) -> float: + """ + Step a SWMM simulation. + + :return: elapsed_time, current_date + :rtype: Tuple[float, datetime] + """ + ... + + def finalize(self) -> Any: + """ + Finalize the solver. + """ + ... + + def execute(self) -> Any: + """ + Run the solver to completion. + + :return: Error code (0 if successful) + """ + ... + + def use_hotstart(self, hotstart_file: str) -> Any: + """ + Use a hotstart file. + + :param hotstart_file: Hotstart file name + """ + ... + + def save_hotstart(self, hotstart_file: str) -> Any: + """ + Save a hotstart file. + + :param hotstart_file: Hotstart file name + """ + ... + + def get_mass_balance_error(self) -> Tuple[float, float, float]: + """ + Get the mass balance error. + + :return: Mass balance error + :rtype: Tuple[float, float, float] + """ + ... + + + diff --git a/python/epaswmm/solver/solver.pyx b/python/epaswmm/solver/solver.pyx index f881fefb..20840990 100644 --- a/python/epaswmm/solver/solver.pyx +++ b/python/epaswmm/solver/solver.pyx @@ -19,8 +19,6 @@ cimport epaswmm.solver.solver as solver cimport epaswmm.epaswmm as cepaswmm # cython: language_level=3 - - from epaswmm.solver.solver cimport ( PyEval_CallObject, clock_t, @@ -56,7 +54,9 @@ from epaswmm.solver.solver cimport ( swmm_getName, swmm_getIndex, swmm_getValue, + swmm_getValueExpanded, swmm_setValue, + swmm_setValueExpanded, swmm_getSavedValue, swmm_writeLine, swmm_decodeDate, @@ -175,8 +175,22 @@ class SWMMSubcatchmentProperties(Enum): :type RUNOFF: int :ivar REPORT_FLAG: Report flag :type REPORT_FLAG: int + :ivar WIDTH: Width + :type WIDTH: int + :ivar SLOPE: Slope + :type SLOPE: int + :ivar CURB_LENGTH: Curb length + :type CURB_LENGTH: int + :ivar API_RAINFALL: API Rainfall + :type API_RAINFALL: int + :ivar API_SNOWFALL: API Snowfall + :type API_SNOWFALL: int :ivar POLLUTANT_BUILDUP: Pollutant buildup :type POLLUTANT_BUILDUP: int + :ivar EXTERNAL_POLLUTANT_BUILDUP: External pollutant buildup + :type EXTERNAL_POLLUTANT_BUILDUP: int + :ivar POLLUTANT_RUNOFF_CONCENTRATION: Pollutant runoff concentration + :type POLLUTANT_RUNOFF_CONCENTRATION: int :ivar POLLUTANT_PONDED_CONCENTRATION: Pollutant ponded concentration :type POLLUTANT_PONDED_CONCENTRATION: int :ivar POLLUTANT_TOTAL_LOAD: Pollutant total load @@ -189,18 +203,25 @@ class SWMMSubcatchmentProperties(Enum): INFILTRATION = swmm_SubcatchProperty.swmm_SUBCATCH_INFIL RUNOFF = swmm_SubcatchProperty.swmm_SUBCATCH_RUNOFF REPORT_FLAG = swmm_SubcatchProperty.swmm_SUBCATCH_RPTFLAG - POLLUTANT_BUILDUP = swmm_SubcatchProperty.swmm_SUBCATCH_POLLUTANT_BUILDUP # Pollutant buildup - POLLUTANT_PONDED_CONCENTRATION = swmm_SubcatchProperty.swmm_SUBCATCH_POLLUTANT_PONDED_CONCENTRATION # Pollutant ponded concentration - POLLUTANT_RUNOFF_CONCENTRATION = swmm_SubcatchProperty.swmm_SUBCATCH_POLLUTANT_TOTAL_LOAD # Pollutant total load - + WIDTH = swmm_SubcatchProperty.swmm_SUBCATCH_WIDTH + SLOPE = swmm_SubcatchProperty.swmm_SUBCATCH_SLOPE + CURB_LENGTH = swmm_SubcatchProperty.swmm_SUBCATCH_CURB_LENGTH + API_RAINFALL = swmm_SubcatchProperty.swmm_SUBCATCH_API_RAINFALL + API_SNOWFALL = swmm_SubcatchProperty.swmm_SUBCATCH_API_SNOWFALL + POLLUTANT_BUILDUP = swmm_SubcatchProperty.swmm_SUBCATCH_POLLUTANT_BUILDUP + EXTERNAL_POLLUTANT_BUILDUP = swmm_SubcatchProperty.swmm_SUBCATCH_EXTERNAL_POLLUTANT_BUILDUP + POLLUTANT_RUNOFF_CONCENTRATION = swmm_SubcatchProperty.swmm_SUBCATCH_POLLUTANT_RUNOFF_CONCENTRATION + POLLUTANT_PONDED_CONCENTRATION = swmm_SubcatchProperty.swmm_SUBCATCH_POLLUTANT_PONDED_CONCENTRATION + POLLUTANT_TOTAL_LOAD = swmm_SubcatchProperty.swmm_SUBCATCH_POLLUTANT_TOTAL_LOAD + class SWMMNodeProperties(Enum): """ Enumeration of SWMM node properties. :ivar TYPE: Node type :type TYPE: int - :ivar ELEVATION: Elevation - :type ELEVATION: int + :ivar INVERT_ELEVATION: Invert elevation + :type INVERT_ELEVATION: int :ivar MAX_DEPTH: Maximum depth :type MAX_DEPTH: int :ivar DEPTH: Depth @@ -217,9 +238,19 @@ class SWMMNodeProperties(Enum): :type FLOODING: int :ivar REPORT_FLAG: Report flag :type REPORT_FLAG: int + :ivar SURCHARGE_DEPTH: Surcharge depth + :type SURCHARGE_DEPTH: int + :ivar PONDING_AREA: Ponding area + :type PONDING_AREA: int + :ivar INITIAL_DEPTH: Initial depth + :type INITIAL_DEPTH: int + :ivar POLLUTANT_CONCENTRATION: Pollutant concentration + :type POLLUTANT_CONCENTRATION: int + :ivar POLLUTANT_LATERAL_MASS_FLUX: Pollutant lateral mass flux + :type POLLUTANT_LATERAL_MASS_FLUX: int """ TYPE = swmm_NodeProperty.swmm_NODE_TYPE - ELEVATION = swmm_NodeProperty.swmm_NODE_ELEV + INVERT_ELEVATION = swmm_NodeProperty.swmm_NODE_ELEV MAX_DEPTH = swmm_NodeProperty.swmm_NODE_MAXDEPTH DEPTH = swmm_NodeProperty.swmm_NODE_DEPTH HYDRAULIC_HEAD = swmm_NodeProperty.swmm_NODE_HEAD @@ -228,8 +259,11 @@ class SWMMNodeProperties(Enum): TOTAL_INFLOW = swmm_NodeProperty.swmm_NODE_INFLOW FLOODING = swmm_NodeProperty.swmm_NODE_OVERFLOW REPORT_FLAG = swmm_NodeProperty.swmm_NODE_RPTFLAG + SURCHARGE_DEPTH = swmm_NodeProperty.swmm_NODE_SURCHARGE_DEPTH + PONDING_AREA = swmm_NodeProperty.swmm_NODE_PONDED_AREA + INITIAL_DEPTH = swmm_NodeProperty.swmm_NODE_INITIAL_DEPTH POLLUTANT_CONCENTRATION = swmm_NodeProperty.swmm_NODE_POLLUTANT_CONCENTRATION # Pollutant concentration - POLLUTANT_INFLOW_CONCENTRATION = swmm_NodeProperty.swmm_NODE_POLLUTANT_INFLOW_CONCENTRATION # Pollutant inflow concentration + POLLUTANT_LATERAL_MASS_FLUX = swmm_NodeProperty.swmm_NODE_POLLUTANT_LATMASS_FLUX # Pollutant inflow concentration class SWMMLinkProperties(Enum): """ @@ -273,12 +307,125 @@ class SWMMLinkProperties(Enum): VELOCITY = swmm_LinkProperty.swmm_LINK_VELOCITY TOP_WIDTH = swmm_LinkProperty.swmm_LINK_TOPWIDTH REPORT_FLAG = swmm_LinkProperty.swmm_LINK_RPTFLAG + START_NODE_OFFSET = swmm_LinkProperty.swmm_LINK_OFFSET1 + END_NODE_OFFSET = swmm_LinkProperty.swmm_LINK_OFFSET2 + INITIAL_FLOW = swmm_LinkProperty.swmm_LINK_INITIAL_FLOW + FLOW_LIMIT = swmm_LinkProperty.swmm_LINK_FLOW_LIMIT + INLET_LOSS = swmm_LinkProperty.swmm_LINK_INLET_LOSS + OUTLET_LOSS = swmm_LinkProperty.swmm_LINK_OUTLET_LOSS + AVERAGE_LOSS = swmm_LinkProperty.swmm_LINK_AVERAGE_LOSS + SEEPAGE_RATE = swmm_LinkProperty.swmm_LINK_SEEPAGE_RATE + HAS_FLAPGATE = swmm_LinkProperty.swmm_LINK_HAS_FLAPGATE POLLUTANT_CONCENTRATION = swmm_LinkProperty.swmm_LINK_POLLUTANT_CONCENTRATION # Pollutant concentration POLLUTANT_LOAD = swmm_LinkProperty.swmm_LINK_POLLUTANT_LOAD # Pollutant load + POLLUTANT_LATERAL_MASS_FLUX = swmm_LinkProperty.swmm_LINK_POLLUTANT_LATMASS_FLUX # Pollutant lateral mass flux + +class SWMMLinkTypes(Enum): + """ + Enumeration of SWMM link types. + + :ivar CONDUIT: Conduit link + :type CONDUIT: int + :ivar PUMP: Pump link + :type PUMP: int + :ivar ORIFICE: Orifice link + :type ORIFICE: int + :ivar WEIR: Weir link + :type WEIR: int + :ivar OUTLET: Outlet link + :type OUTLET: int + """ + CONDUIT = swmm_LinkType.swmm_CONDUIT + PUMP = swmm_LinkType.swmm_PUMP + ORIFICE = swmm_LinkType.swmm_ORIFICE + WEIR = swmm_LinkType.swmm_WEIR + OUTLET = swmm_LinkType.swmm_OUTLET class SWMMSystemProperties(Enum): """ Enumeration of SWMM system properties. + + :ivar START_DATE: Start date for the simulation + :type START_DATE: int + :ivar CURRENT_DATE: Current date for the simulation + :type CURRENT_DATE: int + :ivar ELAPSED_TIME: Elapsed time for the simulation + :type ELAPSED_TIME: int + :ivar ROUTING_STEP: Routing time step + :type ROUTING_STEP: int + :ivar MAX_ROUTING_STEP: Maximum routing time step + :type MAX_ROUTING_STEP: int + :ivar REPORT_STEP: Report time step + :type REPORT_STEP: int + :ivar TOTAL_STEPS: Total number of steps + :type TOTAL_STEPS: int + :ivar NO_REPORT_FLAG: No report flag + :type NO_REPORT_FLAG: int + :ivar FLOW_UNITS: Flow units + :type FLOW_UNITS: int + :ivar END_DATE: End date for the simulation + :type END_DATE: int + :ivar REPORT_START_DATE: Report start date + :type REPORT_START_DATE: int + :ivar UNIT_SYSTEM: Unit system + :type UNIT_SYSTEM: int + :ivar SURCHARGE_METHOD: Surcharge method + :type SURCHARGE_METHOD: int + :ivar ALLOW_PONDING: Allow ponding + :type ALLOW_PONDING: int + :ivar INTERTIAL_DAMPING: Inertial damping + :type INTERTIAL_DAMPING: int + :ivar NORMAL_FLOW_LIMITED: Normal flow limited + :type NORMAL_FLOW_LIMITED: int + :ivar SKIP_STEADY_STATE: Skip steady state + :type SKIP_STEADY_STATE: int + :ivar IGNORE_RAINFALL: Ignore rainfall + :type IGNORE_RAINFALL: int + :ivar IGNORE_RDII: Ignore RDII + :type IGNORE_RDII: int + :ivar IGNORE_SNOWMELT: Ignore snowmelt + :type IGNORE_SNOWMELT: int + :ivar IGNORE_GROUNDWATER: Ignore groundwater + :type IGNORE_GROUNDWATER: int + :ivar IGNORE_ROUTING: Ignore routing + :type IGNORE_ROUTING: int + :ivar IGNORE_QUALITY: Ignore quality + :type IGNORE_QUALITY: int + :ivar RULE_STEP: Rule step + :type RULE_STEP: int + :ivar SWEEP_START: Sweep start + :type SWEEP_START: int + :ivar SWEEP_END: Sweep end + :type SWEEP_END: int + :ivar MAX_TRIALS: Maximum trials + :type MAX_TRIALS: int + :ivar NUM_THREADS: Number of threads + :type NUM_THREADS: int + :ivar MIN_ROUTE_STEP: Minimum routing step + :type MIN_ROUTE_STEP: int + :ivar LENGTHENING_STEP: Lengthening step + :type LENGTHENING_STEP: int + :ivar START_DRY_DAYS: Start dry days + :type START_DRY_DAYS: int + :ivar COURANT_FACTOR: Courant factor + :type COURANT_FACTOR: int + :ivar MIN_SURF_AREA: Minimum surface area + :type MIN_SURF_AREA: int + :ivar MIN_SLOPE: Minimum slope + :type MIN_SLOPE: int + :ivar RUNOFF_ERROR: Runoff error + :type RUNOFF_ERROR: int + :ivar FLOW_ERROR: Flow error + :type FLOW_ERROR: int + :ivar QUAL_ERROR: Quality error + :type QUAL_ERROR: int + :ivar HEAD_TOL: Head tolerance + :type HEAD_TOL: int + :ivar SYS_FLOW_TOL: System flow tolerance + :type SYS_FLOW_TOL: int + :ivar LAT_FLOW_TOL: Lateral flow tolerance + :type LAT_FLOW_TOL: int + """ START_DATE = swmm_SystemProperty.swmm_STARTDATE CURRENT_DATE = swmm_SystemProperty.swmm_CURRENTDATE @@ -300,7 +447,7 @@ class SWMMSystemProperties(Enum): IGNORE_RAINFALL = swmm_SystemProperty.swmm_IGNORERAINFALL IGNORE_RDII = swmm_SystemProperty.swmm_IGNORERDII IGNORE_SNOWMELT = swmm_SystemProperty.swmm_IGNORESNOWMELT - IGNORE_GROUNDWATER = swmm_SystemProperty.swmm_IGNOREGWATER + IGNORE_GROUNDWATER = swmm_SystemProperty.swmm_IGNOREGROUNDWATER IGNORE_ROUTING = swmm_SystemProperty.swmm_IGNOREROUTING IGNORE_QUALITY = swmm_SystemProperty.swmm_IGNOREQUALITY RULE_STEP = swmm_SystemProperty.swmm_RULESTEP @@ -367,6 +514,7 @@ class SWMMAPIErrors(Enum): TIME_PERIOD = swmm_API_Errors.ERR_API_TIME_PERIOD # Invalid time period HOTSTART_FILE_OPEN = swmm_API_Errors.ERR_API_HOTSTART_FILE_OPEN # Error opening hotstart file HOTSTART_FILE_FORMAT = swmm_API_Errors.ERR_API_HOTSTART_FILE_FORMAT # Invalid hotstart file format + SIMULATION_IS_RUNNING = swmm_API_Errors.ERR_API_IS_RUNNING # Simulation is running cdef void c_wrapper_function(double x): """ @@ -563,6 +711,7 @@ cdef class Solver: cdef list _progress_callbacks cdef clock_t _clock cdef double _total_duration + cdef object _solver_state def __cinit__(self, str inp_file, str rpt_file, str out_file, bint save_results=True): """ @@ -601,7 +750,14 @@ cdef class Solver: CallbackType.AFTER_STEP: [], CallbackType.BEFORE_END: [], CallbackType.AFTER_END: [], + CallbackType.BEFORE_REPORT: [], + CallbackType.AFTER_REPORT: [], + CallbackType.BEFORE_CLOSE: [], + CallbackType.AFTER_CLOSE: [] } + + self._progress_callbacks = [] + self._solver_state = SolverState.CREATED def __enter__(self): @@ -662,7 +818,6 @@ cdef class Solver: self.__validate_error(error_code) - @property def end_datetime(self) -> datetime: """ @@ -686,9 +841,76 @@ cdef class Solver: cdef int error_code = swmm_setValue(SWMMSystemProperties.END_DATE.value, 0, end_date) self.__validate_error(error_code) + + @property + def routing_step(self) -> float: + """ + Get the routing time step of the simulation in seconds. + + :return: Routing time step + :rtype: double + """ + cdef double routing_step = swmm_getValue(SWMMSystemProperties.ROUTING_STEP.value, 0) + return routing_step + + @routing_step.setter + def routing_step(self, value: float) -> None: + """ + Set the routing time step of the simulation in seconds. + + :param value: Routing time step in seconds + :type value: float + """ + cdef int error_code = swmm_setValue(SWMMSystemProperties.ROUTING_STEP.value, 0, value) + self.__validate_error(error_code) + + @property + def reporting_step(self) -> float: + """ + Get the reporting time step of the simulation in seconds. + + :return: Reporting time step + :rtype: double + """ + cdef double reporting_step = swmm_getValue(SWMMSystemProperties.REPORT_STEP.value, 0) + return reporting_step + + @reporting_step.setter + def reporting_step(self, value: float) -> None: + """ + Set the reporting time step of the simulation in seconds. + + :param value: Reporting time step in seconds + :type value: float + """ + cdef int error_code = swmm_setValue(SWMMSystemProperties.REPORT_STEP.value, 0, value) + self.__validate_error(error_code) + + @property + def report_start_datetime(self) -> datetime: + """ + Get the report start date of the simulation. + + :return: Report start date + :rtype: datetime + """ + cdef double report_start_date = swmm_getValue(SWMMSystemProperties.REPORT_START_DATE.value, 0) + return cepaswmm.decode_swmm_datetime(report_start_date) + + @report_start_datetime.setter + def report_start_datetime(self, report_start_datetime: datetime) -> None: + """ + Set the report start date of the simulation. + + :param report_start_datetime: Report start date + :type report_start_datetime: datetime + """ + cdef double report_start_date = cepaswmm.encode_swmm_datetime(report_start_datetime) + cdef int error_code = swmm_setValue(SWMMSystemProperties.REPORT_START_DATE.value, 0, report_start_date) + self.__validate_error(error_code) @property - def current_date(self) -> datetime: + def current_datetime(self) -> datetime: """ Get the current date of the simulation. @@ -696,21 +918,78 @@ cdef class Solver: :rtype: datetime """ cdef double current_date = swmm_getValue(SWMMSystemProperties.CURRENT_DATE.value, 0) + return cepaswmm.decode_swmm_datetime(current_date) - def set_value(self, property_type: SWMMObjects, index: int, value: double) -> None: + def get_object_count(self, object_type: SWMMObjects) -> int: + """ + Get the count of a SWMM object type. + + :param object_type: SWMM object type + :type object_type: SWMMObjects + :return: Object count + :rtype: int + """ + cdef int count = swmm_getCount(object_type.value) + + return count + + def get_object_name(self, object_type: SWMMObjects, index: int) -> str: + """ + Get the name of a SWMM object. + + :param object_type: SWMM object type + :type object_type: SWMMObjects + :param index: Object index + :type index: int + :return: Object name + :rtype: str + """ + cdef char* c_object_name = malloc(1024*sizeof(char)) + cdef int error_code = swmm_getName(object_type.value, index, c_object_name, 1024) + + self.__validate_error(error_code) + + object_name = c_object_name.decode('utf-8') + + free(c_object_name) + + return object_name + + def get_object_index(self, object_type: SWMMObjects, object_name: str) -> int: + """ + Get the index of a SWMM object. + + :param object_type: SWMM object type + :type object_type: SWMMObjects + :param object_name: Object name + :type object_name: str + :return: Object index + :rtype: int + """ + cdef int index = swmm_getIndex(object_type.value, object_name.encode('utf-8')) + + return index + + cpdef void set_value(self, int object_type, int property_type, int index, int sub_index, double value): """ Set a SWMM system property value. - :param property_type: System property type - :type property_type: SWMMSystemProperties - :param value: Property value + :param object_type: SWMM object type (e.g., SWMMObjects.NODE.value) + :type object_type: int + :param property_type: System property type (e.g., SWMMSystemProperties.ELEVATION.value) + :type property_type: int + :param index: Object index (e.g., 0) + :type index: int + :param sub_index: Sub-index (e.g., 0) for properties with sub-indexes. For example pollutant index for POLLUTANT properties. + :type sub_index: int + :param value: Property value (e.g., 10.0) :type value: double """ - cdef int error_code = swmm_setValue(property_type.value, index, value) + cdef int error_code = swmm_setValueExpanded(object_type, property_type, index, sub_index, value) self.__validate_error(error_code) - def get_value(self, property_type: SWMMObjects, index: int): + cpdef double get_value(self, int object_type, int property_type, int index, int sub_index): """ Get a SWMM system property value. @@ -719,7 +998,7 @@ cdef class Solver: :return: Property value :rtype: double """ - cdef double value = swmm_getValue(property_type.value, index) + cdef double value = swmm_getValueExpanded(object_type, property_type, index, sub_index) return value @property @@ -742,6 +1021,16 @@ cdef class Solver: """ pass + @property + def solver_state(self) -> SolverState: + """ + Get the state of the solver. + + :return: Solver state + :rtype: SolverState + """ + return self._solver_state + def add_callback(self, callback_type: CallbackType, callback: Callable[[Solver], None]) -> None: """ Add a callback to the solver. @@ -782,13 +1071,7 @@ cdef class Solver: cdef const char* c_rpt_file = c_rpt_file_bytes cdef const char* c_out_file = c_out_file_bytes - if ( - (self._solver_state != SolverState.CREATED) or - (self._solver_state != SolverState.CLOSED) - ): - raise SWMMSolverException(f'Initialize failed: Solver is not in a valid state: {self._solver_state}') - else: - + if (self._solver_state != SolverState.CREATED or self._solver_state != SolverState.CLOSED): self.__execute_callbacks(CallbackType.BEFORE_INITIALIZE) self.__execute_callbacks(CallbackType.BEFORE_OPEN) error_code = swmm_open(c_inp_file, c_rpt_file, c_out_file) @@ -801,6 +1084,9 @@ cdef class Solver: self.__validate_error(error_code) self._solver_state = SolverState.STARTED self.__execute_callbacks(CallbackType.AFTER_START) + else: + raise SWMMSolverException(f'Initialize failed: Solver is not in a valid state: {self._solver_state}') + self._total_duration = swmm_getValue(SWMMSystemProperties.END_DATE.value, 0) - swmm_getValue(SWMMSystemProperties.START_DATE.value, 0) @@ -808,7 +1094,8 @@ cdef class Solver: """ Step a SWMM simulation. - :return: Error code (0 if successful) + :return: elapsed_time, current_date + :rtype: Tuple[float, datetime] """ cdef double elapsed_time = 0.0 cdef double progress = 0.0 @@ -836,19 +1123,19 @@ cdef class Solver: if self._solver_state == SolverState.OPEN or self._solver_state == SolverState.STARTED or self._solver_state == SolverState.FINISHED: self.__execute_callbacks(CallbackType.BEFORE_END) - error_code = self.swmm_end() + error_code = swmm_end() self.__validate_error(error_code) self._solver_state = SolverState.ENDED self.__execute_callbacks(CallbackType.AFTER_END) self.__execute_callbacks(CallbackType.BEFORE_REPORT) - error_code = self.swmm_report() + error_code = swmm_report() self.__validate_error(error_code) self._solver_state = SolverState.REPORTED self.__execute_callbacks(CallbackType.AFTER_REPORT) self.__execute_callbacks(CallbackType.BEFORE_CLOSE) - error_code = self.swmm_close() + error_code = swmm_close() self.__validate_error(error_code) self._solver_state = SolverState.CLOSED self.__execute_callbacks(CallbackType.AFTER_CLOSE) @@ -869,16 +1156,13 @@ cdef class Solver: cdef const char* c_rpt_file = c_rpt_file_bytes cdef const char* c_out_file = c_out_file_bytes - if ( - (self._solver_state != SolverState.CREATED) or - (self._solver_state != SolverState.CLOSED) - ): - raise SWMMSolverException(f'Solver is not in a valid state: {self._solver_state}') - else: - if len(self.__execute_progress_callbacks) > 0: + if (self._solver_state != SolverState.CREATED or self._solver_state != SolverState.CLOSED): + if len(self._progress_callbacks) > 0: error_code = swmm_run_with_callback(c_inp_file, c_rpt_file, c_out_file, swmm_progress_callback) else: error_code = swmm_run(c_inp_file, c_rpt_file, c_out_file) + else: + raise SWMMSolverException(f'Solver is not in a valid state: {self._solver_state}') cpdef void use_hotstart(self, str hotstart_file): """ diff --git a/python/pyproject.toml b/python/pyproject.toml index e0b0e737..334407f8 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -43,15 +43,20 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: C", + "Programming Language :: Cython", + "Programming Language :: C++", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Hydrology", "Topic :: Scientific/Engineering :: Mathematics", "Topic :: Scientific/Engineering :: Physics", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Scientific/Engineering :: GIS", "Development Status :: 3 - Alpha", ] +[tool.distutils.bdist_wheel] +universal = true [project.urls] # TODO: Change these URLs to the correct ones diff --git a/python/tests/data/solver/site_drainage_example.inp b/python/tests/data/solver/site_drainage_example.inp index 334f7e1f..823b9580 100644 --- a/python/tests/data/solver/site_drainage_example.inp +++ b/python/tests/data/solver/site_drainage_example.inp @@ -166,6 +166,7 @@ S6 Commercial 100 [LOADINGS] ;;Subcatchment Pollutant Buildup ;;-------------- ---------------- ---------- +S1 TSS 100 [BUILDUP] ;;Land Use Pollutant Function Coeff1 Coeff2 Coeff3 Per Unit diff --git a/python/tests/test_swmm_solver.py b/python/tests/test_swmm_solver.py index 844fa3e0..a50bea1c 100644 --- a/python/tests/test_swmm_solver.py +++ b/python/tests/test_swmm_solver.py @@ -20,6 +20,11 @@ def setUp(self): @staticmethod def progress_callback(progress: float) -> None: + """ + Progress callback function for the SWMM solver + :param progress: + :return: + """ assert 0 <= progress <= 1.0 def test_get_swmm_version(self): @@ -49,6 +54,11 @@ def test_swmm_decode_date(self): self.assertEqual(swmm_datetime, datetime(year=2024, month=11, day=16, hour=13, minute=33, second=21)) def test_run_solver(self): + """ + Run the SWMM solver to solve the example input file + + :return: + """ error = solver.run_solver( inp_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE, rpt_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".rpt"), @@ -58,6 +68,11 @@ def test_run_solver(self): self.assertEqual(error, 0, "SWMM solver run successfully.") def test_run_solver_with_progress_callback(self): + """ + Run the SWMM solver to solve the example input file with progress callback + + :return: + """ error = solver.run_solver( inp_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE, rpt_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".rpt"), @@ -68,6 +83,10 @@ def test_run_solver_with_progress_callback(self): self.assertEqual(error, 0, "SWMM solver with callbacks run successfully.") def test_run_solver_invalid_inp_file(self): + """ + Run the SWMM solver with an invalid input file path to test error handling + :return: + """ with self.assertRaises(Exception) as context: error = solver.run_solver( inp_file=example_solver_data.NON_EXISTENT_INPUT_FILE, @@ -76,3 +95,69 @@ def test_run_solver_invalid_inp_file(self): ) self.assertIn('ERROR 303: cannot open input file.', str(context.exception)) + + def test_run_without_context_manager(self): + """ + Run the SWMM solver without a context manager + :return: + """ + + swmm_solver = solver.Solver( + inp_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE, + rpt_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".rpt"), + out_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".out"), + ) + + swmm_solver.execute() + + def test_run_without_context_manager_step_by_step(self): + """ + Run the SWMM solver without a context manager and an invalid input file path to test error handling + :return: + """ + + swmm_solver = solver.Solver( + inp_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE, + rpt_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".rpt"), + out_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".out"), + ) + + swmm_solver.initialize() + + while swmm_solver.solver_state != solver.SolverState.FINISHED: + swmm_solver.step() + + swmm_solver.finalize() + + def test_run_solver_with_context_manager(self): + """ + Run the SWMM solver with an invalid report file path to test error handling + :return: + """ + + with solver.Solver( + inp_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE, + rpt_file=example_solver_data.NON_EXISTENT_INPUT_FILE.replace(".inp", ".rpt"), + out_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".out"), + ) as swmm_solver: + swmm_solver.initialize() + + for t in swmm_solver: + pass + + def test_solver_get_time_attributes(self): + """ + Test the get_start_date function of the SWMM solver + :return: + """ + with solver.Solver( + inp_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE, + rpt_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".rpt"), + out_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".out") + ) as swmm_solver: + + swmm_solver.initialize() + + start_date = swmm_solver.start_datetime + + self.assertEqual(start_date, datetime(year=1998, month=1, day=1)) diff --git a/src/solver/include/swmm5.h b/src/solver/include/swmm5.h index 4d1a4118..46440bb3 100644 --- a/src/solver/include/swmm5.h +++ b/src/solver/include/swmm5.h @@ -94,10 +94,16 @@ typedef enum { swmm_SUBCATCH_INFIL = 204, swmm_SUBCATCH_RUNOFF = 205, swmm_SUBCATCH_RPTFLAG = 206, - swmm_SUBCATCH_POLLUTANT_BUILDUP = 207, - swmm_SUBCATCH_POLLUTANT_PONDED_CONCENTRATION = 208, - swmm_SUBCATCH_POLLUTANT_RUNOFF_CONCENTRATION = 209, - swmm_SUBCATCH_POLLUTANT_TOTAL_LOAD = 210, + swmm_SUBCATCH_WIDTH = 207, + swmm_SUBCATCH_SLOPE = 208, + swmm_SUBCATCH_CURB_LENGTH = 209, + swmm_SUBCATCH_API_RAINFALL = 210, + swmm_SUBCATCH_API_SNOWFALL = 211, + swmm_SUBCATCH_POLLUTANT_BUILDUP = 212, + swmm_SUBCATCH_EXTERNAL_POLLUTANT_BUILDUP = 213, + swmm_SUBCATCH_POLLUTANT_RUNOFF_CONCENTRATION = 214, + swmm_SUBCATCH_POLLUTANT_PONDED_CONCENTRATION = 215, + swmm_SUBCATCH_POLLUTANT_TOTAL_LOAD = 216, } swmm_SubcatchProperty; typedef enum { @@ -111,8 +117,11 @@ typedef enum { swmm_NODE_INFLOW = 307, swmm_NODE_OVERFLOW = 308, swmm_NODE_RPTFLAG = 309, - swmm_NODE_POLLUTANT_CONCENTRATION = 310, - swmm_NODE_POLLUTANT_INFLOW_CONCENTRATION = 311, + swmm_NODE_SURCHARGE_DEPTH = 310, + swmm_NODE_PONDED_AREA = 311, + swmm_NODE_INITIAL_DEPTH = 312, + swmm_NODE_POLLUTANT_CONCENTRATION = 313, + swmm_NODE_POLLUTANT_LATMASS_FLUX = 314, } swmm_NodeProperty; typedef enum { @@ -131,8 +140,18 @@ typedef enum { swmm_LINK_VELOCITY = 412, swmm_LINK_TOPWIDTH = 413, swmm_LINK_RPTFLAG = 414, - swmm_LINK_POLLUTANT_CONCENTRATION = 415, - swmm_LINK_POLLUTANT_LOAD = 416, + swmm_LINK_OFFSET1 = 415, + swmm_LINK_OFFSET2 = 416, + swmm_LINK_INITIAL_FLOW = 417, + swmm_LINK_FLOW_LIMIT = 418, + swmm_LINK_INLET_LOSS = 419, + swmm_LINK_OUTLET_LOSS = 420, + swmm_LINK_AVERAGE_LOSS = 421, + swmm_LINK_SEEPAGE_RATE = 422, + swmm_LINK_HAS_FLAPGATE = 423, + swmm_LINK_POLLUTANT_CONCENTRATION = 424, + swmm_LINK_POLLUTANT_LOAD = 425, + swmm_LINK_POLLUTANT_LATMASS_FLUX = 426, } swmm_LinkProperty; typedef enum { @@ -156,7 +175,7 @@ typedef enum { swmm_IGNORERAINFALL = 17, swmm_IGNORERDII = 18, swmm_IGNORESNOWMELT = 19, - swmm_IGNOREGWATER = 20, + swmm_IGNOREGROUNDWATER = 20, swmm_IGNOREROUTING = 21, swmm_IGNOREQUALITY = 22, swmm_RULESTEP= 23, @@ -176,7 +195,6 @@ typedef enum { swmm_HEADTOL = 37, swmm_SYSFLOWTOL = 38, swmm_LATFLOWTOL = 39, - } swmm_SystemProperty; @@ -204,6 +222,7 @@ typedef enum { ERR_API_TIME_PERIOD = -999909, ERR_API_HOTSTART_FILE_OPEN = -999910, ERR_API_HOTSTART_FILE_FORMAT= -999911, + ERR_API_IS_RUNNING = -999912, } swmm_API_Errors; typedef void (*progress_callback)(double progress); @@ -230,7 +249,9 @@ int DLLEXPORT swmm_getCount(int objType); int DLLEXPORT swmm_getName(int objType, int index, char *name, int size); int DLLEXPORT swmm_getIndex(int objType, const char *name); double DLLEXPORT swmm_getValue(int property, int index); +double DLLEXPORT swmm_getValueExpanded(int objType, int property, int index, int subIndex); int DLLEXPORT swmm_setValue(int property, int index, double value); +int DLLEXPORT swmm_setValueExpanded(int objType, int property, int index, int subIndex, double value); double DLLEXPORT swmm_getSavedValue(int property, int index, int period); void DLLEXPORT swmm_writeLine(const char *line); void DLLEXPORT swmm_decodeDate(double date, int *year, int *month, int *day, diff --git a/src/solver/objects.h b/src/solver/objects.h index 9d3ba2e7..1190284f 100644 --- a/src/solver/objects.h +++ b/src/solver/objects.h @@ -58,6 +58,7 @@ // - Refactored TRptFlags struct. // Build 5.3.0: // - Modified TFile to support specification of time for saving hotstart files. +// - Adding support for API provided pollutant fluxes and inflows. //----------------------------------------------------------------------------- #ifndef OBJECTS_H @@ -392,6 +393,7 @@ typedef struct double slope; // slope (ft/ft) double curbLength; // total curb length (ft) double* initBuildup; // initial pollutant buildup (mass/ft2) + double* apiExtBuildup; // build up flux from API (mass/ft2) TLandFactor* landFactor; // array of land use factors TGroundwater* groundwater; // associated groundwater data MathExpr* gwLatFlowExpr; // user-supplied lateral outflow expression @@ -403,6 +405,8 @@ typedef struct //----------------------------- double lidArea; // area devoted to LIDs (ft2) double rainfall; // current rainfall (ft/sec) + double apiRainfall; // api provided rainfall (ft/sec) + double apiSnowfall; // api provided snowfall (ft/sec) double evapLoss; // current evap losses (ft/sec) double infilLoss; // current infil losses (ft/sec) double runon; // runon from other subcatchments (cfs) @@ -524,11 +528,11 @@ typedef struct double newLatFlow; // current lateral inflow (cfs) double* oldQual; // previous quality state double* newQual; // current quality state + double* apiExtQualMassFlux; // pollutant mass flux from swmm_setValue function (mass/sec) double oldFlowInflow; // previous flow inflow double oldNetInflow; // previous net inflow double qualInflow; // inflow seen for quality routing (cfs) double apiExtInflow; // inflow from swmm_setValue function (cfs) - } TNode; //--------------- @@ -706,6 +710,7 @@ typedef struct char bypassed; // bypass dynwave calc. flag char normalFlow; // normal flow limited flag char inletControl; // culvert inlet control flag + double* apiExtQualMassFlux; // pollutant mass flux from swmm_setValue function (mass/sec) } TLink; //--------------- diff --git a/src/solver/project.c b/src/solver/project.c index 13d3c071..427a5f7e 100644 --- a/src/solver/project.c +++ b/src/solver/project.c @@ -59,6 +59,7 @@ // Build 5.3.0: // - Fixed potential precision loss when calculating TotalDuration. // - Memory allocation and reading options for saving multiple hotstart files +// - Added support for api provided pollutant fluxes //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -1073,8 +1074,8 @@ void createObjects() // --- allocate memory for water quality state variables for (j = 0; j < Nobjects[SUBCATCH]; j++) { - Subcatch[j].initBuildup = - (double *) calloc(Nobjects[POLLUT], sizeof(double)); + Subcatch[j].initBuildup = (double *) calloc(Nobjects[POLLUT], sizeof(double)); + Subcatch[j].apiExtBuildup = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Subcatch[j].oldQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Subcatch[j].newQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Subcatch[j].pondedQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); @@ -1084,6 +1085,7 @@ void createObjects() { Node[j].oldQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Node[j].newQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); + Node[j].apiExtQualMassFlux = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Node[j].extInflow = NULL; Node[j].dwfInflow = NULL; Node[j].rdiiInflow = NULL; @@ -1095,6 +1097,7 @@ void createObjects() Link[j].oldQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Link[j].newQual = (double *) calloc(Nobjects[POLLUT], sizeof(double)); Link[j].totalLoad = (double *) calloc(Nobjects[POLLUT], sizeof(double)); + Link[j].apiExtQualMassFlux = (double *) calloc(Nobjects[POLLUT], sizeof(double)); } // --- allocate memory for land use buildup/washoff functions @@ -1150,6 +1153,7 @@ void createObjects() for (k = 0; k < Nobjects[POLLUT]; k++) { Subcatch[j].initBuildup[k] = 0.0; + Subcatch[j].apiExtBuildup[k] = 0.0; } } @@ -1223,6 +1227,7 @@ void deleteObjects() if ( Subcatch ) for (j = 0; j < Nobjects[SUBCATCH]; j++) { FREE(Subcatch[j].initBuildup); + FREE(Subcatch[j].apiExtBuildup); FREE(Subcatch[j].oldQual); FREE(Subcatch[j].newQual); FREE(Subcatch[j].pondedQual); @@ -1232,12 +1237,14 @@ void deleteObjects() { FREE(Node[j].oldQual); FREE(Node[j].newQual); + FREE(Node[j].apiExtQualMassFlux) } if ( Link ) for (j = 0; j < Nobjects[LINK]; j++) { FREE(Link[j].oldQual); FREE(Link[j].newQual); FREE(Link[j].totalLoad); + FREE(Link[j].apiExtQualMassFlux); // Any inlet assigned to Link[j].inlet is freed in inlet_delete(). } diff --git a/src/solver/qualrout.c b/src/solver/qualrout.c index 9fb0b263..797fd01b 100644 --- a/src/solver/qualrout.c +++ b/src/solver/qualrout.c @@ -24,8 +24,10 @@ // - Support added for flow capture by inlet structures. // - Definition of a dry node/link modified. // Build 5.2.1: -// - Dry non-storage nodes now have quality determined by inflow. +// - Dry non-storage nodes now have quality determined by inflow. // - Wet non-storage nodes with no inflow now have no change in quality. +// Build 5.3.0: +// - Added API support for external pollutant mass fluxes. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -38,7 +40,7 @@ // Constants //----------------------------------------------------------------------------- static const double ZeroVolume = 0.0353147; // 1 liter in ft3 -static const double ZeroDepth = 0.003281; // 1 mm in ft +static const double ZeroDepth = 0.003281; // 1 mm in ft //----------------------------------------------------------------------------- // External functions (declared in funcs.h) @@ -49,48 +51,52 @@ static const double ZeroDepth = 0.003281; // 1 mm in ft //----------------------------------------------------------------------------- // Function declarations //----------------------------------------------------------------------------- -static void findLinkMassFlow(int i, double tStep); -static void findNodeQual(int j); -static void findLinkQual(int i, double tStep); -static void findSFLinkQual(int i, double qSeep, double fEvap, double tStep); -static void findStorageQual(int j, double tStep); -static void updateHRT(int j, double v, double q, double tStep); +static void findLinkMassFlow(int i, double tStep); +static void findNodeQual(int j); +static void findLinkQual(int i, double tStep); +static void findSFLinkQual(int i, double qSeep, double fEvap, double tStep); +static void findStorageQual(int j, double tStep); +static void updateHRT(int j, double v, double q, double tStep); static double getReactedQual(int p, double c, double v1, double tStep); static double getMixedQual(double c, double v1, double wIn, double qIn, - double tStep); + double tStep); //============================================================================= -void qualrout_init() +void qualrout_init() // // Input: none // Output: none // Purpose: initializes water quality concentrations in all nodes and links. // { - int i, p, isWet; - double c; + int i, p, isWet; + double c; for (i = 0; i < Nobjects[NODE]; i++) { - isWet = ( Node[i].newDepth > ZeroDepth ); + isWet = (Node[i].newDepth > ZeroDepth); for (p = 0; p < Nobjects[POLLUT]; p++) { c = 0.0; - if ( isWet ) c = Pollut[p].initConcen; + if (isWet) + c = Pollut[p].initConcen; Node[i].oldQual[p] = c; Node[i].newQual[p] = c; + Node[i].apiExtQualMassFlux[p] = 0.0; } } for (i = 0; i < Nobjects[LINK]; i++) { - isWet = ( Link[i].newDepth > ZeroDepth ); + isWet = (Link[i].newDepth > ZeroDepth); for (p = 0; p < Nobjects[POLLUT]; p++) { c = 0.0; - if ( isWet ) c = Pollut[p].initConcen; + if (isWet) + c = Pollut[p].initConcen; Link[i].oldQual[p] = c; Link[i].newQual[p] = c; + Link[i].apiExtQualMassFlux[p] = 0.0; } } } @@ -105,40 +111,45 @@ void qualrout_execute(double tStep) // network over the current time step. // { - int i, j; + int i, j; double qIn, vAvg; // --- find mass flow each link contributes to its downstream node - for ( i = 0; i < Nobjects[LINK]; i++ ) findLinkMassFlow(i, tStep); + for (i = 0; i < Nobjects[LINK]; i++) + findLinkMassFlow(i, tStep); - // --- find new water quality concentration at each node + // --- find new water quality concentration at each node for (j = 0; j < Nobjects[NODE]; j++) - { + { // --- get node inflow and average volume Node[j].qualInflow = Node[j].inflow; qIn = Node[j].qualInflow; vAvg = (Node[j].oldVolume + Node[j].newVolume) / 2.0; - + // --- save inflow concentrations if treatment applied - if ( Node[j].treatment ) + if (Node[j].treatment) { - if ( qIn < ZERO ) qIn = 0.0; + if (qIn < ZERO) + qIn = 0.0; treatmnt_setInflow(qIn, Node[j].newQual); } - - // --- find new quality at the node - if ( Node[j].type == STORAGE || Node[j].oldVolume > ZeroVolume ) + + // --- find new quality at the node + if (Node[j].type == STORAGE || Node[j].oldVolume > ZeroVolume) { findStorageQual(j, tStep); } - else findNodeQual(j); + else + findNodeQual(j); // --- apply treatment to new quality values - if ( Node[j].treatment ) treatmnt_treat(j, qIn, vAvg, tStep); + if (Node[j].treatment) + treatmnt_treat(j, qIn, vAvg, tStep); } // --- find new water quality in each link - for ( i = 0; i < Nobjects[LINK]; i++ ) findLinkQual(i, tStep); + for (i = 0; i < Nobjects[LINK]; i++) + findLinkQual(i, tStep); } //============================================================================= @@ -157,7 +168,8 @@ double getMixedQual(double c, double v1, double wIn, double qIn, double tStep) double vIn, cIn, cMax; // --- if no inflow then reactor concentration is unchanged - if ( qIn <= ZERO ) return c; + if (qIn <= ZERO) + return c; // --- compute concentration of any inflow vIn = qIn * tStep; @@ -167,13 +179,12 @@ double getMixedQual(double c, double v1, double wIn, double qIn, double tStep) cMax = MAX(c, cIn); // --- mix inflow with current reactor contents - c = (c*v1 + wIn*tStep) / (v1 + vIn); + c = (c * v1 + wIn * tStep) / (v1 + vIn); c = MIN(c, cMax); c = MAX(c, 0.0); return c; } - //============================================================================= void findLinkMassFlow(int i, double tStep) @@ -188,7 +199,7 @@ void findLinkMassFlow(int i, double tStep) // contributions from runoff and other external inflows from // calculations made in routing_execute(). { - int j, p; + int j, p; double qLink, w; // --- find inflow to downstream node @@ -196,7 +207,8 @@ void findLinkMassFlow(int i, double tStep) // --- identify index of downstream node j = Link[i].node2; - if ( qLink < 0.0 ) j = Link[i].node1; + if (qLink < 0.0) + j = Link[i].node1; // --- flow rate into downstream node (adjusted for inlet capture) qLink = fabs(qLink); @@ -225,26 +237,35 @@ void findNodeQual(int j) // Purpose: finds new quality in a node with no storage volume. // { - int p; - double qNode; + int p; + double qNode, cIn = 0.0, cOut = 0.0; // --- if there is flow into node then concen. = mass inflow/node flow qNode = Node[j].qualInflow; - if ( qNode > ZERO ) + if (qNode > ZERO) { for (p = 0; p < Nobjects[POLLUT]; p++) { - Node[j].newQual[p] /= qNode; + cIn = Node[j].newQual[p] / qNode; + + // --- Calculate bounded externally provided api pollutant flux and update mass balance + // --- Positive fluxes are added in the addExternalInflows function in routing.c (This really needs to be refactored for consistency) + cOut = min(cIn, max(0.0, -Node[j].apiExtQualMassFlux[p] / qNode)); + cIn -= cOut; + massbal_addOutflowQual(p, cOut * qNode, FALSE); + + Node[j].newQual[p] = cIn; } } // --- otherwise concen. remains the same - else for (p = 0; p < Nobjects[POLLUT]; p++) + else { - if (Node[j].newDepth > ZeroDepth) - Node[j].newQual[p] = Node[j].oldQual[p]; - else - Node[j].newQual[p] = 0.0; + for (p = 0; p < Nobjects[POLLUT]; p++) + { + if (Node[j].newDepth > ZeroDepth) + Node[j].newQual[p] = Node[j].oldQual[p]; + } } } @@ -258,28 +279,30 @@ void findLinkQual(int i, double tStep) // Purpose: finds new quality in a link at end of the current time step. // { - int j, // upstream node index - k, // conduit index - p; // pollutant index - double wIn, // pollutant mass inflow rate (mass/sec) - qIn, // inflow rate (cfs) - qSeep, // rate of seepage loss (cfs) - v1, // link volume at start of time step (ft3) - v2, // link volume at end of time step (ft3) - c1, // current concentration within link (mass/ft3) - c2, // new concentration within link (mass/ft3) - vEvap, // volume lost to evaporation (ft3) - vLosses, // evap. + seepage volume loss (ft3) - fEvap, // evaporation concentration factor - barrels; // number of barrels in conduit + int j, // upstream node index + k, // conduit index + p; // pollutant index + double wIn, // pollutant mass inflow rate (mass/sec) + qIn, // inflow rate (cfs) + qSeep, // rate of seepage loss (cfs) + v1, // link volume at start of time step (ft3) + v2, // link volume at end of time step (ft3) + c1, // current concentration within link (mass/ft3) + c2, // new concentration within link (mass/ft3) + vEvap, // volume lost to evaporation (ft3) + vLosses, // evap. + seepage volume loss (ft3) + fEvap, // evaporation concentration factor + barrels, // number of barrels in conduit + cOut = 0.0; // outflow concentration // --- identify index of upstream node j = Link[i].node1; - if ( Link[i].newFlow < 0.0 ) j = Link[i].node2; + if (Link[i].newFlow < 0.0) + j = Link[i].node2; // --- link quality is that of upstream node when // link is not a conduit or is a dummy link - if ( Link[i].type != CONDUIT || Link[i].xsect.type == DUMMY ) + if (Link[i].type != CONDUIT || Link[i].xsect.type == DUMMY) { for (p = 0; p < Nobjects[POLLUT]; p++) { @@ -291,22 +314,23 @@ void findLinkQual(int i, double tStep) // --- get flow rates and evaporation loss k = Link[i].subIndex; barrels = Conduit[k].barrels; - qIn = fabs(Conduit[k].q1) * barrels; + qIn = fabs(Conduit[k].q1) * barrels; qSeep = Conduit[k].seepLossRate * barrels; vEvap = Conduit[k].evapLossRate * barrels * tStep; // --- get starting and ending volumes v1 = Link[i].oldVolume; v2 = Link[i].newVolume; - vLosses = qSeep*tStep + vEvap; + vLosses = qSeep * tStep + vEvap; // --- compute factor by which concentrations are increased due to - // evaporation loss + // evaporation loss fEvap = 1.0; - if ( vEvap > 0.0 && v1 > ZeroVolume ) fEvap += vEvap / v1; + if (vEvap > 0.0 && v1 > ZeroVolume) + fEvap += vEvap / v1; // --- Steady Flow routing requires special treatment - if ( RouteModel == SF ) + if (RouteModel == SF) { findSFLinkQual(i, qSeep, fEvap, tStep); return; @@ -315,9 +339,9 @@ void findLinkQual(int i, double tStep) // --- adjust inflow to compensate for volume change under Dynamic // Wave routing (which produces just a single (out)flow rate // for a conduit) - if ( RouteModel == DW ) + if (RouteModel == DW) { - qIn = qIn + (v2 + vLosses - v1) / tStep; + qIn = qIn + (v2 + vLosses - v1) / tStep; qIn = MAX(qIn, 0.0); } @@ -328,7 +352,7 @@ void findLinkQual(int i, double tStep) c1 = Link[i].oldQual[p]; // --- update mass balance accounting for seepage loss - massbal_addSeepageLoss(p, qSeep*c1); + massbal_addSeepageLoss(p, qSeep * c1); // --- increase concen. by evaporation factor c1 *= fEvap; @@ -337,16 +361,43 @@ void findLinkQual(int i, double tStep) c2 = getReactedQual(p, c1, v1, tStep); // --- mix resulting contents with inflow from upstream node - wIn = Node[j].newQual[p]*qIn; + wIn = Node[j].newQual[p] * qIn; c2 = getMixedQual(c2, v1, wIn, qIn, tStep); // --- set concen. to zero if remaining volume is negligible - if ( v2 < ZeroVolume || Link[i].newDepth <= ZeroDepth) + if (v2 < ZeroVolume || Link[i].newDepth <= ZeroDepth) { massbal_addToFinalStorage(p, c2 * v2); c2 = 0.0; } + // --- Calculate bounded externally provided api pollutant flux and update mass balance + cOut = Link[i].apiExtQualMassFlux[p]; + + if (cOut < 0.0) + { + cOut = -cOut * tStep / (v1 + qIn * tStep); + cOut = min(c2, max(0.0, cOut)); + c2 -= cOut; + + cOut = cOut * (v1 + qIn * tStep); + Link[i].totalLoad[p] -= cOut; + + cOut = cOut/ tStep; + massbal_addOutflowQual(p, cOut, FALSE); + } + else + { + cOut = cOut * tStep / (v1 + qIn * tStep); + c2 += cOut; + + cOut = cOut * (v1 + qIn * tStep); + Link[i].totalLoad[p] += cOut; + + cOut = cOut/ tStep; + massbal_addInflowQual(EXTERNAL_INFLOW, p, cOut); + } + // --- assign new concen. to link Link[i].newQual[p] = c2; } @@ -354,7 +405,7 @@ void findLinkQual(int i, double tStep) //============================================================================= -void findSFLinkQual(int i, double qSeep, double fEvap, double tStep) +void findSFLinkQual(int i, double qSeep, double fEvap, double tStep) // // Input: i = link index // tStep = routing time step (sec) @@ -364,9 +415,9 @@ void findSFLinkQual(int i, double qSeep, double fEvap, double tStep) // { int j = Link[i].node1; - int p; + int p, k; double c1, c2; - double lossRate; + double lossRate, cOut, barrels, qIn, v1; // --- examine each pollutant for (p = 0; p < Nobjects[POLLUT]; p++) @@ -375,52 +426,87 @@ void findSFLinkQual(int i, double qSeep, double fEvap, double tStep) c1 = Node[j].newQual[p]; // --- update mass balance accounting for seepage loss - massbal_addSeepageLoss(p, qSeep*c1); + massbal_addSeepageLoss(p, qSeep * c1); // --- increase concen. by evaporation factor c1 *= fEvap; // --- apply first-order decay over travel time c2 = c1; - if ( Pollut[p].kDecay > 0.0 ) + if (Pollut[p].kDecay > 0.0) { c2 = c1 * exp(-Pollut[p].kDecay * tStep); c2 = MAX(0.0, c2); lossRate = (c1 - c2) * Link[i].newFlow; massbal_addReactedMass(p, lossRate); } + + // --- Calculate bounded externally provided api pollutant flux and update mass balance + + k = Link[i].subIndex; + barrels = Conduit[k].barrels; + qIn = fabs(Conduit[k].q1) * barrels; + v1 = Link[i].oldVolume; + + cOut = Link[j].apiExtQualMassFlux[p]; + + if (cOut < 0.0) + { + cOut = -cOut * tStep / (v1 + qIn * tStep); + cOut = min(c2, max(0.0, cOut)); + c2 -= cOut; + + cOut = cOut * (v1 + qIn * tStep) ; + Link[j].totalLoad[p] -= cOut; + + cOut = cOut/ tStep; + massbal_addOutflowQual(p, cOut, FALSE); + } + else + { + cOut = cOut * tStep / (v1 + qIn * tStep); + c2 += cOut; + + cOut = cOut * (v1 + qIn * tStep); + Link[j].totalLoad[p] += cOut; + + cOut = cOut/ tStep; + massbal_addInflowQual(EXTERNAL_INFLOW, p, cOut); + } + Link[i].newQual[p] = c2; } } //============================================================================= -void findStorageQual(int j, double tStep) +void findStorageQual(int j, double tStep) // // Input: j = node index // tStep = routing time step (sec) // Output: none // Purpose: finds new quality in a node with storage volume. -// +// { - int p, // pollutant index - k; // storage unit index - double qIn, // inflow rate (cfs) - wIn, // pollutant mass inflow rate (mass) - v1, // volume at start of time step (ft3) - c1, // initial pollutant concentration (mass/ft3) - c2, // final pollutant concentration (mass/ft3) - qExfil = 0.0, // exfiltration rate from storage unit (cfs) - vEvap = 0.0, // evaporation loss from storage unit (ft3) - fEvap = 1.0; // evaporation concentration factor + int p, // pollutant index + k; // storage unit index + double qIn, // inflow rate (cfs) + wIn, // pollutant mass inflow rate (mass) + v1, // volume at start of time step (ft3) + c1, // initial pollutant concentration (mass/ft3) + c2, // final pollutant concentration (mass/ft3) + qExfil = 0.0, // exfiltration rate from storage unit (cfs) + vEvap = 0.0, // evaporation loss from storage unit (ft3) + fEvap = 1.0, // evaporation concentration factor + cOut = 0.0; // outflow concentration // --- get inflow rate & initial volume qIn = Node[j].qualInflow; v1 = Node[j].oldVolume; // -- for storage nodes - if ( Node[j].type == STORAGE ) - { + if (Node[j].type == STORAGE) + { // --- update hydraulic residence time // (HRT can be used in treatment functions) updateHRT(j, Node[j].oldVolume, qIn, tStep); @@ -433,24 +519,25 @@ void findStorageQual(int j, double tStep) // --- compute factor by which concentrations are increased due to // evaporation loss (avoiding huge factors as storage unit // dries out completely) - if ( vEvap > 0.0 && v1 > ZeroVolume ) fEvap += vEvap / v1; + if (vEvap > 0.0 && v1 > ZeroVolume) + fEvap += vEvap / v1; } // --- for each pollutant for (p = 0; p < Nobjects[POLLUT]; p++) { - // --- start with concen. at start of time step + // --- start with concen. at start of time step c1 = Node[j].oldQual[p]; // --- update mass balance accounting for exfiltration loss - massbal_addSeepageLoss(p, qExfil*c1); + massbal_addSeepageLoss(p, qExfil * c1); // --- increase concen. by evaporation factor c1 *= fEvap; // --- apply first order reaction only if no separate treatment function - if ( Node[j].treatment == NULL || - Node[j].treatment[p].equation == NULL ) + if (Node[j].treatment == NULL || + Node[j].treatment[p].equation == NULL) { c1 = getReactedQual(p, c1, v1, tStep); } @@ -462,11 +549,21 @@ void findStorageQual(int j, double tStep) // --- set concen. to zero if remaining volume & inflow is negligible if ((Node[j].newVolume <= ZeroVolume || - Node[j].newDepth <= ZeroDepth) && qIn <= ZERO) + Node[j].newDepth <= ZeroDepth) && + qIn <= ZERO) { massbal_addToFinalStorage(p, c2 * Node[j].newVolume); c2 = 0.0; } + else + { + // --- Calculate bounded externally provided api pollutant flux and update mass balance + // --- Positive fluxes are added in the addExternalInflow function in routing.c + cOut = -Node[j].apiExtQualMassFlux[p] * tStep / (v1 + qIn * tStep); + cOut = min(c2, max(0.0, cOut)); + c2 -= cOut; + massbal_addOutflowQual(p, cOut * (v1 + qIn * tStep) / tStep, FALSE); + } // --- assign new concen. to node Node[j].newQual[p] = c2; @@ -482,14 +579,16 @@ void updateHRT(int j, double v, double q, double tStep) // q = inflow rate (cfs) // tStep = time step (sec) // Output: none -// Purpose: updates hydraulic residence time (i.e., water age) at a +// Purpose: updates hydraulic residence time (i.e., water age) at a // storage node. // { - int k = Node[j].subIndex; + int k = Node[j].subIndex; double hrt = Storage[k].hrt; - if ( v < ZERO ) hrt = 0.0; - else hrt = (hrt + tStep) * v / (v + q*tStep); + if (v < ZERO) + hrt = 0.0; + else + hrt = (hrt + tStep) * v / (v + q * tStep); Storage[k].hrt = MAX(hrt, 0.0); } @@ -509,11 +608,11 @@ double getReactedQual(int p, double c, double v1, double tStep) double c2, lossRate; double kDecay = Pollut[p].kDecay; - if ( kDecay == 0.0 ) return c; + if (kDecay == 0.0) + return c; c2 = c * (1.0 - kDecay * tStep); c2 = MAX(0.0, c2); lossRate = (c - c2) * v1 / tStep; massbal_addReactedMass(p, lossRate); return c2; } - \ No newline at end of file diff --git a/src/solver/routing.c b/src/solver/routing.c index 6d7144fb..bf93bd6b 100644 --- a/src/solver/routing.c +++ b/src/solver/routing.c @@ -494,6 +494,18 @@ void addExternalInflows(DateTime currentDate) } inflow = inflow->next; } + + // --- add api mass fluxes to node's inflow + for (p = 0; p < Nobjects[POLLUT]; p++) + { + w = Node[j].apiExtQualMassFlux[p]; + + if (w > 0.0) + { + Node[j].newQual[p] += w; + massbal_addInflowQual(EXTERNAL_INFLOW, p, w); + } + } } } diff --git a/src/solver/subcatch.c b/src/solver/subcatch.c index 4d94bae0..b3030b2c 100644 --- a/src/solver/subcatch.c +++ b/src/solver/subcatch.c @@ -39,6 +39,7 @@ // - Only pervious area depression storage receives monthly adjustment. // Build 5.3.0: // - Modified to use global constants defined in consts.h. +// - Add api provided rainfall and snowfall to subcatchment's rainfall. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -435,6 +436,9 @@ void subcatch_initState(int j) Subcatch[j].runon = 0.0; Subcatch[j].evapLoss = 0.0; Subcatch[j].infilLoss = 0.0; + Subcatch[j].apiRainfall = 0.0; + Subcatch[j].apiSnowfall = 0.0; + // --- initialize state of infiltration, groundwater, & snow pack objects if ( Subcatch[j].infil == j ) infil_initState(j); @@ -779,6 +783,9 @@ void getNetPrecip(int j, double* netPrecip, double tStep) gage_getPrecip(k, &rainfall, &snowfall); } + rainfall += Subcatch[j].apiRainfall; + snowfall += Subcatch[j].apiSnowfall; + // --- assign total precip. rate to subcatch's rainfall property Subcatch[j].rainfall = rainfall + snowfall; diff --git a/src/solver/surfqual.c b/src/solver/surfqual.c index 4b1a2f7d..e50d8b48 100644 --- a/src/solver/surfqual.c +++ b/src/solver/surfqual.c @@ -77,6 +77,7 @@ void surfqual_initState(int j) Subcatch[j].oldQual[p] = 0.0; Subcatch[j].newQual[p] = 0.0; Subcatch[j].pondedQual[p] = 0.0; + Subcatch[j].apiExtBuildup[p] = 0.0; } // --- initialize pollutant buildup @@ -125,6 +126,10 @@ void surfqual_getBuildup(int j, double tStep) newBuildup = landuse_getBuildup(i, p, area, curb, oldBuildup, tStep); newBuildup = MAX(newBuildup, oldBuildup); + + //--- add bounded building from external API + newBuildup = max(0.0, newBuildup + Subcatch[j].apiExtBuildup[p] * area); + Subcatch[j].landFactor[i].buildup[p] = newBuildup; massbal_updateLoadingTotals(BUILDUP_LOAD, p, (newBuildup - oldBuildup)); diff --git a/src/solver/swmm5.c b/src/solver/swmm5.c index 72ed6910..86bb41d0 100644 --- a/src/solver/swmm5.c +++ b/src/solver/swmm5.c @@ -17,7 +17,7 @@ // ============== // Build 5.1.008: // - Support added for the MinGW compiler. -// - Reporting of project options moved to swmm_start. +// - Reporting of project options moved to swmm_start. // - Hot start file now read before routing system opened. // - Final routing step adjusted so that total duration not exceeded. // Build 5.1.011: @@ -52,30 +52,30 @@ // --- define WINDOWS #undef WINDOWS #ifdef _WIN32 - #define WINDOWS +#define WINDOWS #endif #ifdef __WIN32__ - #define WINDOWS +#define WINDOWS #endif // --- define EXH (MS Windows exception handling) -#undef EXH // indicates if exception handling included +#undef EXH // indicates if exception handling included #ifdef WINDOWS - #ifdef _MSC_VER - #define EXH - #endif +#ifdef _MSC_VER +#define EXH +#endif #endif // --- include Windows & exception handling headers #ifdef WINDOWS - #include - #include - #include +#include +#include +#include #else - #include +#include #endif #ifdef EXH - #include +#include #endif #include @@ -85,54 +85,62 @@ #include #include +// Protect against lack of compiler support for OpenMP +#if defined(_OPENMP) +#include +#else +int omp_get_max_threads(void) { return 1; } +#endif + //----------------------------------------------------------------------------- // SWMM's header files // // Note: the directives listed below are also contained in headers.h which // is included at the start of most of SWMM's other code modules. //----------------------------------------------------------------------------- -#include "macros.h" // macros used throughout SWMM -#include "objects.h" // definitions of SWMM's data objects -#define EXTERN // defined as 'extern' in headers.h -#include "globals.h" // declaration of all global variables -#include "funcs.h" // declaration of all global functions -#include "error.h" // error message codes -#include "text.h" // listing of all text strings +#include "macros.h" // macros used throughout SWMM +#include "objects.h" // definitions of SWMM's data objects +#define EXTERN // defined as 'extern' in headers.h +#include "globals.h" // declaration of all global variables +#include "funcs.h" // declaration of all global functions +#include "error.h" // error message codes +#include "text.h" // listing of all text strings -#include "swmm5.h" // declaration of SWMM's API functions +#include "swmm5.h" // declaration of SWMM's API functions -#define MAX_EXCEPTIONS 100 // max. number of exceptions handled +#define MAX_EXCEPTIONS 100 // max. number of exceptions handled //----------------------------------------------------------------------------- // Unit conversion factors //----------------------------------------------------------------------------- -const double Ucf[10][2] = - {// US SI - {43200.0, 1097280.0 }, // RAINFALL (in/hr, mm/hr --> ft/sec) - {12.0, 304.8 }, // RAINDEPTH (in, mm --> ft) - {1036800.0, 26334720.0}, // EVAPRATE (in/day, mm/day --> ft/sec) - {1.0, 0.3048 }, // LENGTH (ft, m --> ft) - {2.2956e-5, 0.92903e-5}, // LANDAREA (ac, ha --> ft2) - {1.0, 0.02832 }, // VOLUME (ft3, m3 --> ft3) - {1.0, 1.608 }, // WINDSPEED (mph, km/hr --> mph) - {1.0, 1.8 }, // TEMPERATURE (deg F, deg C --> deg F) - {2.203e-6, 1.0e-6 }, // MASS (lb, kg --> mg) - {43560.0, 3048.0 } // GWFLOW (cfs/ac, cms/ha --> ft/sec) - }; -const double Qcf[6] = // Flow Conversion Factors: - {1.0, 448.831, 0.64632, // cfs, gpm, mgd --> cfs - 0.02832, 28.317, 2.4466 }; // cms, lps, mld --> cfs +const double Ucf[10][2] = + { + // US SI + {43200.0, 1097280.0}, // RAINFALL (in/hr, mm/hr --> ft/sec) + {12.0, 304.8}, // RAINDEPTH (in, mm --> ft) + {1036800.0, 26334720.0}, // EVAPRATE (in/day, mm/day --> ft/sec) + {1.0, 0.3048}, // LENGTH (ft, m --> ft) + {2.2956e-5, 0.92903e-5}, // LANDAREA (ac, ha --> ft2) + {1.0, 0.02832}, // VOLUME (ft3, m3 --> ft3) + {1.0, 1.608}, // WINDSPEED (mph, km/hr --> mph) + {1.0, 1.8}, // TEMPERATURE (deg F, deg C --> deg F) + {2.203e-6, 1.0e-6}, // MASS (lb, kg --> mg) + {43560.0, 3048.0} // GWFLOW (cfs/ac, cms/ha --> ft/sec) +}; +const double Qcf[6] = // Flow Conversion Factors: + {1.0, 448.831, 0.64632, // cfs, gpm, mgd --> cfs + 0.02832, 28.317, 2.4466}; // cms, lps, mld --> cfs //----------------------------------------------------------------------------- // Shared variables //----------------------------------------------------------------------------- -static int IsOpenFlag; // TRUE if a project has been opened -static int IsStartedFlag; // TRUE if a simulation has been started -static int SaveResultsFlag; // TRUE if output to be saved to binary file -static int ExceptionCount; // number of exceptions handled -static int DoRunoff; // TRUE if runoff is computed -static int DoRouting; // TRUE if flow routing is computed -static double RoutingDuration; // duration of a set of routing steps (msecs) +static int IsOpenFlag; // TRUE if a project has been opened +static int IsStartedFlag; // TRUE if a simulation has been started +static int SaveResultsFlag; // TRUE if output to be saved to binary file +static int ExceptionCount; // number of exceptions handled +static int DoRunoff; // TRUE if runoff is computed +static int DoRouting; // TRUE if flow routing is computed +static double RoutingDuration; // duration of a set of routing steps (msecs) //----------------------------------------------------------------------------- // External API functions (prototyped in swmm5.h) @@ -162,33 +170,37 @@ static double RoutingDuration; // duration of a set of routing steps (msecs //----------------------------------------------------------------------------- // Local functions //----------------------------------------------------------------------------- -static void execRouting(void); -static void saveResults(void); +static void execRouting(void); +static void saveResults(void); static double getGageValue(int index, int property); -static double getSubcatchValue(int index, int property); -static double getNodeValue(int index, int property); -static double getLinkValue(int index, int property); +static double getSubcatchValue(int property, int index, int subIndex); +static double getNodeValue(int property, int index, int subIndex); +static double getLinkValue(int property, int index, int subIndex); static double getSavedDate(int period); -static double getSavedSubcatchValue(int index, int property, int period); -static double getSavedNodeValue(int index, int property, int period); -static double getSavedLinkValue(int index, int property, int period); +static double getSavedSubcatchValue(int property, int index, int period); +static double getSavedNodeValue(int property, int index, int period); +static double getSavedLinkValue(int property, int index, int period); static double getSystemValue(int property); static double getMaxRouteStep(); -static int setNodeLatFlow(int index, double value); -static int setOutfallStage(int index, double value); -static int setLinkSetting(int index, double value); -static int setRoutingStep(double value); -static int setSystemValue(int property, double value); -static void getAbsolutePath(const char* fname, char* absPath, size_t size); +static int setGageValue(int property, int index, int subIndex, double value); +static int setSubcatchValue(int property, int index, int subIndex, double value); +static int setNodeValue(int property, int index, int subIndex, double value); +static int setLinkValue(int property, int index, int subIndex, double value); +static int setNodeLatFlow(int index, double value); +static int setOutfallStage(int index, double value); +static int setLinkSetting(int index, double value); +static int setRoutingStep(double value); +static int setSystemValue(int property, double value); +static void getAbsolutePath(const char *fname, char *absPath, size_t size); // Exception filtering function #ifdef EXH -static int xfilter(int xc, char* module, double elapsedTime, long step); +static int xfilter(int xc, char *module, double elapsedTime, long step); #endif //============================================================================= -int DLLEXPORT swmm_run(const char* inputFile, const char* reportFile, const char* outputFile) +int DLLEXPORT swmm_run(const char *inputFile, const char *reportFile, const char *outputFile) // // Input: inputFile = name of input file // reportFile = name of report file @@ -212,20 +224,20 @@ int DLLEXPORT swmm_run(const char* inputFile, const char* reportFile, const char swmm_open(inputFile, reportFile, outputFile); // --- run the simulation if input data OK - if ( !ErrorCode ) + if (!ErrorCode) { // --- initialize values swmm_start(TRUE); // --- execute each time step until elapsed time is re-set to 0 - if ( !ErrorCode ) + if (!ErrorCode) { writecon("\n o Simulating day: 0 hour: 0"); do { swmm_step(&elapsedTime); newHour = (long)(elapsedTime * 24.0); - if ( newHour > oldHour ) + if (newHour > oldHour) { theDay = (long)elapsedTime; theHour = (long)((elapsedTime - floor(elapsedTime)) * 24.0); @@ -234,7 +246,7 @@ int DLLEXPORT swmm_run(const char* inputFile, const char* reportFile, const char writecon(Msg); oldHour = newHour; } - } while ( elapsedTime > 0.0 && !ErrorCode ); + } while (elapsedTime > 0.0 && !ErrorCode); writecon("\b\b\b\b\b\b\b\b\b\b\b\b\b\b" "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); writecon("Simulation complete "); @@ -245,7 +257,7 @@ int DLLEXPORT swmm_run(const char* inputFile, const char* reportFile, const char } // --- report results - if ( !ErrorCode && Fout.mode == SCRATCH_FILE ) + if (!ErrorCode && Fout.mode == SCRATCH_FILE) { writecon("\n o Writing output report"); swmm_report(); @@ -258,7 +270,7 @@ int DLLEXPORT swmm_run(const char* inputFile, const char* reportFile, const char //============================================================================= -int DLLEXPORT swmm_run_with_callback(const char* inputFile, const char* reportFile, const char* outputFile, progress_callback callback) +int DLLEXPORT swmm_run_with_callback(const char *inputFile, const char *reportFile, const char *outputFile, progress_callback callback) // // Input: inputFile = name of input file // reportFile = name of report file @@ -281,13 +293,13 @@ int DLLEXPORT swmm_run_with_callback(const char* inputFile, const char* reportFi swmm_open(inputFile, reportFile, outputFile); // --- run the simulation if input data OK - if ( !ErrorCode ) + if (!ErrorCode) { // --- initialize values swmm_start(TRUE); // --- execute each time step until elapsed time is re-set to 0 - if ( !ErrorCode ) + if (!ErrorCode) { do { @@ -300,7 +312,7 @@ int DLLEXPORT swmm_run_with_callback(const char* inputFile, const char* reportFi callback(progress); } - } while ( elapsedTime > 0.0 && !ErrorCode ); + } while (elapsedTime > 0.0 && !ErrorCode); } // --- clean up @@ -308,7 +320,7 @@ int DLLEXPORT swmm_run_with_callback(const char* inputFile, const char* reportFi } // --- report results - if ( !ErrorCode && Fout.mode == SCRATCH_FILE ) + if (!ErrorCode && Fout.mode == SCRATCH_FILE) { swmm_report(); } @@ -319,16 +331,15 @@ int DLLEXPORT swmm_run_with_callback(const char* inputFile, const char* reportFi return ErrorCode; } - //============================================================================= -int DLLEXPORT swmm_open(const char* inputFile, const char* reportFile, const char* outputFile) +int DLLEXPORT swmm_open(const char *inputFile, const char *reportFile, const char *outputFile) // // Input: inputFile = name of input file // reportFile = name of report file // outputFile = name of binary output file // Output: returns error code -// Purpose: opens a SWMM project. +// Purpose: opens a SWMM project. // { // --- to be safe, reset the state of the floating point unit @@ -355,13 +366,15 @@ int DLLEXPORT swmm_open(const char* inputFile, const char* reportFile, const cha strcpy(InpDir, ""); project_open(inputFile, reportFile, outputFile); getAbsolutePath(inputFile, InpDir, sizeof(InpDir)); - if ( ErrorCode ) return ErrorCode; + if (ErrorCode) + return ErrorCode; IsOpenFlag = TRUE; report_writeLogo(); // --- retrieve project data from input file project_readInput(); - if ( ErrorCode ) return ErrorCode; + if (ErrorCode) + return ErrorCode; // --- write project title to report file & validate data report_writeTitle(); @@ -370,7 +383,7 @@ int DLLEXPORT swmm_open(const char* inputFile, const char* reportFile, const cha #ifdef EXH // --- end of try loop; handle exception here - __except(xfilter(GetExceptionCode(), "swmm_open", 0.0, 0)) + __except (xfilter(GetExceptionCode(), "swmm_open", 0.0, 0)) { ErrorCode = ERR_SYSTEM; } @@ -382,16 +395,17 @@ int DLLEXPORT swmm_open(const char* inputFile, const char* reportFile, const cha int DLLEXPORT swmm_start(int saveResults) // -// Input: saveResults = TRUE if simulation results saved to binary file +// Input: saveResults = TRUE if simulation results saved to binary file // Output: returns an error code // Purpose: starts a SWMM simulation. // { // --- check that a project is open & no run started - if ( ErrorCode ) return ErrorCode; - if ( !IsOpenFlag ) + if (ErrorCode) + return ErrorCode; + if (!IsOpenFlag) return (ErrorCode = ERR_API_NOT_OPEN); - if ( IsStartedFlag ) + if (IsStartedFlag) return (ErrorCode = ERR_API_NOT_ENDED); // --- write input summary & project options to report file if requested @@ -432,42 +446,51 @@ int DLLEXPORT swmm_start(int saveResults) // --- open rainfall processor (creates/opens a rainfall // interface file and generates any RDII flows) - if ( !IgnoreRainfall ) rain_open(); - if ( ErrorCode ) return ErrorCode; + if (!IgnoreRainfall) + rain_open(); + if (ErrorCode) + return ErrorCode; // --- initialize state of each major system component project_init(); // --- see if runoff & routing needs to be computed - if ( Nobjects[SUBCATCH] > 0 ) DoRunoff = TRUE; - else DoRunoff = FALSE; - if ( Nobjects[NODE] > 0 && !IgnoreRouting ) DoRouting = TRUE; - else DoRouting = FALSE; + if (Nobjects[SUBCATCH] > 0) + DoRunoff = TRUE; + else + DoRunoff = FALSE; + if (Nobjects[NODE] > 0 && !IgnoreRouting) + DoRouting = TRUE; + else + DoRouting = FALSE; // --- open binary output file output_open(); // --- open runoff processor - if ( DoRunoff ) runoff_open(); + if (DoRunoff) + runoff_open(); // --- open & read hot start file if present - if ( !hotstart_open() ) return ErrorCode; + if (!hotstart_open()) + return ErrorCode; // --- open routing processor - if ( DoRouting ) routing_open(); + if (DoRouting) + routing_open(); // --- open mass balance and statistics processors massbal_open(); stats_open(); - // --- write heading for control actions listing - if (!RptFlags.disabled && RptFlags.controls) - report_writeControlActionsHeading(); + // --- write heading for control actions listing + if (!RptFlags.disabled && RptFlags.controls) + report_writeControlActionsHeading(); } #ifdef EXH // --- end of try loop; handle exception here - __except(xfilter(GetExceptionCode(), "swmm_start", 0.0, 0)) + __except (xfilter(GetExceptionCode(), "swmm_start", 0.0, 0)) { ErrorCode = ERR_SYSTEM; } @@ -487,11 +510,11 @@ int DLLEXPORT swmm_step(double *elapsedTime) { // --- check that simulation can proceed *elapsedTime = 0.0; - if ( ErrorCode ) + if (ErrorCode) return ErrorCode; - if ( !IsOpenFlag ) + if (!IsOpenFlag) return (ErrorCode = ERR_API_NOT_OPEN); - if ( !IsStartedFlag ) + if (!IsStartedFlag) return (ErrorCode = ERR_API_NOT_STARTED); #ifdef EXH @@ -500,7 +523,7 @@ int DLLEXPORT swmm_step(double *elapsedTime) #endif { // --- if routing time has not exceeded total duration - if ( NewRoutingTime < RoutingDuration ) + if (NewRoutingTime < RoutingDuration) { // --- route flow & WQ through drainage system // (runoff will be calculated as needed) @@ -509,24 +532,25 @@ int DLLEXPORT swmm_step(double *elapsedTime) } // --- if saving results to the binary file - if ( SaveResultsFlag ) + if (SaveResultsFlag) saveResults(); // --- save hostart files if applicable hotstart_save(); // --- update elapsed time (days) - if ( NewRoutingTime < RoutingDuration ) + if (NewRoutingTime < RoutingDuration) ElapsedTime = NewRoutingTime / MSECperDAY; // --- otherwise end the simulation - else ElapsedTime = 0.0; + else + ElapsedTime = 0.0; *elapsedTime = ElapsedTime; } #ifdef EXH // --- end of try loop; handle exception here - __except(xfilter(GetExceptionCode(), "swmm_step", ElapsedTime, TotalStepCount)) + __except (xfilter(GetExceptionCode(), "swmm_step", ElapsedTime, TotalStepCount)) { ErrorCode = ERR_SYSTEM; } @@ -560,7 +584,8 @@ int DLLEXPORT swmm_stride(int strideStep, double *elapsedTime) RoutingDuration = MIN(TotalDuration, RoutingDuration); // --- modify routing step to not exceed stride time step - if (strideStep < RouteStep) RouteStep = strideStep; + if (strideStep < RouteStep) + RouteStep = strideStep; // --- step through simulation until next stride step is reached do @@ -577,14 +602,15 @@ int DLLEXPORT swmm_stride(int strideStep, double *elapsedTime) { ElapsedTime = NewRoutingTime / MSECperDAY; } - else ElapsedTime = 0.0; + else + ElapsedTime = 0.0; *elapsedTime = ElapsedTime; return ErrorCode; } //============================================================================= -int DLLEXPORT swmm_useHotStart(const char* hotStartFile) +int DLLEXPORT swmm_useHotStart(const char *hotStartFile) // // Input: hotStartFile = filepath to hotstart file to use // Output: returns error code @@ -593,7 +619,7 @@ int DLLEXPORT swmm_useHotStart(const char* hotStartFile) { int errorCode = 0; int fileVersion = 0; - char fname[MAXFNAME + 1]; + char fname[MAXFNAME + 1]; if (ErrorCode) return ErrorCode; @@ -607,9 +633,9 @@ int DLLEXPORT swmm_useHotStart(const char* hotStartFile) // Try to open the hotstart file first to see if it is valid errorCode = hotstart_is_valid(addAbsolutePath(fname), &fileVersion); if (errorCode) - { - return errorCode; - } + { + return errorCode; + } else { FhotstartInput.mode = USE_FILE; @@ -621,7 +647,7 @@ int DLLEXPORT swmm_useHotStart(const char* hotStartFile) //============================================================================= -int DLLEXPORT swmm_saveHotStart(const char* hotStartFile) +int DLLEXPORT swmm_saveHotStart(const char *hotStartFile) // // Input: hotStartFile = filepath to hotstart file to save // Output: returns error code @@ -636,10 +662,9 @@ int DLLEXPORT swmm_saveHotStart(const char* hotStartFile) else if (!IsStartedFlag) return (ErrorCode = ERR_API_NOT_STARTED); - errorCode = hotstart_save_to_file(hotStartFile); + errorCode = hotstart_save_to_file(hotStartFile); - return errorCode; - + return errorCode; } //============================================================================= @@ -651,8 +676,8 @@ void execRouting() // Purpose: routes flow & WQ through drainage system over a single time step. // { - double nextRoutingTime; // updated elapsed routing time (msec) - double routingStep; // routing time step (sec) + double nextRoutingTime; // updated elapsed routing time (msec) + double routingStep; // routing time step (sec) #ifdef EXH // --- begin exception handling loop here @@ -661,9 +686,11 @@ void execRouting() { // --- determine when next routing time occurs TotalStepCount++; - if ( !DoRouting ) routingStep = MIN(WetStep, ReportStep); - else routingStep = routing_getRoutingStep(RouteModel, RouteStep); - if ( routingStep <= 0.0 ) + if (!DoRouting) + routingStep = MIN(WetStep, ReportStep); + else + routingStep = routing_getRoutingStep(RouteModel, RouteStep); + if (routingStep <= 0.0) { ErrorCode = ERR_TIMESTEP; return; @@ -671,7 +698,7 @@ void execRouting() nextRoutingTime = NewRoutingTime + 1000.0 * routingStep; // --- adjust routing step so that total duration not exceeded - if ( nextRoutingTime > RoutingDuration ) + if (nextRoutingTime > RoutingDuration) { routingStep = (RoutingDuration - NewRoutingTime) / 1000.0; routingStep = MAX(routingStep, 1. / 1000.0); @@ -679,18 +706,21 @@ void execRouting() } // --- compute runoff until next routing time reached or exceeded - if ( DoRunoff ) while ( NewRunoffTime < nextRoutingTime) - { - runoff_execute(); - if ( ErrorCode ) return; - } + if (DoRunoff) + while (NewRunoffTime < nextRoutingTime) + { + runoff_execute(); + if (ErrorCode) + return; + } // --- if no runoff analysis, update climate state (for evaporation) - else climate_setState(getDateTime(NewRoutingTime)); - + else + climate_setState(getDateTime(NewRoutingTime)); + // --- route flows & pollutants through drainage system // (while updating NewRoutingTime) - if ( DoRouting ) + if (DoRouting) routing_execute(RouteModel, routingStep); else NewRoutingTime = nextRoutingTime; @@ -698,8 +728,8 @@ void execRouting() #ifdef EXH // --- end of try loop; handle exception here - __except(xfilter(GetExceptionCode(), "execRouting", - ElapsedTime, TotalStepCount)) + __except (xfilter(GetExceptionCode(), "execRouting", + ElapsedTime, TotalStepCount)) { ErrorCode = ERR_SYSTEM; return; @@ -722,7 +752,8 @@ void saveResults() { // --- include latest results in current averages // if current time equals the reporting time - if (NewRoutingTime == ReportTime) output_updateAvgResults(); + if (NewRoutingTime == ReportTime) + output_updateAvgResults(); // --- save current average results to binary file // (which will re-set averages to 0) @@ -730,19 +761,21 @@ void saveResults() // --- if current time exceeds reporting period then // start computing averages for next period - if (NewRoutingTime > ReportTime) output_updateAvgResults(); + if (NewRoutingTime > ReportTime) + output_updateAvgResults(); } // --- otherwise save interpolated point results - else output_saveResults(ReportTime); + else + output_saveResults(ReportTime); // --- advance to next reporting period ReportTime = ReportTime + 1000 * (double)ReportStep; } // --- not a reporting period so update average results if applicable - else if (RptFlags.averages) output_updateAvgResults(); - + else if (RptFlags.averages) + output_updateAvgResults(); } //============================================================================= @@ -755,16 +788,17 @@ int DLLEXPORT swmm_end(void) // { // --- check that project opened and run started - if ( !IsOpenFlag ) + if (!IsOpenFlag) return (ErrorCode = ERR_API_NOT_OPEN); - if ( IsStartedFlag ) + if (IsStartedFlag) { // --- write ending records to binary output file - if ( Fout.file ) output_end(); + if (Fout.file) + output_end(); // --- report mass balance results and system statistics - if ( !ErrorCode && RptFlags.disabled == 0 ) + if (!ErrorCode && RptFlags.disabled == 0) { massbal_report(); stats_report(); @@ -773,9 +807,12 @@ int DLLEXPORT swmm_end(void) // --- close all computing systems stats_close(); massbal_close(); - if ( !IgnoreRainfall ) rain_close(); - if ( DoRunoff ) runoff_close(); - if ( DoRouting ) routing_close(RouteModel); + if (!IgnoreRainfall) + rain_close(); + if (DoRunoff) + runoff_close(); + if (DoRouting) + routing_close(RouteModel); hotstart_close(); IsStartedFlag = FALSE; } @@ -791,7 +828,7 @@ int DLLEXPORT swmm_report() // Purpose: writes simulation results to the report file. // { - if ( !ErrorCode ) + if (!ErrorCode) report_writeReport(); return ErrorCode; } @@ -818,17 +855,20 @@ int DLLEXPORT swmm_close() // Purpose: closes a SWMM project. // { - if ( Fout.file ) output_close(); - if ( IsOpenFlag ) project_close(); + if (Fout.file) + output_close(); + if (IsOpenFlag) + project_close(); report_writeSysTime(); - if ( Finp.file != NULL ) + if (Finp.file != NULL) fclose(Finp.file); - if ( Frpt.file != NULL ) + if (Frpt.file != NULL) fclose(Frpt.file); - if ( Fout.file != NULL ) + if (Fout.file != NULL) { fclose(Fout.file); - if ( Fout.mode == SCRATCH_FILE ) remove(Fout.name); + if (Fout.mode == SCRATCH_FILE) + remove(Fout.name); } IsOpenFlag = FALSE; IsStartedFlag = FALSE; @@ -838,7 +878,7 @@ int DLLEXPORT swmm_close() //============================================================================= int DLLEXPORT swmm_getMassBalErr(float *runoffErr, float *flowErr, - float *qualErr) + float *qualErr) // // Input: none // Output: runoffErr = runoff mass balance error (percent) @@ -849,14 +889,14 @@ int DLLEXPORT swmm_getMassBalErr(float *runoffErr, float *flowErr, // { *runoffErr = 0.0; - *flowErr = 0.0; - *qualErr = 0.0; + *flowErr = 0.0; + *qualErr = 0.0; - if ( IsOpenFlag && !IsStartedFlag) + if (IsOpenFlag && !IsStartedFlag) { *runoffErr = (float)RunoffError; - *flowErr = (float)FlowError; - *qualErr = (float)QualError; + *flowErr = (float)FlowError; + *qualErr = (float)QualError; } return 0; } @@ -904,7 +944,8 @@ int DLLEXPORT swmm_getError(char *errMsg, int msgLen) sstrncpy(errMsg, ErrorMsg, msgLen); // --- remove leading line feed from errMsg - if ( msgLen > 0 && errMsg[0] == '\n' ) errMsg[0] = ' '; + if (msgLen > 0 && errMsg[0] == '\n') + errMsg[0] = ' '; return ErrorCode; } @@ -918,10 +959,10 @@ int DLLEXPORT swmm_getErrorFromCode(int errorCode, char *outErrMsg[1024]) // error code number. { char err_msg[MAXMSG]; - error_getMsg(errorCode, err_msg); + error_getMsg(errorCode, err_msg); sstrncpy(*outErrMsg, err_msg, MAXMSG); - return 0; + return 0; } //============================================================================= @@ -934,7 +975,7 @@ int DLLEXPORT swmm_getCount(int objType) { if (!IsOpenFlag) return ERR_API_NOT_OPEN; - if (objType < swmm_GAGE || objType > swmm_LINK) + if (objType < swmm_GAGE || objType > swmm_SYSTEM) return ERR_API_OBJECT_TYPE; return Nobjects[objType]; } @@ -947,7 +988,7 @@ int DLLEXPORT swmm_getName(int objType, int index, char *name, int size) // index = the object's index in the array of objects // name = a character array // size = size of the name array -// Output: name = the object's ID name; +// Output: name = the object's ID name; // error code // Purpose: retrieves the ID name of an object. { @@ -956,17 +997,52 @@ int DLLEXPORT swmm_getName(int objType, int index, char *name, int size) name[0] = '\0'; if (!IsOpenFlag) return ERR_API_NOT_OPEN; - if (objType < swmm_GAGE || objType > swmm_LINK) - return ERR_API_OBJECT_TYPE; - if (index < 0 || index >= Nobjects[objType]) - return ERR_API_OBJECT_INDEX; + switch (objType) { - case GAGE: idName = Gage[index].ID; break; - case SUBCATCH: idName = Subcatch[index].ID; break; - case NODE: idName = Node[index].ID; break; - case LINK: idName = Link[index].ID; break; + case GAGE: + idName = Gage[index].ID; + break; + case SUBCATCH: + idName = Subcatch[index].ID; + break; + case NODE: + idName = Node[index].ID; + break; + case LINK: + idName = Link[index].ID; + break; + case POLLUT: + idName = Pollut[index].ID; + break; + case LANDUSE: + idName = Landuse[index].ID; + break; + case TIMEPATTERN: + idName = Pattern[index].ID; + break; + case CURVE: + idName = Curve[index].ID; + break; + case TSERIES: + idName = Tseries[index].ID; + break; + case TRANSECT: + idName = Transect[index].ID; + break; + case AQUIFER: + idName = Aquifer[index].ID; + break; + case UNITHYD: + idName = UnitHyd[index].ID; + break; + case SNOWMELT: + idName = Snowmelt[index].ID; + break; + default: + return ERR_API_OBJECT_TYPE; } + if (idName) sstrncpy(name, idName, size); @@ -995,9 +1071,10 @@ double DLLEXPORT swmm_getValue(int property, int index) // // Input: property = an object's property code // index = the object's index in the array of like objects -// +// // Output: returns the property's current value // Purpose: retrieves the value of an object's property. +// TODO: Will be deprecated in SWMM version 6.0. Use swmm_getValueExpanded instead. { if (!IsOpenFlag) return ERR_API_NOT_OPEN; @@ -1006,17 +1083,48 @@ double DLLEXPORT swmm_getValue(int property, int index) if (property < 200) return getGageValue(property, index); if (property < 300) - return getSubcatchValue(property, index); + return getSubcatchValue(property, index, -1); if (property < 400) - return getNodeValue(property, index); + return getNodeValue(property, index, -1); if (property < 500) - return getLinkValue(property, index); + return getLinkValue(property, index, -1); return ERR_API_PROPERTY_TYPE; } //============================================================================= +double DLLEXPORT swmm_getValueExpanded(int objType, int property, int index, int subIndex) +// +// Input: property = an object's property code +// index = the object's index in the array of like objects +// subIndex = an index of the object's property +// +// Output: returns the property's current value +// Purpose: retrieves the value of an object's property. +{ + if (!IsOpenFlag) + return ERR_API_NOT_OPEN; + + switch (objType) + { + case swmm_SYSTEM: + return getSystemValue(property); + case swmm_GAGE: + return getGageValue(property, index); + case swmm_SUBCATCH: + return getSubcatchValue(property, index, subIndex); + case swmm_NODE: + return getNodeValue(property, index, subIndex); + case swmm_LINK: + return getLinkValue(property, index, subIndex); + default: + return ERR_API_OBJECT_TYPE; + } +} + +//============================================================================= + int DLLEXPORT swmm_setValue(int property, int index, double value) // // Input: property = an object's property code @@ -1024,283 +1132,772 @@ int DLLEXPORT swmm_setValue(int property, int index, double value) // value = the property's new value // Output: returns error code // Purpose: sets the value of an object's property. +// TODO: Will be deprecated in SWMM version 6.0. Use swmm_setValueExpanded instead, +// which will be renamed to swmm_setValue. { + if (!IsOpenFlag) return ERR_API_NOT_OPEN; - if (property < 100) - return setSystemValue(property, value); - - switch (property) { case swmm_GAGE_RAINFALL: if (index < 0 || index >= Nobjects[GAGE]) - return ERR_API_OBJECT_INDEX; - else if (value >= 0.0) - { - Gage[index].apiRainfall = value; return 0; - } - else - return ERR_API_PROPERTY_VALUE; - + if (value >= 0.0) + Gage[index].apiRainfall = value; + return 0; case swmm_SUBCATCH_RPTFLAG: - if(IsStartedFlag) - return ERR_API_NOT_ENDED; - else if (index >= 0 && index < Nobjects[SUBCATCH]) - { + if (!IsStartedFlag && index >= 0 && index < Nobjects[SUBCATCH]) Subcatch[index].rptFlag = (value > 0.0); - return 0; - } - else - return ERR_API_OBJECT_INDEX; + return 0; case swmm_NODE_LATFLOW: - return setNodeLatFlow(index, value); + setNodeLatFlow(index, value); + return 0; case swmm_NODE_HEAD: - return setOutfallStage(index, value); + setOutfallStage(index, value); + return 0; case swmm_NODE_RPTFLAG: - if (IsStartedFlag) - return ERR_API_NOT_ENDED; - else if (index >= 0 && index < Nobjects[NODE]) - { + if (!IsStartedFlag && index >= 0 && index < Nobjects[NODE]) Node[index].rptFlag = (value > 0.0); - return 0; - } - else - return ERR_API_OBJECT_INDEX; + return 0; case swmm_LINK_SETTING: - return setLinkSetting(index, value); + setLinkSetting(index, value); + return 0; case swmm_LINK_RPTFLAG: - if (IsStartedFlag) - return ERR_API_NOT_ENDED; - if (index >= 0 && index < Nobjects[LINK]) - { + if (!IsStartedFlag && index >= 0 && index < Nobjects[LINK]) Link[index].rptFlag = (value > 0.0); - return 0; - } - else - return ERR_API_OBJECT_INDEX; - default: - return ERR_API_PROPERTY_TYPE; - } -} - -//============================================================================= - -double DLLEXPORT swmm_getSavedValue(int property, int index, int period) -// -// Input: property = an object's property code -// index = the object's index in the array of like objects -// period = a reporting time period (starting from 1) -// Output: returns the property's saved value -// Purpose: retrieves an object's computed value at a specific reporting time period. -{ - if (!IsOpenFlag) return 0; - if (IsStartedFlag) + case swmm_ROUTESTEP: + setRoutingStep(value); return 0; - if (period < 1 || period > Nperiods) + case swmm_REPORTSTEP: + if (!IsStartedFlag && value > 0) + ReportStep = (int)value; return 0; - if (property == swmm_CURRENTDATE) - return getSavedDate(period); - if (property >= 200 && property < 300) - return getSavedSubcatchValue(property, index, period); - if (property < 400) - return getSavedNodeValue(property, index, period); - if (property < 500) - return getSavedLinkValue(property, index, period); - return 0; + case swmm_NOREPORT: + if (!IsStartedFlag) + RptFlags.disabled = (value > 0.0); + return 0; + default: + return ERR_API_PROPERTY_TYPE; + } } //============================================================================= -void DLLEXPORT swmm_decodeDate(double date, int *year, int *month, int *day, - int *hour, int *minute, int *second, int *dayOfWeek) +int DLLEXPORT swmm_setValueExpanded(int objType, int property, int index, int subIndex, double value) // -// Input: date = an encoded date in decimal days -// Output: date's year, month of year, day of month, time of day (hour, -// minute, second), and day of weeek -// Purpose: retrieves the calendar date and clock time of an encoded date. +// Input: objType = a type of SWMM object +// property = an object's property code +// index = the object's index in the array of like objects +// subIndex = an index of the object's property +// value = the property's new value +// Output: returns error code +// Purpose: sets the value of an object's property. +// TODO: Will be deprecated in SWMM version 6.0 { - datetime_decodeDate(date, year, month, day); - datetime_decodeTime(date, hour, minute, second); - *dayOfWeek = datetime_dayOfWeek(date); -} - -//============================================================================= + if (!IsOpenFlag) + return ERR_API_NOT_OPEN; -double DLLEXPORT swmm_encodeDate(int year, int month, int day, - int hour, int minute, int second) -// -// Input: date's year, month of year, day of month, time of day (hour, -// minute, second), and day of weeek -// Output: date = an encoded date in decimal days -// Purpose: retrieves the calendar date and clock time of a decoded date. -{ - return datetime_encodeDate(year, month, day) + datetime_encodeTime(hour, minute, second); + switch (index) + { + case swmm_SYSTEM: + return setSystemValue(property, value); + case swmm_GAGE: + return setGageValue(property, index, subIndex, value); + case swmm_SUBCATCH: + return setSubcatchValue(property, index, subIndex, value); + case swmm_NODE: + return setNodeValue(property, index, subIndex, value); + case swmm_LINK: + return setLinkValue(property, index, subIndex, value); + default: + return ERR_API_OBJECT_TYPE; + } } - -//============================================================================= -// Object property getters and setters //============================================================================= -double getGageValue(int property, int index) +int setGageValue(int property, int index, int subIndex, double value) // -// Input: property = a rain gage property code -// index = the index of a rain gage -// Output: returns current property value -// Purpose: retrieves current value of a rain gage property. +// Input: property = an object's property code +// index = the object's index in the array of like objects +// subIndex = an index of the object's property +// value = the property's new value +// Output: returns error code +// Purpose: sets the value of a rain gage object's property. { if (index < 0 || index >= Nobjects[GAGE]) - return 0; - if (property == swmm_GAGE_RAINFALL) - return Gage[index].reportRainfall; - return 0; + return ERR_API_OBJECT_INDEX; + + switch (property) + { + case swmm_GAGE_RAINFALL: + if (value >= 0.0) + { + Gage[index].apiRainfall = value; + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + default: + return ERR_API_PROPERTY_TYPE; + } } //============================================================================= -double getSubcatchValue(int property, int index) +int setSubcatchValue(int property, int index, int subIndex, double value) // -// Input: property = a subcatchment property code -// index = the index of a subcatchment -// Output: returns current property value -// Purpose: retrieves current value of a subcatchment's property. +// Input: property = an object's property code +// index = the object's index in the array of like objects +// subIndex = an index of the object's property +// value = the property's new value +// Output: returns error code +// Purpose: sets the value of a subcatchment object's property. +// TODO: Convert Error codes to warnings { - TSubcatch* subcatch; - if (index < 0 || index >= Nobjects[SUBCATCH]) - return 0; - subcatch = &Subcatch[index]; - switch (property) + + if (IsOpenFlag == FALSE) + { + return ERR_API_NOT_OPEN; + } + // Check if object index is within bounds + else if (index < 0 || index >= Nobjects[SUBCATCH]) + return ERR_API_OBJECT_INDEX; + // Check if Simulation is Running + else if (IsStartedFlag == TRUE) + { + // Set values that can be configured at runtime during simulation + switch (property) + { + case swmm_SUBCATCH_API_RAINFALL: + if (value >= 0.0) + { + Subcatch[index].apiRainfall = value / UCF(RAINFALL); + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + case swmm_SUBCATCH_API_SNOWFALL: + if (value >= 0.0) + { + Subcatch[index].apiSnowfall = value / UCF(RAINFALL); + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + case swmm_SUBCATCH_EXTERNAL_POLLUTANT_BUILDUP: + Subcatch[index].apiExtBuildup[subIndex] = value; + return 0; + default: + return ERR_API_IS_RUNNING; + } + } + else { + switch (property) + { case swmm_SUBCATCH_AREA: - return subcatch->area * UCF(LANDAREA); - case swmm_SUBCATCH_RAINGAGE: - return subcatch->gage; - case swmm_SUBCATCH_RAINFALL: - if ( subcatch->gage >= 0 ) - return Gage[subcatch->gage].reportRainfall; + if (value >= 0.0) + { + Subcatch[index].area = value / UCF(LANDAREA); + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + case swmm_SUBCATCH_WIDTH: + if (value >= 0.0) + { + Subcatch[index].width = value / UCF(LENGTH); + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + case swmm_SUBCATCH_SLOPE: + if (value >= 0.0) + { + Subcatch[index].slope = value; + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + case swmm_SUBCATCH_CURB_LENGTH: + if (value >= 0.0) + { + Subcatch[index].curbLength = value / UCF(LENGTH); + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + case swmm_SUBCATCH_API_RAINFALL: + if (value >= 0.0) + { + Subcatch[index].apiRainfall = value / UCF(RAINFALL); + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + case swmm_SUBCATCH_API_SNOWFALL: + if (value >= 0.0) + { + Subcatch[index].apiSnowfall = value / UCF(RAINFALL); + return 0; + } else - return 0.0; - case swmm_SUBCATCH_EVAP: - return subcatch->evapLoss * UCF(EVAPRATE); - case swmm_SUBCATCH_INFIL: - return subcatch->infilLoss * UCF(RAINFALL); - case swmm_SUBCATCH_RUNOFF: - return subcatch->newRunoff * UCF(FLOW); + return ERR_API_PROPERTY_VALUE; case swmm_SUBCATCH_RPTFLAG: - return (subcatch->rptFlag > 0); + if (value >= 0.0) + { + Subcatch[index].rptFlag = (value > 0.0); + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + case swmm_SUBCATCH_EXTERNAL_POLLUTANT_BUILDUP: + Subcatch[index].apiExtBuildup[subIndex] = value; + return 0; default: - return 0; + return ERR_API_PROPERTY_TYPE; + } } } //============================================================================= -double getNodeValue(int property, int index) +int setNodeValue(int property, int index, int subIndex, double value) // -// Input: property = a node property code -// index = the index of a node -// Output: returns current property value -// Purpose: retrieves current value of a node's property. +// Input: property = an object's property code +// index = the object's index in the array of like objects +// subIndex = an index of the object's property +// value = the property's new value +// Output: returns error code +// Purpose: sets the value of a node object's property. +// TODO: Convert Error codes to warnings { - TNode* node; - if (index < 0 || index >= Nobjects[NODE]) - return 0; - node = &Node[index]; - switch (property) + + TNode *node = NULL; + + if (IsOpenFlag == FALSE) { - case swmm_NODE_TYPE: - return node->type; - case swmm_NODE_ELEV: - return node->invertElev * UCF(LENGTH); - case swmm_NODE_MAXDEPTH: - return node->fullDepth * UCF(LENGTH); - case swmm_NODE_DEPTH: - return node->newDepth * UCF(LENGTH); - case swmm_NODE_HEAD: - return (node->newDepth + node->invertElev) * UCF(LENGTH); - case swmm_NODE_VOLUME: - return node->newVolume * UCF(VOLUME); - case swmm_NODE_LATFLOW: - return node->newLatFlow * UCF(FLOW); - case swmm_NODE_INFLOW: - return node->inflow * UCF(FLOW); - case swmm_NODE_OVERFLOW: - return node->overflow * UCF(FLOW); - case swmm_NODE_RPTFLAG: - return (node->rptFlag > 0); - default: - return 0; + return ERR_API_NOT_OPEN; + } + // Check if object index is within bounds + else if (index < 0 || index >= Nobjects[NODE]) + { + return ERR_API_OBJECT_INDEX; + } + // Check if Simulation is Running + else if (IsStartedFlag == TRUE) + { + + // Set values that can be configured at runtime during simulation + switch (property) + { + case swmm_NODE_LATFLOW: + Node[index].apiExtInflow = value / UCF(FLOW); + return 0; + case swmm_NODE_HEAD: + node = &Node[index]; + + if (node->type != OUTFALL) + return ERR_API_OBJECT_TYPE; + + Outfall[node->subIndex].fixedStage = value / UCF(LENGTH); + Outfall[node->subIndex].type = FIXED_OUTFALL; + return 0; + case swmm_NODE_POLLUTANT_LATMASS_FLUX: + Node[index].apiExtQualMassFlux[subIndex] = value; + return 0; + default: + return ERR_API_IS_RUNNING; + } + } + else + { + // Set values that can only be configured before simulation starts + switch (property) + { + case swmm_NODE_ELEV: + Node[index].invertElev = value / UCF(LENGTH); + return 0; + case swmm_NODE_MAXDEPTH: + if (value >= 0.0) + { + Node[index].fullDepth = value / UCF(LENGTH); + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + case swmm_NODE_SURCHARGE_DEPTH: + if (value >= 0.0) + { + Node[index].surDepth = value / UCF(LENGTH); + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + case swmm_NODE_PONDED_AREA: + if (value >= 0.0) + { + Node[index].pondedArea = value / UCF(LANDAREA); + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + case swmm_NODE_INITIAL_DEPTH: + if (value >= 0.0) + { + Node[index].initDepth = value / UCF(LENGTH); + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + case swmm_NODE_LATFLOW: + Node[index].apiExtInflow = value / UCF(FLOW); + return 0; + case swmm_NODE_HEAD: + node = &Node[index]; + + if (node->type != OUTFALL) + return ERR_API_OBJECT_TYPE; + + Outfall[node->subIndex].fixedStage = value / UCF(LENGTH); + Outfall[node->subIndex].type = FIXED_OUTFALL; + return 0; + case swmm_NODE_RPTFLAG: + Node[index].rptFlag = (value > 0.0); + return 0; + case swmm_NODE_POLLUTANT_LATMASS_FLUX: + Node[index].apiExtQualMassFlux[subIndex] = value; + return 0; + default: + return ERR_API_PROPERTY_TYPE; + } + } +} + +//============================================================================= + +int setLinkValue(int property, int index, int subIndex, double value) +// +// Input: property = an object's property code +// index = the object's index in the array of like objects +// subIndex = an index of the object's property +// value = the property's new value +// Output: returns error code +// Purpose: sets the value of a link object's property. +// TODO: Convert Error codes to warnings +{ + TLink *link = NULL; + const char* control_rule_label = "SWMM API"; + + if (IsOpenFlag == FALSE) + { + return ERR_API_NOT_OPEN; + } + // Check if object index is within bounds + else if (index < 0 || index >= Nobjects[LINK]) + { + return ERR_API_OBJECT_INDEX; + } + // Check if Simulation is Running + else if (IsStartedFlag == TRUE) + { + + link = &Link[index]; + + // Set values that can be configured at runtime during simulation + switch (property) + { + case swmm_LINK_SETTING: + return setLinkSetting(index, value); + case swmm_LINK_FLOW_LIMIT: + link->qLimit = value / UCF(FLOW); + return 0; + case swmm_LINK_SEEPAGE_RATE: + if (value >= 0.0) + { + link->seepRate = value / UCF(RAINFALL); + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + case swmm_LINK_POLLUTANT_LATMASS_FLUX: + link->apiExtQualMassFlux[subIndex] = value; + return 0; + default: + return ERR_API_IS_RUNNING; + } + } + else + { + // Set values that can only be configured before simulation starts + switch (property) + { + case swmm_LINK_SETTING: + return setLinkSetting(index, value); + case swmm_LINK_OFFSET1: + Link[index].offset1 = value / UCF(LENGTH); + return 0; + case swmm_LINK_OFFSET2: + Link[index].offset2 = value / UCF(LENGTH); + return 0; + case swmm_LINK_INITIAL_FLOW: + Link[index].q0 = value / UCF(FLOW); + return 0; + case swmm_LINK_FLOW_LIMIT: + Link[index].qLimit = value / UCF(FLOW); + return 0; + case swmm_LINK_INLET_LOSS: + Link[index].cLossInlet = value; + return 0; + case swmm_LINK_OUTLET_LOSS: + Link[index].cLossOutlet = value; + return 0; + case swmm_LINK_AVERAGE_LOSS: + Link[index].cLossAvg = value; + return 0; + case swmm_LINK_SEEPAGE_RATE: + if (value >= 0.0) + { + Link[index].seepRate = value / UCF(RAINFALL); + return 0; + } + else + return ERR_API_PROPERTY_VALUE; + return 0; + case swmm_LINK_HAS_FLAPGATE: + Link[index].hasFlapGate = (value > 0.0); + return 0; + case swmm_LINK_POLLUTANT_LATMASS_FLUX: + link->apiExtQualMassFlux[subIndex] = value; + return 0; + default: + return ERR_API_PROPERTY_TYPE; + } + } +} + +//============================================================================= + +double DLLEXPORT swmm_getSavedValue(int property, int index, int period) +// +// Input: property = an object's property code +// index = the object's index in the array of like objects +// period = a reporting time period (starting from 1) +// Output: returns the property's saved value +// Purpose: retrieves an object's computed value at a specific reporting time period. +{ + if (!IsOpenFlag) + return 0; + if (IsStartedFlag) + return 0; + if (period < 1 || period > Nperiods) + return 0; + if (property == swmm_CURRENTDATE) + return getSavedDate(period); + if (property >= 200 && property < 300) + return getSavedSubcatchValue(property, index, period); + if (property < 400) + return getSavedNodeValue(property, index, period); + if (property < 500) + return getSavedLinkValue(property, index, period); + return 0; +} + +//============================================================================= + +void DLLEXPORT swmm_decodeDate(double date, int *year, int *month, int *day, + int *hour, int *minute, int *second, int *dayOfWeek) +// +// Input: date = an encoded date in decimal days +// Output: date's year, month of year, day of month, time of day (hour, +// minute, second), and day of weeek +// Purpose: retrieves the calendar date and clock time of an encoded date. +{ + datetime_decodeDate(date, year, month, day); + datetime_decodeTime(date, hour, minute, second); + *dayOfWeek = datetime_dayOfWeek(date); +} + +//============================================================================= + +double DLLEXPORT swmm_encodeDate(int year, int month, int day, + int hour, int minute, int second) +// +// Input: date's year, month of year, day of month, time of day (hour, +// minute, second), and day of weeek +// Output: date = an encoded date in decimal days +// Purpose: retrieves the calendar date and clock time of a decoded date. +{ + return datetime_encodeDate(year, month, day) + datetime_encodeTime(hour, minute, second); +} + +//============================================================================= +// Object property getters and setters +//============================================================================= + +double getGageValue(int property, int index) +// +// Input: property = a rain gage property code +// index = the index of a rain gage +// Output: returns current property value +// Purpose: retrieves current value of a rain gage property. +{ + double total, snow, rain; + + if (index < 0 || index >= Nobjects[GAGE]) + return ERR_API_OBJECT_INDEX; + + total = gage_getPrecip(index, &rain, &snow); + + switch (index) + { + case swmm_GAGE_TOTAL_PRECIPITATION: + return total * UCF(RAINFALL); + case swmm_GAGE_RAINFALL: + return rain * UCF(RAINFALL); + case swmm_GAGE_SNOWFALL: + return snow * UCF(RAINFALL); + default: + return ERR_API_PROPERTY_TYPE; + } +} + +//============================================================================= + +double getSubcatchValue(int property, int index, int subIndex) +// +// Input: property = a subcatchment property code +// index = the index of a subcatchment +// subIndex = an index of the subcatchment's property +// Output: returns current property value +// Purpose: retrieves current value of a subcatchment's property. +{ + TSubcatch *subcatch; + double prop_results = 0.0; + int i; + + if (index < 0 || index >= Nobjects[SUBCATCH]) + return 0; + + subcatch = &Subcatch[index]; + + switch (property) + { + case swmm_SUBCATCH_AREA: + return subcatch->area * UCF(LANDAREA); + case swmm_SUBCATCH_RAINGAGE: + return subcatch->gage; + case swmm_SUBCATCH_RAINFALL: + return subcatch->rainfall * UCF(RAINFALL); + case swmm_SUBCATCH_EVAP: + return subcatch->evapLoss * UCF(EVAPRATE); + case swmm_SUBCATCH_INFIL: + return subcatch->infilLoss * UCF(RAINFALL); + case swmm_SUBCATCH_RUNOFF: + return subcatch->newRunoff * UCF(FLOW); + case swmm_SUBCATCH_RPTFLAG: + return (subcatch->rptFlag > 0); + case swmm_SUBCATCH_SLOPE: + return subcatch->slope; + case swmm_SUBCATCH_CURB_LENGTH: + return subcatch->curbLength * UCF(LENGTH); + case swmm_SUBCATCH_API_RAINFALL: + return subcatch->apiRainfall * UCF(RAINFALL); + case swmm_SUBCATCH_API_SNOWFALL: + return subcatch->apiSnowfall * UCF(RAINFALL); + case swmm_SUBCATCH_POLLUTANT_BUILDUP: + if (subIndex < 0 || subIndex >= Nobjects[POLLUT]) + return ERR_API_OBJECT_INDEX; + + for(i = 0; i < Nobjects[LANDUSE]; i++) + { + prop_results += subcatch->landFactor[i].buildup[subIndex] / + (subcatch->area * UCF(LANDAREA) * subcatch->landFactor[i].fraction); + } + + return prop_results; + case swmm_SUBCATCH_EXTERNAL_POLLUTANT_BUILDUP: + if (subIndex < 0 || subIndex >= Nobjects[POLLUT]) + return ERR_API_OBJECT_INDEX; + return subcatch->apiExtBuildup[subIndex] / UCF(LANDAREA); + + case swmm_SUBCATCH_POLLUTANT_RUNOFF_CONCENTRATION: + if (subIndex < 0 || subIndex >= Nobjects[POLLUT]) + return ERR_API_OBJECT_INDEX; + return subcatch->newQual[subIndex]; + + case swmm_SUBCATCH_POLLUTANT_PONDED_CONCENTRATION: + if (subIndex < 0 || subIndex >= Nobjects[POLLUT]) + return ERR_API_OBJECT_INDEX; + return subcatch->pondedQual[subIndex] / (subcatch_getDepth(index) * max(0.0, subcatch->area - subcatch->lidArea) * UCF(LANDAREA)); + + case swmm_SUBCATCH_POLLUTANT_TOTAL_LOAD: + if (subIndex < 0 || subIndex >= Nobjects[POLLUT]) + return ERR_API_OBJECT_INDEX; + return subcatch->totalLoad[subIndex]; + + default: + return ERR_API_PROPERTY_TYPE; + } +} + +//============================================================================= + +double getNodeValue(int property, int index, int subIndex) +// +// Input: property = a node property code +// index = the index of a node +// subIndex = an index of the node's property +// Output: returns current property value +// Purpose: retrieves current value of a node's property. +{ + TNode *node; + if (index < 0 || index >= Nobjects[NODE]) + return 0; + else + { + node = &Node[index]; + + switch (property) + { + case swmm_NODE_TYPE: + return node->type; + case swmm_NODE_ELEV: + return node->invertElev * UCF(LENGTH); + case swmm_NODE_MAXDEPTH: + return node->fullDepth * UCF(LENGTH); + case swmm_NODE_DEPTH: + return node->newDepth * UCF(LENGTH); + case swmm_NODE_HEAD: + return (node->newDepth + node->invertElev) * UCF(LENGTH); + case swmm_NODE_VOLUME: + return node->newVolume * UCF(VOLUME); + case swmm_NODE_LATFLOW: + return node->newLatFlow * UCF(FLOW); + case swmm_NODE_INFLOW: + return node->inflow * UCF(FLOW); + case swmm_NODE_OVERFLOW: + return node->overflow * UCF(FLOW); + case swmm_NODE_RPTFLAG: + return (node->rptFlag > 0); + case swmm_NODE_SURCHARGE_DEPTH: + return node->surDepth * UCF(LENGTH); + case swmm_NODE_PONDED_AREA: + return node->pondedArea * UCF(LANDAREA); + case swmm_NODE_INITIAL_DEPTH: + return node->initDepth * UCF(LENGTH); + case swmm_NODE_POLLUTANT_CONCENTRATION: + if (subIndex < 0 || subIndex >= Nobjects[POLLUT]) + return ERR_API_OBJECT_INDEX; + return node->newQual[subIndex]; + case swmm_NODE_POLLUTANT_LATMASS_FLUX: + if (subIndex < 0 || subIndex >= Nobjects[POLLUT]) + return ERR_API_OBJECT_INDEX; + return node->apiExtQualMassFlux[subIndex]; + default: + return ERR_API_OBJECT_TYPE; + } } } //============================================================================= -double getLinkValue(int property, int index) +double getLinkValue(int property, int index, int subIndex) // // Input: property = a link property code // index = the index of a link +// subIndex = an index of the link's property // Output: returns current property value // Purpose: retrieves current value of a link's property. { - TLink* link; + TLink *link; if (index < 0 || index >= Nobjects[LINK]) return 0; link = &Link[index]; switch (property) { - case swmm_LINK_TYPE: - return link->type; - case swmm_LINK_NODE1: - return link->node1; - case swmm_LINK_NODE2: - return link->node2; - case swmm_LINK_LENGTH: - if (link->type == CONDUIT) - return Conduit[link->subIndex].length * UCF(LENGTH); - else - return 0; - case swmm_LINK_SLOPE: - if (link->type == CONDUIT) - return Conduit[link->subIndex].slope; - else - return 0; - break; - case swmm_LINK_FULLDEPTH: - return link->xsect.yFull * UCF(LENGTH); - case swmm_LINK_FULLFLOW: - return link->qFull * UCF(FLOW); - case swmm_LINK_FLOW: - return link->newFlow * UCF(FLOW) * (double)link->direction; - case swmm_LINK_VELOCITY: - return link_getVelocity(index, fabs(link->newFlow), link->newDepth) - * UCF(LENGTH); - case swmm_LINK_DEPTH: - return link->newDepth * UCF(LENGTH); - case swmm_LINK_TOPWIDTH: - if (link->type == CONDUIT) - return xsect_getWofY(&link->xsect, link->newDepth) * UCF(LENGTH); - else - return 0; - case swmm_LINK_SETTING: - return link->setting; - case swmm_LINK_TIMEOPEN: - if (link->setting > 0.0) - return (getDateTime(NewRoutingTime) - link->timeLastSet) * 24.; - else - return 0; - case swmm_LINK_TIMECLOSED: - if (link->setting == 0.0) + case swmm_LINK_TYPE: + return link->type; + case swmm_LINK_NODE1: + return link->node1; + case swmm_LINK_NODE2: + return link->node2; + case swmm_LINK_LENGTH: + if (link->type == CONDUIT) + return Conduit[link->subIndex].length * UCF(LENGTH); + else + return ERR_API_OBJECT_TYPE; + case swmm_LINK_SLOPE: + if (link->type == CONDUIT) + return Conduit[link->subIndex].slope; + else + return ERR_API_OBJECT_TYPE; + break; + case swmm_LINK_FULLDEPTH: + return link->xsect.yFull * UCF(LENGTH); + case swmm_LINK_FULLFLOW: + return link->qFull * UCF(FLOW); + case swmm_LINK_SETTING: + return link->setting; + case swmm_LINK_TIMEOPEN: + if (link->setting > 0.0) + return (getDateTime(NewRoutingTime) - link->timeLastSet) * 24.; + else + return 0; + case swmm_LINK_TIMECLOSED: + if (link->setting <= 0.0) return (getDateTime(NewRoutingTime) - link->timeLastSet) * 24.; else return 0; - case swmm_LINK_RPTFLAG: - return (link->rptFlag > 0); - default: - return 0; + case swmm_LINK_FLOW: + return link->newFlow * UCF(FLOW); + case swmm_LINK_DEPTH: + return link->newDepth * UCF(LENGTH); + case swmm_LINK_VELOCITY: + return link_getVelocity(index, fabs(link->newFlow), link->newDepth) * UCF(LENGTH); + case swmm_LINK_TOPWIDTH: + if (link->type == CONDUIT) + return xsect_getWofY(&link->xsect, link->newDepth) * UCF(LENGTH); + else + return ERR_API_OBJECT_TYPE; + case swmm_LINK_RPTFLAG: + return (link->rptFlag > 0); + case swmm_LINK_OFFSET1: + return link->offset1 * UCF(LENGTH); + case swmm_LINK_OFFSET2: + return link->offset2 * UCF(LENGTH); + case swmm_LINK_INITIAL_FLOW: + return link->q0 * UCF(FLOW); + case swmm_LINK_FLOW_LIMIT: + return link->qLimit * UCF(FLOW); + case swmm_LINK_INLET_LOSS: + return link->cLossInlet; + case swmm_LINK_OUTLET_LOSS: + return link->cLossOutlet; + case swmm_LINK_AVERAGE_LOSS: + return link->cLossAvg; + case swmm_LINK_SEEPAGE_RATE: + return link->seepRate * UCF(RAINFALL); + case swmm_LINK_HAS_FLAPGATE: + return (link->hasFlapGate > 0); + case swmm_LINK_POLLUTANT_CONCENTRATION: + if (subIndex < 0 || subIndex >= Nobjects[POLLUT]) + return ERR_API_OBJECT_INDEX; + return link->newQual[subIndex]; + case swmm_LINK_POLLUTANT_LOAD: + if (subIndex < 0 || subIndex >= Nobjects[POLLUT]) + return ERR_API_OBJECT_INDEX; + return link->totalLoad[subIndex]; + case swmm_LINK_POLLUTANT_LATMASS_FLUX: + if (subIndex < 0 || subIndex >= Nobjects[POLLUT]) + return ERR_API_OBJECT_INDEX; + return link->apiExtQualMassFlux[subIndex]; + default: + return ERR_API_OBJECT_TYPE; } } @@ -1312,51 +1909,89 @@ double getSystemValue(int property) // Output: returns current property value or an error code // Purpose: retrieves current value of a system property. { - switch (property) - { - case swmm_STARTDATE: - return StartDateTime; - case swmm_ENDDATE: - return EndDateTime; - case swmm_REPORTSTART: - return ReportStart; - case swmm_CURRENTDATE: - return StartDateTime + ElapsedTime; - case swmm_ELAPSEDTIME: - return ElapsedTime; - case swmm_ROUTESTEP: - return RouteStep; - case swmm_MAXROUTESTEP: - return getMaxRouteStep(); - case swmm_REPORTSTEP: - return ReportStep; - case swmm_TOTALSTEPS: - return Nperiods; - case swmm_NOREPORT: - return RptFlags.disabled; - case swmm_FLOWUNITS: - return FlowUnits; - case swmm_UNITSYSTEM: - return UnitSystem; - case swmm_SURCHARGEMETHOD: - return SurchargeMethod; - case swmm_ALLOWPONDING: - return AllowPonding; - case swmm_INERTIADAMPING: - return InertDamping; + switch (property) + { + case swmm_STARTDATE: + return StartDateTime; + case swmm_CURRENTDATE: + return StartDateTime + ElapsedTime; + case swmm_ELAPSEDTIME: + return ElapsedTime; + case swmm_ROUTESTEP: + return RouteStep; + case swmm_MAXROUTESTEP: + return getMaxRouteStep(); + case swmm_REPORTSTEP: + return ReportStep; + case swmm_TOTALSTEPS: + return Nperiods; + case swmm_NOREPORT: + return RptFlags.disabled; + case swmm_FLOWUNITS: + return FlowUnits; + case swmm_ENDDATE: + return EndDateTime; + case swmm_REPORTSTART: + return ReportStart; + case swmm_UNITSYSTEM: + return UnitSystem; + case swmm_SURCHARGEMETHOD: + return SurchargeMethod; + case swmm_ALLOWPONDING: + return AllowPonding; + case swmm_INERTIADAMPING: + return InertDamping; case swmm_NORMALFLOWLTD: return NormalFlowLtd; case swmm_SKIPSTEADYSTATE: return SkipSteadyState; case swmm_IGNORERAINFALL: - return IgnoreRainfall; + return IgnoreRainfall; + case swmm_IGNORERDII: + return IgnoreRDII; + case swmm_IGNORESNOWMELT: + return IgnoreSnowmelt; + case swmm_IGNOREGROUNDWATER: + return IgnoreGwater; + case swmm_IGNOREROUTING: + return IgnoreRouting; + case swmm_IGNOREQUALITY: + return IgnoreQuality; case swmm_RULESTEP: - return RuleStep; + return RuleStep; case swmm_SWEEPSTART: return SweepStart; - default: - return ERR_API_PROPERTY_TYPE; - } + case swmm_SWEEPEND: + return SweepEnd; + case swmm_MAXTRIALS: + return MaxTrials; + case swmm_NUMTHREADS: + return NumThreads; + case swmm_MINROUTESTEP: + return MinRouteStep; + case swmm_LENGTHENINGSTEP: + return LengtheningStep; + case swmm_STARTDRYDAYS: + return StartDryDays; + case swmm_COURANTFACTOR: + return CourantFactor; + case swmm_MINSURFAREA: + return MinSurfArea * UCF(LENGTH) * UCF(LENGTH); + case swmm_MINSLOPE: + return MinSlope; + case swmm_RUNOFFERROR: + return RunoffError; + case swmm_FLOWERROR: + return FlowError; + case swmm_HEADTOL: + return HeadTol * UCF(LENGTH); + case swmm_SYSFLOWTOL: + return SysFlowTol; + case swmm_LATFLOWTOL: + return LatFlowTol; + default: + return ERR_API_PROPERTY_TYPE; + } } //============================================================================= @@ -1383,7 +2018,7 @@ int setOutfallStage(int index, double value) // Output: returns an error code // Purpose: sets the value of an outfall node's fixed stage. { - TNode* node; + TNode *node; if (index < 0 || index >= Nobjects[NODE]) return ERR_API_OBJECT_INDEX; @@ -1407,12 +2042,15 @@ int setLinkSetting(int index, double value) // Output: returns an error code // Purpose: sets the value of a link's setting. { - TLink* link; + TLink *link; + char control_rule_label[9] = "SWMM API"; + double currentTime; + if (index < 0 || index >= Nobjects[LINK]) return ERR_API_OBJECT_INDEX; link = &Link[index]; - if (value < 0.0 || link->type == CONDUIT) + if (value < 0.0 || link->type == CONDUIT) return ERR_API_OBJECT_INDEX; if (link->type != PUMP && value > 1.0) @@ -1422,12 +2060,19 @@ int setLinkSetting(int index, double value) return 0; link->targetSetting = value; - + if (link->targetSetting * link->setting == 0.0) link->timeLastSet = StartDateTime + ElapsedTime; link_setSetting(index, 0.0); + // Add control action to RPT file if desired flagged + if (RptFlags.controls) + { + currentTime = getDateTime(NewRoutingTime); + report_writeControlAction(currentTime, Link[index].ID, value, control_rule_label); + } + return 0; } @@ -1437,7 +2082,7 @@ double getSavedDate(int period) // // Input: period = a reporting period (starting at 1) // Output: returns the date/time of the reporting period in decimal days -// Purpose: retrieves the date/time of a reporting period. +// Purpose: retrieves the date/time of a reporting period. { double days; output_readDateTime(period, &days); @@ -1457,25 +2102,26 @@ double getSavedSubcatchValue(int property, int index, int period) { // --- SubcatchResults array is defined in output.c and contains // computed results in user's units - extern float* SubcatchResults; + extern float *SubcatchResults; // --- order in which subcatchment was saved to output results file int outIndex = Subcatch[index].rptFlag - 1; - if (outIndex < 0) return 0; + if (outIndex < 0) + return 0; output_readSubcatchResults(period, outIndex); switch (property) { - case swmm_SUBCATCH_RAINFALL: - return SubcatchResults[SUBCATCH_RAINFALL]; - case swmm_SUBCATCH_EVAP: - return SubcatchResults[SUBCATCH_EVAP]; - case swmm_SUBCATCH_INFIL: - return SubcatchResults[SUBCATCH_INFIL]; - case swmm_SUBCATCH_RUNOFF: - return SubcatchResults[SUBCATCH_RUNOFF]; - default: - return 0; + case swmm_SUBCATCH_RAINFALL: + return SubcatchResults[SUBCATCH_RAINFALL]; + case swmm_SUBCATCH_EVAP: + return SubcatchResults[SUBCATCH_EVAP]; + case swmm_SUBCATCH_INFIL: + return SubcatchResults[SUBCATCH_INFIL]; + case swmm_SUBCATCH_RUNOFF: + return SubcatchResults[SUBCATCH_RUNOFF]; + default: + return 0; } } @@ -1492,11 +2138,12 @@ double getSavedNodeValue(int property, int index, int period) { // --- NodeResults array is defined in output.c and contains // computed results in user's units - extern float* NodeResults; + extern float *NodeResults; // --- order in which node was saved to output results file int outIndex = Node[index].rptFlag - 1; - if (outIndex < 0) return 0; + if (outIndex < 0) + return 0; output_readNodeResults(period, outIndex); switch (property) @@ -1533,11 +2180,12 @@ double getSavedLinkValue(int property, int index, int period) // --- LinkResults array is defined in output.c and contains // computed results in user's units - extern float* LinkResults; + extern float *LinkResults; // --- order in which link was saved to output results file - int outIndex = Link[index].rptFlag - 1; - if (outIndex < 0) return 0; + int outIndex = Link[index].rptFlag - 1; + if (outIndex < 0) + return 0; output_readLinkResults(period, outIndex); switch (property) @@ -1590,7 +2238,7 @@ int setRoutingStep(double value) CourantFactor = 0.0; RouteStep = value; - + return 0; } @@ -1608,36 +2256,38 @@ int setSystemValue(int property, double value) if (IsStartedFlag) return ERR_API_NOT_ENDED; - switch (property) - { + switch (property) + { + case swmm_STARTDATE: + StartDateTime = value; + datetime_decodeDate(value, &y, &m, &d); + datetime_decodeTime(value, &h, &min, &s); + StartDate = datetime_encodeDate(y, m, d); + StartTime = datetime_encodeTime(h, min, s); + TotalDuration = floor((EndDate - StartDate) * SECperDAY + (EndTime - StartTime) * SECperDAY); + // convert total duration to milliseconds + TotalDuration *= 1000.0; + return 0; case swmm_ROUTESTEP: return setRoutingStep(value); + case swmm_REPORTSTEP: if (value > 0) { ReportStep = (int)value; return 0; } - else - { - return ERR_API_PROPERTY_VALUE; - } - + else + { + return ERR_API_PROPERTY_VALUE; + } + case swmm_NOREPORT: RptFlags.disabled = (value > 0.0); return 0; - case swmm_STARTDATE: - StartDateTime = value; - datetime_decodeDate(value, &y, &m, &d); - datetime_decodeTime(value, &h, &min, &s); - StartDate = datetime_encodeDate(y, m, d); - StartTime = datetime_encodeTime(h, min, s); - TotalDuration = floor((EndDate - StartDate) * SECperDAY + (EndTime - StartTime) * SECperDAY); - // convert total duration to milliseconds - TotalDuration *= 1000.0; - return 0; + case swmm_ENDDATE: - EndDateTime = value; + EndDateTime = value; datetime_decodeDate(value, &y, &m, &d); datetime_decodeTime(value, &h, &min, &s); EndDate = datetime_encodeDate(y, m, d); @@ -1653,11 +2303,167 @@ int setSystemValue(int property, double value) ReportStartDate = datetime_encodeDate(y, m, d); ReportStartTime = datetime_encodeTime(h, min, s); return 0; - + case swmm_NUMTHREADS: + // possible over allocation of threads but we trust the user to know what they are doing. Limit to max threads. + NumThreads = max(1, min((int)value, omp_get_max_threads())); + return 0; + case swmm_SURCHARGEMETHOD: + if (value >= EXTRAN && value <= SLOT) + { + SurchargeMethod = (int)value; + return 0; + } + else + { + return ERR_API_PROPERTY_VALUE; + } + case swmm_ALLOWPONDING: + AllowPonding = (value > 0.0); + return 0; + case swmm_INERTIADAMPING: + if (value >= NO_DAMPING && value <= FULL_DAMPING) + { + InertDamping = (int)value; + return 0; + } + else + { + return ERR_API_PROPERTY_VALUE; + } + case swmm_NORMALFLOWLTD: + if (value >= SLOPE && value <= NEITHER) + { + NormalFlowLtd = (int)value; + return 0; + } + else + { + return ERR_API_PROPERTY_VALUE; + } + case swmm_SKIPSTEADYSTATE: + SkipSteadyState = (value > 0.0); + return 0; + case swmm_IGNORERAINFALL: + IgnoreRainfall = (value > 0.0); + return 0; + case swmm_IGNORERDII: + IgnoreRDII = (value > 0.0); + return 0; + case swmm_IGNORESNOWMELT: + IgnoreSnowmelt = (value > 0.0); + return 0; + case swmm_IGNOREGROUNDWATER: + IgnoreGwater = (value > 0.0); + return 0; + case swmm_IGNOREROUTING: + IgnoreRouting = (value > 0.0); + return 0; + case swmm_IGNOREQUALITY: + IgnoreQuality = (value > 0.0); + return 0; + case swmm_RULESTEP: + if (value > 0) + { + RuleStep = (int)value; + return 0; + } + else + { + return ERR_API_PROPERTY_VALUE; + } + case swmm_SWEEPSTART: + if (value >= 0.0 && value <= 365) + { + SweepStart = (int)value; + return 0; + } + else + { + return ERR_API_PROPERTY_VALUE; + } + case swmm_SWEEPEND: + if (value >= 0.0 && value <= 365) + { + SweepEnd = (int)value; + return 0; + } + else + { + return ERR_API_PROPERTY_VALUE; + } + case swmm_MAXTRIALS: + if (value >= 2) + { + MaxTrials = (int)value; + return 0; + } + else + { + return ERR_API_PROPERTY_VALUE; + } + case swmm_MINROUTESTEP: + if (value > 0) + { + MinRouteStep = value; + return 0; + } + else + { + return ERR_API_PROPERTY_VALUE; + } + case swmm_LENGTHENINGSTEP: + if (value > 0) + { + LengtheningStep = value; + return 0; + } + else + { + return ERR_API_PROPERTY_VALUE; + } + case swmm_STARTDRYDAYS: + if (value >= 0) + { + StartDryDays = value; + return 0; + } + else + { + return ERR_API_PROPERTY_VALUE; + } + case swmm_COURANTFACTOR: + if (value > 0 && value <= 2.0) + { + CourantFactor = value; + return 0; + } + else + { + return ERR_API_PROPERTY_VALUE; + } + case swmm_MINSURFAREA: + if (value >= 0) + { + MinSurfArea = value / UCF(LENGTH) / UCF(LENGTH); + return 0; + } + else + { + return ERR_API_PROPERTY_VALUE; + } + case swmm_MINSLOPE: + if (value >= 0 && value < 100) + { + MinSlope = value; + return 0; + } + else + { + return ERR_API_PROPERTY_VALUE; + } default: return ERR_API_PROPERTY_TYPE; - } - + } } //============================================================================= @@ -1672,8 +2478,10 @@ double UCF(int u) // units to user's units // { - if ( u < FLOW ) return Ucf[u][UnitSystem]; - else return Qcf[FlowUnits]; + if (u < FLOW) + return Ucf[u][UnitSystem]; + else + return Qcf[FlowUnits]; } //============================================================================= @@ -1704,7 +2512,7 @@ size_t sstrncpy(char *dest, const char *src, size_t n) //============================================================================= -size_t sstrcat(char* dest, const char* src, size_t size) +size_t sstrcat(char *dest, const char *src, size_t size) // // Input: dest = string to be appended // src = string to append to dest @@ -1739,7 +2547,7 @@ size_t sstrcat(char* dest, const char* src, size_t size) //============================================================================= -int strcomp(const char *s1, const char *s2) +int strcomp(const char *s1, const char *s2) // // Input: s1 = a character string // s2 = a character string @@ -1750,14 +2558,15 @@ int strcomp(const char *s1, const char *s2) int i; for (i = 0; UCHAR(s1[i]) == UCHAR(s2[i]); i++) { - if (!s1[i+1] && !s2[i+1]) return(1); + if (!s1[i + 1] && !s2[i + 1]) + return (1); } - return(0); + return (0); } //============================================================================= -char* getTempFileName(char* fname) +char *getTempFileName(char *fname) // // Input: fname = file name string (with max size of MAXFNAME) // Output: returns pointer to file name @@ -1767,8 +2576,8 @@ char* getTempFileName(char* fname) // For Windows systems: #ifdef WINDOWS - char* name = NULL; - char* dir = NULL; + char *name = NULL; + char *dir = NULL; // --- set dir to user's choice of a temporary directory if (strlen(TempDir) > 0) @@ -1779,11 +2588,14 @@ char* getTempFileName(char* fname) // --- use _tempnam to get a pointer to an unused file name name = _tempnam(dir, "swmm"); - if (name == NULL) return NULL; + if (name == NULL) + return NULL; // --- copy the file name to fname - if (strlen(name) <= MAXFNAME) sstrncpy(fname, name, MAXFNAME); - else fname = NULL; + if (strlen(name) <= MAXFNAME) + sstrncpy(fname, name, MAXFNAME); + else + fname = NULL; // --- free the pointer returned by _tempnam free(name); @@ -1804,7 +2616,7 @@ char* getTempFileName(char* fname) //============================================================================= -void getElapsedTime(DateTime aDate, int* days, int* hrs, int* mins) +void getElapsedTime(DateTime aDate, int *days, int *hrs, int *mins) // // Input: aDate = simulation calendar date + time // Output: days, hrs, mins = elapsed days, hours & minutes for aDate @@ -1814,10 +2626,10 @@ void getElapsedTime(DateTime aDate, int* days, int* hrs, int* mins) DateTime x; int secs; x = aDate - ReportStart; - if ( x <= 0.0 ) + if (x <= 0.0) { *days = 0; - *hrs = 0; + *hrs = 0; *mins = 0; } else @@ -1837,27 +2649,30 @@ DateTime getDateTime(double elapsedMsec) // simulation time. // { - return datetime_addSeconds(StartDateTime, (elapsedMsec+1)/1000.0); + return datetime_addSeconds(StartDateTime, (elapsedMsec + 1) / 1000.0); } //============================================================================= -static int isRelativePath(const char* fname) +static int isRelativePath(const char *fname) // // Input: fname = a file name // Output: returns 1 if fname's path is relative or 0 if absolute // Purpose: determines if a file name contains a relative or absolute path. // { - if (strchr(fname, ':')) return 0; - if (fname[0] == '\\') return 0; - if (fname[0] == '/') return 0; + if (strchr(fname, ':')) + return 0; + if (fname[0] == '\\') + return 0; + if (fname[0] == '/') + return 0; return 1; } //============================================================================= -void getAbsolutePath(const char* fname, char* absPath, size_t size) +void getAbsolutePath(const char *fname, char *absPath, size_t size) // // Input: fname = a file name // absPath = string to hold the absolute path @@ -1867,7 +2682,7 @@ void getAbsolutePath(const char* fname, char* absPath, size_t size) // Purpose: finds the full path of the directory for file fname // { - char* endOfDir; + char *endOfDir; // --- case of empty file anme if (fname == NULL || strlen(fname) == 0) @@ -1876,11 +2691,11 @@ void getAbsolutePath(const char* fname, char* absPath, size_t size) // --- if fname has a relative path then retrieve its full path if (isRelativePath(fname)) { - #ifdef WINDOWS - GetFullPathName((LPCSTR)fname, (DWORD)size, (LPSTR)absPath, NULL); - #else - realpath(fname, absPath); - #endif +#ifdef WINDOWS + GetFullPathName((LPCSTR)fname, (DWORD)size, (LPSTR)absPath, NULL); +#else + realpath(fname, absPath); +#endif } // --- otherwise copy fname to absPath @@ -1889,21 +2704,21 @@ void getAbsolutePath(const char* fname, char* absPath, size_t size) sstrncpy(absPath, fname, strlen(fname)); } - // --- trim file name portion of absPath - #ifdef WINDOWS - endOfDir = strrchr(absPath, '\\'); - #else - endOfDir = strrchr(absPath, '/'); - #endif +// --- trim file name portion of absPath +#ifdef WINDOWS + endOfDir = strrchr(absPath, '\\'); +#else + endOfDir = strrchr(absPath, '/'); +#endif if (endOfDir) { - *(endOfDir+1) = '\0'; + *(endOfDir + 1) = '\0'; } } //============================================================================= -char* addAbsolutePath(char* fname) +char *addAbsolutePath(char *fname) // // Input: fname = a file name // Output: returns fname with a full path prepended to it @@ -1911,7 +2726,7 @@ char* addAbsolutePath(char* fname) // Note: fname must have been dimensioned to accept MAXFNAME characters. // { - size_t n; + size_t n; char buffer[MAXFNAME]; if (isRelativePath(fname)) { @@ -1924,21 +2739,21 @@ char* addAbsolutePath(char* fname) //============================================================================= -void writecon(const char *s) +void writecon(const char *s) // // Input: s = a character string // Output: none // Purpose: writes string of characters to the console. // { - fprintf(stdout,"%s",s); + fprintf(stdout, "%s", s); fflush(stdout); } //============================================================================= #ifdef EXH -int xfilter(int xc, char* module, double elapsedTime, long step) +int xfilter(int xc, char *module, double elapsedTime, long step) // // Input: xc = exception code // module = name of code module where exception was handled @@ -1949,10 +2764,10 @@ int xfilter(int xc, char* module, double elapsedTime, long step) // under Windows and the Microsoft C compiler. // { - int rc; // result code - long hour; // current hour of simulation - char msg[40]; // exception type text - char xmsg[240]; // error message text + int rc; // result code + long hour; // current hour of simulation + char msg[40]; // exception type text + char xmsg[240]; // error message text switch (xc) { case EXCEPTION_ACCESS_VIOLATION: @@ -1998,8 +2813,8 @@ int xfilter(int xc, char* module, double elapsedTime, long step) hour = (long)(elapsedTime / 1000.0 / 3600.0); sprintf(xmsg, "%sin module %s at step %ld, hour %ld", msg, module, step, hour); - if ( rc == EXCEPTION_EXECUTE_HANDLER || - ++ExceptionCount >= MAX_EXCEPTIONS ) + if (rc == EXCEPTION_EXECUTE_HANDLER || + ++ExceptionCount >= MAX_EXCEPTIONS) { strcat(xmsg, " --- execution halted."); rc = EXCEPTION_EXECUTE_HANDLER;