From beef5f53518860d6054233108dbbff7c861e4dae Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 11 Jul 2019 10:25:00 -0400 Subject: [PATCH 001/266] Configuring cmake Adding support for cmake build and install --- CMakeLists.txt | 47 ++++++++++++++++++++++++++++++++ {src => include}/swmm5.h | 0 make.bat | 51 +++++++++++++++++++++++++++++++++++ src/library/CMakeLists.txt | 34 +++++++++++++++++++++++ src/{ => library}/Roadmap.txt | 0 src/{ => library}/climate.c | 0 src/{ => library}/consts.h | 0 src/{ => library}/controls.c | 0 src/{ => library}/culvert.c | 0 src/{ => library}/datetime.c | 0 src/{ => library}/datetime.h | 0 src/{ => library}/dwflow.c | 0 src/{ => library}/dynwave.c | 0 src/{ => library}/enums.h | 0 src/{ => library}/error.c | 0 src/{ => library}/error.h | 0 src/{ => library}/exfil.c | 0 src/{ => library}/exfil.h | 0 src/{ => library}/findroot.c | 0 src/{ => library}/findroot.h | 0 src/{ => library}/flowrout.c | 0 src/{ => library}/forcmain.c | 0 src/{ => library}/funcs.h | 0 src/{ => library}/gage.c | 0 src/{ => library}/globals.h | 0 src/{ => library}/gwater.c | 0 src/{ => library}/hash.c | 0 src/{ => library}/hash.h | 0 src/{ => library}/headers.h | 0 src/{ => library}/hotstart.c | 0 src/{ => library}/iface.c | 0 src/{ => library}/infil.c | 0 src/{ => library}/infil.h | 0 src/{ => library}/inflow.c | 0 src/{ => library}/input.c | 0 src/{ => library}/inputrpt.c | 0 src/{ => library}/keywords.c | 0 src/{ => library}/keywords.h | 0 src/{ => library}/kinwave.c | 0 src/{ => library}/landuse.c | 0 src/{ => library}/lid.c | 0 src/{ => library}/lid.h | 0 src/{ => library}/lidproc.c | 0 src/{ => library}/link.c | 0 src/{ => library}/macros.h | 0 src/{ => library}/massbal.c | 0 src/{ => library}/mathexpr.c | 0 src/{ => library}/mathexpr.h | 0 src/{ => library}/mempool.c | 0 src/{ => library}/mempool.h | 0 src/{ => library}/node.c | 0 src/{ => library}/objects.h | 0 src/{ => library}/odesolve.c | 0 src/{ => library}/odesolve.h | 0 src/{ => library}/output.c | 0 src/{ => library}/project.c | 0 src/{ => library}/qualrout.c | 0 src/{ => library}/rain.c | 0 src/{ => library}/rdii.c | 0 src/{ => library}/report.c | 0 src/{ => library}/roadway.c | 0 src/{ => library}/routing.c | 0 src/{ => library}/runoff.c | 0 src/{ => library}/shape.c | 0 src/{ => library}/snow.c | 0 src/{ => library}/stats.c | 0 src/{ => library}/statsrpt.c | 0 src/{ => library}/subcatch.c | 0 src/{ => library}/surfqual.c | 0 src/{ => library}/swmm5.c | 0 src/{ => library}/swmm5.def | 0 src/{ => library}/table.c | 0 src/{ => library}/text.h | 0 src/{ => library}/toposort.c | 0 src/{ => library}/transect.c | 0 src/{ => library}/treatmnt.c | 0 src/{ => library}/xsect.c | 0 src/{ => library}/xsect.dat | 0 src/run/CMakeLists.txt | 24 +++++++++++++++++ src/{ => run}/main.c | 0 80 files changed, 156 insertions(+) create mode 100644 CMakeLists.txt rename {src => include}/swmm5.h (100%) create mode 100644 make.bat create mode 100644 src/library/CMakeLists.txt rename src/{ => library}/Roadmap.txt (100%) rename src/{ => library}/climate.c (100%) rename src/{ => library}/consts.h (100%) rename src/{ => library}/controls.c (100%) rename src/{ => library}/culvert.c (100%) rename src/{ => library}/datetime.c (100%) rename src/{ => library}/datetime.h (100%) rename src/{ => library}/dwflow.c (100%) rename src/{ => library}/dynwave.c (100%) rename src/{ => library}/enums.h (100%) rename src/{ => library}/error.c (100%) rename src/{ => library}/error.h (100%) rename src/{ => library}/exfil.c (100%) rename src/{ => library}/exfil.h (100%) rename src/{ => library}/findroot.c (100%) rename src/{ => library}/findroot.h (100%) rename src/{ => library}/flowrout.c (100%) rename src/{ => library}/forcmain.c (100%) rename src/{ => library}/funcs.h (100%) rename src/{ => library}/gage.c (100%) rename src/{ => library}/globals.h (100%) rename src/{ => library}/gwater.c (100%) rename src/{ => library}/hash.c (100%) rename src/{ => library}/hash.h (100%) rename src/{ => library}/headers.h (100%) rename src/{ => library}/hotstart.c (100%) rename src/{ => library}/iface.c (100%) rename src/{ => library}/infil.c (100%) rename src/{ => library}/infil.h (100%) rename src/{ => library}/inflow.c (100%) rename src/{ => library}/input.c (100%) rename src/{ => library}/inputrpt.c (100%) rename src/{ => library}/keywords.c (100%) rename src/{ => library}/keywords.h (100%) rename src/{ => library}/kinwave.c (100%) rename src/{ => library}/landuse.c (100%) rename src/{ => library}/lid.c (100%) rename src/{ => library}/lid.h (100%) rename src/{ => library}/lidproc.c (100%) rename src/{ => library}/link.c (100%) rename src/{ => library}/macros.h (100%) rename src/{ => library}/massbal.c (100%) rename src/{ => library}/mathexpr.c (100%) rename src/{ => library}/mathexpr.h (100%) rename src/{ => library}/mempool.c (100%) rename src/{ => library}/mempool.h (100%) rename src/{ => library}/node.c (100%) rename src/{ => library}/objects.h (100%) rename src/{ => library}/odesolve.c (100%) rename src/{ => library}/odesolve.h (100%) rename src/{ => library}/output.c (100%) rename src/{ => library}/project.c (100%) rename src/{ => library}/qualrout.c (100%) rename src/{ => library}/rain.c (100%) rename src/{ => library}/rdii.c (100%) rename src/{ => library}/report.c (100%) rename src/{ => library}/roadway.c (100%) rename src/{ => library}/routing.c (100%) rename src/{ => library}/runoff.c (100%) rename src/{ => library}/shape.c (100%) rename src/{ => library}/snow.c (100%) rename src/{ => library}/stats.c (100%) rename src/{ => library}/statsrpt.c (100%) rename src/{ => library}/subcatch.c (100%) rename src/{ => library}/surfqual.c (100%) rename src/{ => library}/swmm5.c (100%) rename src/{ => library}/swmm5.def (100%) rename src/{ => library}/table.c (100%) rename src/{ => library}/text.h (100%) rename src/{ => library}/toposort.c (100%) rename src/{ => library}/transect.c (100%) rename src/{ => library}/treatmnt.c (100%) rename src/{ => library}/xsect.c (100%) rename src/{ => library}/xsect.dat (100%) create mode 100644 src/run/CMakeLists.txt rename src/{ => run}/main.c (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..270979aaf --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,47 @@ +# +# CMakeLists.txt - CMake configuration file for swmm-solver +# +# Created: July 11, 2019 +# +# Modified by: Michael E. Tryby +# US EPA ORD/NRMRL +# + +cmake_minimum_required (VERSION 3.0) + +project(swmm-solver + VERSION 5.1.13 + LANGUAGES C CXX + ) + +# Sets default install prefix when cmakecache is initialized for first time +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "..." FORCE) +endif() + +# Define install locations (will be prepended by install prefix) +set(TOOL_DIST "bin") +set(INCLUDE_DIST "include") +set(LIBRARY_DIST "lib") +set(CONFIG_DIST "cmake") + +# Add project subdirectories +add_subdirectory(src/library) +add_subdirectory(src/run) + +# Create target import scripts so other cmake projects can use swmm libraries +install( + EXPORT + swmm5Targets + DESTINATION + "${CONFIG_DIST}" + FILE + swmm5-config.cmake + ) + +# Configure CPack driven installer package +set(CPACK_GENERATOR "ZIP") +set(CPACK_PACKAGE_VENDOR "US_EPA") +set(CPACK_ARCHIVE_FILE_NAME "swmm") + +include(CPack) diff --git a/src/swmm5.h b/include/swmm5.h similarity index 100% rename from src/swmm5.h rename to include/swmm5.h diff --git a/make.bat b/make.bat new file mode 100644 index 000000000..14c90ce7a --- /dev/null +++ b/make.bat @@ -0,0 +1,51 @@ +:: +:: makefile.bat - build swmm-solver +:: +:: Date Created: 7/11/2019 +:: +:: Author: Michael E. Tryby +:: US EPA - ORD/NRMRL +:: +:: Requires: +:: Build Tools for Visual Studio +:: https://visualstudio.microsoft.com/downloads/ +:: +:: CMake +:: https://cmake.org/download/ +:: +:: Note: +:: This script must be located at the root of the project folder +:: in order to work properly. +:: + +@echo off +echo INFO: Building swmm-solver ... + +set GENERATOR=Visual Studio 15 2017 + +:: Determine project path and strip trailing \ from path +set "PROJECT_PATH=%~dp0" +IF %PROJECT_PATH:~-1%==\ set "PROJECT_PATH=%PROJECT_PATH:~0,-1%" + +:: check for requirements +WHERE cmake +IF %ERRORLEVEL% NEQ 0 ECHO cmake not installed & EXIT /B 1 + +:: generate build system +IF exist buildprod_win32 ( cd buildprod_win32 ) ELSE ( mkdir buildprod_win32 & cd buildprod_win32 ) +cmake -G"%GENERATOR%" .. + +cd .. +IF exist buildprod_win64 ( cd buildprod_win64 ) ELSE ( mkdir buildprod_win64 & cd buildprod_win64 ) +cmake -G"%GENERATOR% Win64" .. + +:: perform build +cmake --build . --config Release --target install +cpack + +cd ..\buildprod_win32 +cmake --build . --config Release --target install +cpack + +:: return to project root +cd %PROJECT_PATH% diff --git a/src/library/CMakeLists.txt b/src/library/CMakeLists.txt new file mode 100644 index 000000000..c0fd08b99 --- /dev/null +++ b/src/library/CMakeLists.txt @@ -0,0 +1,34 @@ +# +# CMakeLists.txt - CMake configuration file for swmm-solver/library +# +# Created: July 11, 2019 +# +# Modified by: Michael E. Tryby +# US EPA ORD/NRMRL +# + +# configure file groups +set(SWMM_PUBLIC_HEADERS + ${PROJECT_SOURCE_DIR}/include/swmm5.h + ) + +file(GLOB SWMM_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c *.h) + +add_library(swmm5 + SHARED + ${SWMM_SOURCES} + ) +target_include_directories(swmm5 + PUBLIC + $ + $ + ) + +install(TARGETS swmm5 EXPORT swmm5Targets + RUNTIME DESTINATION "${TOOL_DIST}" + LIBRARY DESTINATION "${TOOL_DIST}" + ARCHIVE DESTINATION "${LIBRARY_DIST}" + FRAMEWORK DESTINATION "${TOOL_DIST}" + ) + +install(FILES ${SWMM_PUBLIC_HEADERS} DESTINATION "${INCLUDE_DIST}") diff --git a/src/Roadmap.txt b/src/library/Roadmap.txt similarity index 100% rename from src/Roadmap.txt rename to src/library/Roadmap.txt diff --git a/src/climate.c b/src/library/climate.c similarity index 100% rename from src/climate.c rename to src/library/climate.c diff --git a/src/consts.h b/src/library/consts.h similarity index 100% rename from src/consts.h rename to src/library/consts.h diff --git a/src/controls.c b/src/library/controls.c similarity index 100% rename from src/controls.c rename to src/library/controls.c diff --git a/src/culvert.c b/src/library/culvert.c similarity index 100% rename from src/culvert.c rename to src/library/culvert.c diff --git a/src/datetime.c b/src/library/datetime.c similarity index 100% rename from src/datetime.c rename to src/library/datetime.c diff --git a/src/datetime.h b/src/library/datetime.h similarity index 100% rename from src/datetime.h rename to src/library/datetime.h diff --git a/src/dwflow.c b/src/library/dwflow.c similarity index 100% rename from src/dwflow.c rename to src/library/dwflow.c diff --git a/src/dynwave.c b/src/library/dynwave.c similarity index 100% rename from src/dynwave.c rename to src/library/dynwave.c diff --git a/src/enums.h b/src/library/enums.h similarity index 100% rename from src/enums.h rename to src/library/enums.h diff --git a/src/error.c b/src/library/error.c similarity index 100% rename from src/error.c rename to src/library/error.c diff --git a/src/error.h b/src/library/error.h similarity index 100% rename from src/error.h rename to src/library/error.h diff --git a/src/exfil.c b/src/library/exfil.c similarity index 100% rename from src/exfil.c rename to src/library/exfil.c diff --git a/src/exfil.h b/src/library/exfil.h similarity index 100% rename from src/exfil.h rename to src/library/exfil.h diff --git a/src/findroot.c b/src/library/findroot.c similarity index 100% rename from src/findroot.c rename to src/library/findroot.c diff --git a/src/findroot.h b/src/library/findroot.h similarity index 100% rename from src/findroot.h rename to src/library/findroot.h diff --git a/src/flowrout.c b/src/library/flowrout.c similarity index 100% rename from src/flowrout.c rename to src/library/flowrout.c diff --git a/src/forcmain.c b/src/library/forcmain.c similarity index 100% rename from src/forcmain.c rename to src/library/forcmain.c diff --git a/src/funcs.h b/src/library/funcs.h similarity index 100% rename from src/funcs.h rename to src/library/funcs.h diff --git a/src/gage.c b/src/library/gage.c similarity index 100% rename from src/gage.c rename to src/library/gage.c diff --git a/src/globals.h b/src/library/globals.h similarity index 100% rename from src/globals.h rename to src/library/globals.h diff --git a/src/gwater.c b/src/library/gwater.c similarity index 100% rename from src/gwater.c rename to src/library/gwater.c diff --git a/src/hash.c b/src/library/hash.c similarity index 100% rename from src/hash.c rename to src/library/hash.c diff --git a/src/hash.h b/src/library/hash.h similarity index 100% rename from src/hash.h rename to src/library/hash.h diff --git a/src/headers.h b/src/library/headers.h similarity index 100% rename from src/headers.h rename to src/library/headers.h diff --git a/src/hotstart.c b/src/library/hotstart.c similarity index 100% rename from src/hotstart.c rename to src/library/hotstart.c diff --git a/src/iface.c b/src/library/iface.c similarity index 100% rename from src/iface.c rename to src/library/iface.c diff --git a/src/infil.c b/src/library/infil.c similarity index 100% rename from src/infil.c rename to src/library/infil.c diff --git a/src/infil.h b/src/library/infil.h similarity index 100% rename from src/infil.h rename to src/library/infil.h diff --git a/src/inflow.c b/src/library/inflow.c similarity index 100% rename from src/inflow.c rename to src/library/inflow.c diff --git a/src/input.c b/src/library/input.c similarity index 100% rename from src/input.c rename to src/library/input.c diff --git a/src/inputrpt.c b/src/library/inputrpt.c similarity index 100% rename from src/inputrpt.c rename to src/library/inputrpt.c diff --git a/src/keywords.c b/src/library/keywords.c similarity index 100% rename from src/keywords.c rename to src/library/keywords.c diff --git a/src/keywords.h b/src/library/keywords.h similarity index 100% rename from src/keywords.h rename to src/library/keywords.h diff --git a/src/kinwave.c b/src/library/kinwave.c similarity index 100% rename from src/kinwave.c rename to src/library/kinwave.c diff --git a/src/landuse.c b/src/library/landuse.c similarity index 100% rename from src/landuse.c rename to src/library/landuse.c diff --git a/src/lid.c b/src/library/lid.c similarity index 100% rename from src/lid.c rename to src/library/lid.c diff --git a/src/lid.h b/src/library/lid.h similarity index 100% rename from src/lid.h rename to src/library/lid.h diff --git a/src/lidproc.c b/src/library/lidproc.c similarity index 100% rename from src/lidproc.c rename to src/library/lidproc.c diff --git a/src/link.c b/src/library/link.c similarity index 100% rename from src/link.c rename to src/library/link.c diff --git a/src/macros.h b/src/library/macros.h similarity index 100% rename from src/macros.h rename to src/library/macros.h diff --git a/src/massbal.c b/src/library/massbal.c similarity index 100% rename from src/massbal.c rename to src/library/massbal.c diff --git a/src/mathexpr.c b/src/library/mathexpr.c similarity index 100% rename from src/mathexpr.c rename to src/library/mathexpr.c diff --git a/src/mathexpr.h b/src/library/mathexpr.h similarity index 100% rename from src/mathexpr.h rename to src/library/mathexpr.h diff --git a/src/mempool.c b/src/library/mempool.c similarity index 100% rename from src/mempool.c rename to src/library/mempool.c diff --git a/src/mempool.h b/src/library/mempool.h similarity index 100% rename from src/mempool.h rename to src/library/mempool.h diff --git a/src/node.c b/src/library/node.c similarity index 100% rename from src/node.c rename to src/library/node.c diff --git a/src/objects.h b/src/library/objects.h similarity index 100% rename from src/objects.h rename to src/library/objects.h diff --git a/src/odesolve.c b/src/library/odesolve.c similarity index 100% rename from src/odesolve.c rename to src/library/odesolve.c diff --git a/src/odesolve.h b/src/library/odesolve.h similarity index 100% rename from src/odesolve.h rename to src/library/odesolve.h diff --git a/src/output.c b/src/library/output.c similarity index 100% rename from src/output.c rename to src/library/output.c diff --git a/src/project.c b/src/library/project.c similarity index 100% rename from src/project.c rename to src/library/project.c diff --git a/src/qualrout.c b/src/library/qualrout.c similarity index 100% rename from src/qualrout.c rename to src/library/qualrout.c diff --git a/src/rain.c b/src/library/rain.c similarity index 100% rename from src/rain.c rename to src/library/rain.c diff --git a/src/rdii.c b/src/library/rdii.c similarity index 100% rename from src/rdii.c rename to src/library/rdii.c diff --git a/src/report.c b/src/library/report.c similarity index 100% rename from src/report.c rename to src/library/report.c diff --git a/src/roadway.c b/src/library/roadway.c similarity index 100% rename from src/roadway.c rename to src/library/roadway.c diff --git a/src/routing.c b/src/library/routing.c similarity index 100% rename from src/routing.c rename to src/library/routing.c diff --git a/src/runoff.c b/src/library/runoff.c similarity index 100% rename from src/runoff.c rename to src/library/runoff.c diff --git a/src/shape.c b/src/library/shape.c similarity index 100% rename from src/shape.c rename to src/library/shape.c diff --git a/src/snow.c b/src/library/snow.c similarity index 100% rename from src/snow.c rename to src/library/snow.c diff --git a/src/stats.c b/src/library/stats.c similarity index 100% rename from src/stats.c rename to src/library/stats.c diff --git a/src/statsrpt.c b/src/library/statsrpt.c similarity index 100% rename from src/statsrpt.c rename to src/library/statsrpt.c diff --git a/src/subcatch.c b/src/library/subcatch.c similarity index 100% rename from src/subcatch.c rename to src/library/subcatch.c diff --git a/src/surfqual.c b/src/library/surfqual.c similarity index 100% rename from src/surfqual.c rename to src/library/surfqual.c diff --git a/src/swmm5.c b/src/library/swmm5.c similarity index 100% rename from src/swmm5.c rename to src/library/swmm5.c diff --git a/src/swmm5.def b/src/library/swmm5.def similarity index 100% rename from src/swmm5.def rename to src/library/swmm5.def diff --git a/src/table.c b/src/library/table.c similarity index 100% rename from src/table.c rename to src/library/table.c diff --git a/src/text.h b/src/library/text.h similarity index 100% rename from src/text.h rename to src/library/text.h diff --git a/src/toposort.c b/src/library/toposort.c similarity index 100% rename from src/toposort.c rename to src/library/toposort.c diff --git a/src/transect.c b/src/library/transect.c similarity index 100% rename from src/transect.c rename to src/library/transect.c diff --git a/src/treatmnt.c b/src/library/treatmnt.c similarity index 100% rename from src/treatmnt.c rename to src/library/treatmnt.c diff --git a/src/xsect.c b/src/library/xsect.c similarity index 100% rename from src/xsect.c rename to src/library/xsect.c diff --git a/src/xsect.dat b/src/library/xsect.dat similarity index 100% rename from src/xsect.dat rename to src/library/xsect.dat diff --git a/src/run/CMakeLists.txt b/src/run/CMakeLists.txt new file mode 100644 index 000000000..b4320380c --- /dev/null +++ b/src/run/CMakeLists.txt @@ -0,0 +1,24 @@ +# +# CMakeLists.txt - CMake configuration file for swmm-solver/run +# +# Created: July 11, 2019 +# +# Modified by: Michael E. Tryby +# US EPA ORD/NRMRL +# + + +# Creates the EPANET command line executable +add_executable(run_swmm + main.c + ) +target_include_directories(run_swmm + PUBLIC + include + ) +target_link_libraries(run_swmm + LINK_PUBLIC + swmm5 + ) + +install(TARGETS run_swmm DESTINATION "${TOOL_DIST}") diff --git a/src/main.c b/src/run/main.c similarity index 100% rename from src/main.c rename to src/run/main.c From 9ebc6704cba3d6cbb52f70d89294d35f6343e754 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 11 Jul 2019 11:18:40 -0400 Subject: [PATCH 002/266] Applying bug fixes Contents of zip archive attached to Issue #22 --- src/library/consts.h | 2 +- src/library/lid.c | 30 ++++++++++++++++-------------- src/library/node.c | 12 ++++++------ src/library/rain.c | 5 ++++- src/library/rdii.c | 6 +++--- src/library/report.c | 10 ++++++++-- src/library/runoff.c | 9 ++++++++- src/library/surfqual.c | 9 ++++++--- src/library/text.h | 2 +- 9 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/library/consts.h b/src/library/consts.h index 789eddea2..7c5b132c5 100644 --- a/src/library/consts.h +++ b/src/library/consts.h @@ -15,7 +15,7 @@ // General Constants //------------------ -#define VERSION 51013 +#define VERSION 51014 #define MAGICNUMBER 516114522 #define EOFMARK 0x1A // Use 0x04 for UNIX systems #define MAXTITLE 3 // Max. # title lines diff --git a/src/library/lid.c b/src/library/lid.c index f204cc5fb..da5f4d5f7 100644 --- a/src/library/lid.c +++ b/src/library/lid.c @@ -72,6 +72,10 @@ // control curve for underdrain flow. // - Support added for unclogging permeable pavement at fixed intervals. // - Support added for pollutant removal in underdrain flow. +// +// Build 5.1.014: +// - Fixed bug in creating LidProcs when there are no subcatchments. +// - Fixed bug in adding underdrain pollutant loads to mass balances. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -248,17 +252,19 @@ void lid_create(int lidCount, int subcatchCount) //... create LID groups GroupCount = subcatchCount; - if ( GroupCount == 0 ) return; - LidGroups = (TLidGroup *) calloc(GroupCount, sizeof(TLidGroup)); - if ( LidGroups == NULL ) + if ( GroupCount > 0 ) { - ErrorCode = ERR_MEMORY; - return; + LidGroups = (TLidGroup *) calloc(GroupCount, sizeof(TLidGroup)); + if ( LidGroups == NULL ) + { + ErrorCode = ERR_MEMORY; + return; + } } - + //... initialize LID groups for (j = 0; j < GroupCount; j++) LidGroups[j] = NULL; - + //... create LID objects if ( LidCount == 0 ) return; LidProcs = (TLidProc *) calloc(LidCount, sizeof(TLidProc)); @@ -1465,16 +1471,12 @@ void lid_addDrainLoads(int j, double c[], double tStep) { lidUnit = lidList->lidUnit; - //... skip LID unit if it sends its drain flow onto - // its subcatchment's pervious area - if (lidUnit->toPerv) continue; - //... see if unit's drain flow becomes external runoff isRunoffLoad = (lidUnit->drainNode >= 0 || lidUnit->drainSubcatch == j); - //... for each pollutant - for (p = 0; p < Nobjects[POLLUT]; p++) + //... for each pollutant not routed back on to subcatchment surface + if (!lidUnit->toPerv) for (p = 0; p < Nobjects[POLLUT]; p++) { //... get mass load flowing through the drain w = lidUnit->newDrainFlow * c[p] * tStep * LperFT3 * Pollut[p].mcf; @@ -1485,7 +1487,7 @@ void lid_addDrainLoads(int j, double c[], double tStep) //... update system mass balance totals massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, r*w); if (isRunoffLoad) - massbal_updateLoadingTotals(RUNOFF_LOAD, p, w*(1.0-r)); + massbal_updateLoadingTotals(RUNOFF_LOAD, p, w*(1.0 - r)); } // process next LID unit in the group diff --git a/src/library/node.c b/src/library/node.c index 2917b5749..58549eb30 100644 --- a/src/library/node.c +++ b/src/library/node.c @@ -27,6 +27,9 @@ // Build 5.1.013: // - A surcharge depth can now be applied to storage nodes. // - A negative inflow is now assigned to an Outfall node with backflow. +// +// Build 5.1.014: +// - Fixed bug in storage_losses() that affected storage exfiltration. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -916,9 +919,6 @@ double storage_getLosses(int j, double tStep) double lossRatio; TExfil* exfil; - // --- if node has some stored volume - if ( Node[j].newVolume > FUDGE ) - { // --- get node's evap. rate (ft/s) & exfiltration object k = Node[j].subIndex; evapRate = Evap.rate * Storage[k].fEvap; @@ -932,7 +932,8 @@ double storage_getLosses(int j, double tStep) area = storage_getSurfArea(j, depth); // --- compute evap rate over this area (cfs) - evapRate = area * evapRate; + if (Node[j].newVolume > FUDGE) + evapRate = area * evapRate; // --- find exfiltration rate (cfs) through bottom and side banks if ( exfil != NULL ) @@ -949,8 +950,7 @@ double storage_getLosses(int j, double tStep) exfilRate *= lossRatio; } } - } - + // --- save evap & infil losses at the node Storage[Node[j].subIndex].evapLoss = evapRate * tStep; Storage[Node[j].subIndex].exfilLoss = exfilRate * tStep; diff --git a/src/library/rain.c b/src/library/rain.c index 03af41187..75b1e9b80 100644 --- a/src/library/rain.c +++ b/src/library/rain.c @@ -48,6 +48,9 @@ // // Release 5.1.013: // - Variable x properly initialized with float value in readNwsOnlineValue(). +// +// Release 5.1.014: +// - Fixed indexing bug in rainFileConflict() function. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -286,7 +289,7 @@ int rainFileConflict(int i) int j; char* staID = Gage[i].staID; char* fname = Gage[i].fname; - for (j = 1; j < i; j++) + for (j = 0; j < i; j++) { if ( strcomp(Gage[j].staID, staID) && !strcomp(Gage[j].fname, fname) ) { diff --git a/src/library/rdii.c b/src/library/rdii.c index 6da1d7a23..a2a65dd56 100644 --- a/src/library/rdii.c +++ b/src/library/rdii.c @@ -19,6 +19,8 @@ // - Ignore RDII option implemented. // - Rainfall climate adjustment implemented. // +// Build 5.1.014: +// - Fixes bug related to isUsed property of a unit hydrograph's rain gage. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -246,6 +248,7 @@ int rdii_readUnitHydParams(char* tok[], int ntoks) g = project_findObject(GAGE, tok[1]); if ( g < 0 ) return error_setInpError(ERR_NAME, tok[1]); UnitHyd[j].rainGage = g; + Gage[g].isUsed = TRUE; return 0; } else if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); @@ -1050,14 +1053,11 @@ void initGageData() g = UnitHyd[i].rainGage; if ( g >= 0 ) { - Gage[g].isUsed = TRUE; - // --- if UH's gage uses same time series as a previous gage, // then assign the latter gage to the UH if ( Gage[g].coGage >= 0 ) { UnitHyd[i].rainGage = Gage[g].coGage; - Gage[Gage[g].coGage].isUsed = TRUE; } } } diff --git a/src/library/report.c b/src/library/report.c index a78adb232..baf888b5b 100644 --- a/src/library/report.c +++ b/src/library/report.c @@ -40,6 +40,8 @@ // - Name of surcharge method reported in report_writeOptions(). // - Missing format specifier added to fprintf() in report_writeErrorCode. // +// Build 5.1.014: +// - Fixed bug in confusing keywords with ID names in report_readOptions(). //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -150,8 +152,12 @@ int report_readOptions(char* tok[], int ntoks) default: return error_setInpError(ERR_KEYWORD, tok[1]); } - k = (char)findmatch(tok[1], NoneAllWords); - if ( k < 0 ) + + if (strcomp(tok[1], w_NONE)) + k = NONE; + else if (strcomp(tok[1], w_ALL)) + k = ALL; + else { k = SOME; for (t = 1; t < ntoks; t++) diff --git a/src/library/runoff.c b/src/library/runoff.c index bad45a44a..88ad36ce7 100644 --- a/src/library/runoff.c +++ b/src/library/runoff.c @@ -29,6 +29,9 @@ // // Build 5.1.012: // - Runoff wet time step no longer kept aligned with reporting times. +// +// Build 5.1.014: +// - Fixed street sweeping bug. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -205,7 +208,11 @@ void runoff_execute() // --- see if street sweeping can occur on current date day = datetime_dayOfYear(currentDate); - if ( day >= SweepStart && day <= SweepEnd ) canSweep = TRUE; + if ( SweepStart <= SweepEnd ) + { + if ( day >= SweepStart && day <= SweepEnd ) canSweep = TRUE; + } + else if ( day <= SweepEnd || day >= SweepStart ) canSweep = TRUE; else canSweep = FALSE; // --- get runoff time step (in seconds) diff --git a/src/library/surfqual.c b/src/library/surfqual.c index 359cef56d..bf6744753 100644 --- a/src/library/surfqual.c +++ b/src/library/surfqual.c @@ -13,6 +13,8 @@ // subcatch.c. // - Support for separate accounting of LID drain flows included. // +// Build 5.1.014: +// - Fixed bug in computing effective BMP removal by LIDs. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -253,11 +255,12 @@ void surfqual_getWashoff(int j, double runoff, double tStep) if ( vOut1 > 0.0 ) cOut = OutflowLoad[p] / vOut1; // --- assign any difference between pre- and post-LID - // loads (with LID return flow included) to BMP removal + // subcatchment outflow loads to BMP removal if ( Subcatch[j].lidArea > 0.0 ) { - massLoad = cOut * (vOut1 - vOut2 - VlidReturn) * Pollut[p].mcf; - massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, massLoad); + massLoad = cOut * (vOut1 - vOut2) * Pollut[p].mcf; + if (massLoad > 0.0) + massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, massLoad); } // --- update subcatchment's cumulative runoff load in lbs (or kg) diff --git a/src/library/text.h b/src/library/text.h index 6f3100613..42c916cd6 100644 --- a/src/library/text.h +++ b/src/library/text.h @@ -29,7 +29,7 @@ #define FMT06 "\n o Retrieving project data" #define FMT07 "\n o Writing output report" #define FMT08 \ - "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build 5.1.013)" //(5.1.013) + "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build 5.1.014)" //(5.1.014) #define FMT09 \ "\n --------------------------------------------------------------" #define FMT10 "\n" From 8763c83f9958eee8b16a3d99909a9feae60ab1f5 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 11 Jul 2019 14:19:12 -0400 Subject: [PATCH 003/266] Work in progress --- .gitignore | 5 +++- appveyor.yml | 47 ++++++++++++++++++++++++++++++++++++++ src/library/CMakeLists.txt | 8 +++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 appveyor.yml diff --git a/.gitignore b/.gitignore index 9f6b91d60..21ff70489 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ + +buildprod*/ + # Eclipse Stuff .metadata/ -.settings/ \ No newline at end of file +.settings/ diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..bc36578ff --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,47 @@ +version: 2.0.{build} + +matrix: + allow_failures: + #GROUP: (SUPPORTED/EXPERIMENTAL) + #EXPERIMENTAL is allowed to fail under build matrix + - GROUP: "EXPERIMENTAL" + +environment: + matrix: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + GENERATOR: "Visual Studio 15 2017" + GROUP: "SUPPORTED" + BOOST_ROOT: "C:/Libraries/boost_1_67_0" + PLATFORM: "win32" + REF_BUILD_ID: "538_1" + # New build on Visual Studio 15 2017 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + GENERATOR: "Visual Studio 15 2017 Win64" + GROUP: "EXPERIMENTAL" + BOOST_ROOT: "C:/Libraries/boost_1_67_0" + PLATFORM: "win64" + REF_BUILD_ID: "538_2" + +# called before repo cloning +init: + - set SUT_BUILD_ID=%APPVEYOR_BUILD_NUMBER%_%APPVEYOR_JOB_NUMBER% + - set EPANET_HOME=%APPVEYOR_BUILD_FOLDER% + - set BUILD_HOME=buildprod + - set TEST_HOME=nrtestsuite + - set PATH=%PATH%;%BOOST_ROOT%/ + + # See values set + - echo %APPVEYOR_BUILD_WORKER_IMAGE% + - echo %BUILD_HOME% + - echo %GENERATOR% + - echo %BOOST_ROOT% + +# called before build +before_build: + - cmake -E make_directory %BUILD_HOME% + - cmake -E chdir %BUILD_HOME% + - cmake -G "%GENERATOR%" .. + +# run custom build script +build_script: + - cmake --build . --config Release diff --git a/src/library/CMakeLists.txt b/src/library/CMakeLists.txt index c0fd08b99..ba9bb5378 100644 --- a/src/library/CMakeLists.txt +++ b/src/library/CMakeLists.txt @@ -7,6 +7,14 @@ # US EPA ORD/NRMRL # +# Loads settings for OpenMP and append any OpenMP compiler flags. +find_package(OpenMP) +if(OPENMP_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") +endif() + # configure file groups set(SWMM_PUBLIC_HEADERS ${PROJECT_SOURCE_DIR}/include/swmm5.h From 3758ad331645d7b4ce57eadfd104f41d00318bf2 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 11 Jul 2019 14:31:01 -0400 Subject: [PATCH 004/266] Update appveyor.yml Fix generator invocation --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index bc36578ff..ec0b56c6c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -40,8 +40,8 @@ init: before_build: - cmake -E make_directory %BUILD_HOME% - cmake -E chdir %BUILD_HOME% - - cmake -G "%GENERATOR%" .. + cmake -G "%GENERATOR%" .. # run custom build script build_script: - - cmake --build . --config Release + - cmake --build ./%BUILD_HOME% --config Release From 467aa8244a0ec7a8db45bd14f151ac2e69a978c2 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 11 Jul 2019 14:37:17 -0400 Subject: [PATCH 005/266] Update appveyor.yml Cache buildsystem --- appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index ec0b56c6c..a11b0fef5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -45,3 +45,6 @@ before_build: # run custom build script build_script: - cmake --build ./%BUILD_HOME% --config Release + +cache: + - C:\projects\stormwater-management-model\buildprod -> CMakeLists.txt From 1d939145b8a06d40df8b29eecd97dca4142922ae Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 11 Jul 2019 14:46:13 -0400 Subject: [PATCH 006/266] Update README.md Add badge --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b84872ef1..113fbaf3d 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ Stormwater-Management-Model =========================== ORD Stormwater Management Model (aka "SWMM") +------------ + +## Build Status +[![Build status](https://ci.appveyor.com/api/projects/status/28d9pg7rollqfhqm/branch/develop?svg=true)](https://ci.appveyor.com/project/michaeltryby/stormwater-management-model/branch/develop) + Introduction ------------ @@ -11,4 +16,4 @@ SWMM is a dynamic hydrology-hydraulic water quality simulation model. It is used Find Out More ------------- -The source code distributed here is identical to the code found at the official [SWMM Website](http://www2.epa.gov/water-research/storm-water-management-model-swmm). +The source code distributed here is identical to the code found at the official [SWMM Website](http://www2.epa.gov/water-research/storm-water-management-model-swmm). From 5d072a7ca6d9aaba9f754565e59ab5aefefe5bd9 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 11 Jul 2019 14:49:55 -0400 Subject: [PATCH 007/266] Update README.md --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 113fbaf3d..35ff2c73b 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,17 @@ Stormwater-Management-Model =========================== ORD Stormwater Management Model (aka "SWMM") ------------- -## Build Status + +##Build Status [![Build status](https://ci.appveyor.com/api/projects/status/28d9pg7rollqfhqm/branch/develop?svg=true)](https://ci.appveyor.com/project/michaeltryby/stormwater-management-model/branch/develop) -Introduction ------------- +##Introduction This is the official SWMM source code repository maintained by US EPA ORD, NRMRL, Water Supply and Water Resources Division located in Cincinnati, Ohio. SWMM is a dynamic hydrology-hydraulic water quality simulation model. It is used for single event or long-term (continuous) simulation of runoff quantity and quality from primarily urban areas. SWMM source code is written in the C Programming Language and released in the Public Domain. -Find Out More -------------- + +##Find Out More The source code distributed here is identical to the code found at the official [SWMM Website](http://www2.epa.gov/water-research/storm-water-management-model-swmm). From efc3b80fec5c5bc192c3d681b61942b0813ba379 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 11 Jul 2019 14:56:07 -0400 Subject: [PATCH 008/266] Update CMakeLists.txt Bumping version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 270979aaf..b47850d65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ cmake_minimum_required (VERSION 3.0) project(swmm-solver - VERSION 5.1.13 + VERSION 5.1.14 LANGUAGES C CXX ) From 909cfbbdf0edcc83f11b02444982953f69e159cc Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 11 Jul 2019 15:04:24 -0400 Subject: [PATCH 009/266] Update README.md Fixing typo --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 35ff2c73b..3d27255f3 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,15 @@ Stormwater-Management-Model ORD Stormwater Management Model (aka "SWMM") -##Build Status +## Build Status [![Build status](https://ci.appveyor.com/api/projects/status/28d9pg7rollqfhqm/branch/develop?svg=true)](https://ci.appveyor.com/project/michaeltryby/stormwater-management-model/branch/develop) -##Introduction +## Introduction This is the official SWMM source code repository maintained by US EPA ORD, NRMRL, Water Supply and Water Resources Division located in Cincinnati, Ohio. SWMM is a dynamic hydrology-hydraulic water quality simulation model. It is used for single event or long-term (continuous) simulation of runoff quantity and quality from primarily urban areas. SWMM source code is written in the C Programming Language and released in the Public Domain. -##Find Out More +## Find Out More The source code distributed here is identical to the code found at the official [SWMM Website](http://www2.epa.gov/water-research/storm-water-management-model-swmm). From ee770dfcefcc965ff9aa961c583437d274b7c9dd Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 12 Jul 2019 10:07:18 -0400 Subject: [PATCH 010/266] Work in progress Organizing sources by the target they belong to. Renaming library to solver. --- CMakeLists.txt | 2 +- src/{library => solver}/CMakeLists.txt | 0 src/{library => solver}/Roadmap.txt | 0 src/{library => solver}/climate.c | 0 src/{library => solver}/consts.h | 0 src/{library => solver}/controls.c | 0 src/{library => solver}/culvert.c | 0 src/{library => solver}/datetime.c | 0 src/{library => solver}/datetime.h | 0 src/{library => solver}/dwflow.c | 0 src/{library => solver}/dynwave.c | 0 src/{library => solver}/enums.h | 0 src/{library => solver}/error.c | 0 src/{library => solver}/error.h | 0 src/{library => solver}/exfil.c | 0 src/{library => solver}/exfil.h | 0 src/{library => solver}/findroot.c | 0 src/{library => solver}/findroot.h | 0 src/{library => solver}/flowrout.c | 0 src/{library => solver}/forcmain.c | 0 src/{library => solver}/funcs.h | 0 src/{library => solver}/gage.c | 0 src/{library => solver}/globals.h | 0 src/{library => solver}/gwater.c | 0 src/{library => solver}/hash.c | 0 src/{library => solver}/hash.h | 0 src/{library => solver}/headers.h | 0 src/{library => solver}/hotstart.c | 0 src/{library => solver}/iface.c | 0 src/{library => solver}/infil.c | 0 src/{library => solver}/infil.h | 0 src/{library => solver}/inflow.c | 0 src/{library => solver}/input.c | 0 src/{library => solver}/inputrpt.c | 0 src/{library => solver}/keywords.c | 0 src/{library => solver}/keywords.h | 0 src/{library => solver}/kinwave.c | 0 src/{library => solver}/landuse.c | 0 src/{library => solver}/lid.c | 0 src/{library => solver}/lid.h | 0 src/{library => solver}/lidproc.c | 0 src/{library => solver}/link.c | 0 src/{library => solver}/macros.h | 0 src/{library => solver}/massbal.c | 0 src/{library => solver}/mathexpr.c | 0 src/{library => solver}/mathexpr.h | 0 src/{library => solver}/mempool.c | 0 src/{library => solver}/mempool.h | 0 src/{library => solver}/node.c | 0 src/{library => solver}/objects.h | 0 src/{library => solver}/odesolve.c | 0 src/{library => solver}/odesolve.h | 0 src/{library => solver}/output.c | 0 src/{library => solver}/project.c | 0 src/{library => solver}/qualrout.c | 0 src/{library => solver}/rain.c | 0 src/{library => solver}/rdii.c | 0 src/{library => solver}/report.c | 0 src/{library => solver}/roadway.c | 0 src/{library => solver}/routing.c | 0 src/{library => solver}/runoff.c | 0 src/{library => solver}/shape.c | 0 src/{library => solver}/snow.c | 0 src/{library => solver}/stats.c | 0 src/{library => solver}/statsrpt.c | 0 src/{library => solver}/subcatch.c | 0 src/{library => solver}/surfqual.c | 0 src/{library => solver}/swmm5.c | 0 src/{library => solver}/swmm5.def | 0 src/{library => solver}/table.c | 0 src/{library => solver}/text.h | 0 src/{library => solver}/toposort.c | 0 src/{library => solver}/transect.c | 0 src/{library => solver}/treatmnt.c | 0 src/{library => solver}/xsect.c | 0 src/{library => solver}/xsect.dat | 0 76 files changed, 1 insertion(+), 1 deletion(-) rename src/{library => solver}/CMakeLists.txt (100%) rename src/{library => solver}/Roadmap.txt (100%) rename src/{library => solver}/climate.c (100%) rename src/{library => solver}/consts.h (100%) rename src/{library => solver}/controls.c (100%) rename src/{library => solver}/culvert.c (100%) rename src/{library => solver}/datetime.c (100%) rename src/{library => solver}/datetime.h (100%) rename src/{library => solver}/dwflow.c (100%) rename src/{library => solver}/dynwave.c (100%) rename src/{library => solver}/enums.h (100%) rename src/{library => solver}/error.c (100%) rename src/{library => solver}/error.h (100%) rename src/{library => solver}/exfil.c (100%) rename src/{library => solver}/exfil.h (100%) rename src/{library => solver}/findroot.c (100%) rename src/{library => solver}/findroot.h (100%) rename src/{library => solver}/flowrout.c (100%) rename src/{library => solver}/forcmain.c (100%) rename src/{library => solver}/funcs.h (100%) rename src/{library => solver}/gage.c (100%) rename src/{library => solver}/globals.h (100%) rename src/{library => solver}/gwater.c (100%) rename src/{library => solver}/hash.c (100%) rename src/{library => solver}/hash.h (100%) rename src/{library => solver}/headers.h (100%) rename src/{library => solver}/hotstart.c (100%) rename src/{library => solver}/iface.c (100%) rename src/{library => solver}/infil.c (100%) rename src/{library => solver}/infil.h (100%) rename src/{library => solver}/inflow.c (100%) rename src/{library => solver}/input.c (100%) rename src/{library => solver}/inputrpt.c (100%) rename src/{library => solver}/keywords.c (100%) rename src/{library => solver}/keywords.h (100%) rename src/{library => solver}/kinwave.c (100%) rename src/{library => solver}/landuse.c (100%) rename src/{library => solver}/lid.c (100%) rename src/{library => solver}/lid.h (100%) rename src/{library => solver}/lidproc.c (100%) rename src/{library => solver}/link.c (100%) rename src/{library => solver}/macros.h (100%) rename src/{library => solver}/massbal.c (100%) rename src/{library => solver}/mathexpr.c (100%) rename src/{library => solver}/mathexpr.h (100%) rename src/{library => solver}/mempool.c (100%) rename src/{library => solver}/mempool.h (100%) rename src/{library => solver}/node.c (100%) rename src/{library => solver}/objects.h (100%) rename src/{library => solver}/odesolve.c (100%) rename src/{library => solver}/odesolve.h (100%) rename src/{library => solver}/output.c (100%) rename src/{library => solver}/project.c (100%) rename src/{library => solver}/qualrout.c (100%) rename src/{library => solver}/rain.c (100%) rename src/{library => solver}/rdii.c (100%) rename src/{library => solver}/report.c (100%) rename src/{library => solver}/roadway.c (100%) rename src/{library => solver}/routing.c (100%) rename src/{library => solver}/runoff.c (100%) rename src/{library => solver}/shape.c (100%) rename src/{library => solver}/snow.c (100%) rename src/{library => solver}/stats.c (100%) rename src/{library => solver}/statsrpt.c (100%) rename src/{library => solver}/subcatch.c (100%) rename src/{library => solver}/surfqual.c (100%) rename src/{library => solver}/swmm5.c (100%) rename src/{library => solver}/swmm5.def (100%) rename src/{library => solver}/table.c (100%) rename src/{library => solver}/text.h (100%) rename src/{library => solver}/toposort.c (100%) rename src/{library => solver}/transect.c (100%) rename src/{library => solver}/treatmnt.c (100%) rename src/{library => solver}/xsect.c (100%) rename src/{library => solver}/xsect.dat (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index b47850d65..89caecc5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ set(LIBRARY_DIST "lib") set(CONFIG_DIST "cmake") # Add project subdirectories -add_subdirectory(src/library) +add_subdirectory(src/solver) add_subdirectory(src/run) # Create target import scripts so other cmake projects can use swmm libraries diff --git a/src/library/CMakeLists.txt b/src/solver/CMakeLists.txt similarity index 100% rename from src/library/CMakeLists.txt rename to src/solver/CMakeLists.txt diff --git a/src/library/Roadmap.txt b/src/solver/Roadmap.txt similarity index 100% rename from src/library/Roadmap.txt rename to src/solver/Roadmap.txt diff --git a/src/library/climate.c b/src/solver/climate.c similarity index 100% rename from src/library/climate.c rename to src/solver/climate.c diff --git a/src/library/consts.h b/src/solver/consts.h similarity index 100% rename from src/library/consts.h rename to src/solver/consts.h diff --git a/src/library/controls.c b/src/solver/controls.c similarity index 100% rename from src/library/controls.c rename to src/solver/controls.c diff --git a/src/library/culvert.c b/src/solver/culvert.c similarity index 100% rename from src/library/culvert.c rename to src/solver/culvert.c diff --git a/src/library/datetime.c b/src/solver/datetime.c similarity index 100% rename from src/library/datetime.c rename to src/solver/datetime.c diff --git a/src/library/datetime.h b/src/solver/datetime.h similarity index 100% rename from src/library/datetime.h rename to src/solver/datetime.h diff --git a/src/library/dwflow.c b/src/solver/dwflow.c similarity index 100% rename from src/library/dwflow.c rename to src/solver/dwflow.c diff --git a/src/library/dynwave.c b/src/solver/dynwave.c similarity index 100% rename from src/library/dynwave.c rename to src/solver/dynwave.c diff --git a/src/library/enums.h b/src/solver/enums.h similarity index 100% rename from src/library/enums.h rename to src/solver/enums.h diff --git a/src/library/error.c b/src/solver/error.c similarity index 100% rename from src/library/error.c rename to src/solver/error.c diff --git a/src/library/error.h b/src/solver/error.h similarity index 100% rename from src/library/error.h rename to src/solver/error.h diff --git a/src/library/exfil.c b/src/solver/exfil.c similarity index 100% rename from src/library/exfil.c rename to src/solver/exfil.c diff --git a/src/library/exfil.h b/src/solver/exfil.h similarity index 100% rename from src/library/exfil.h rename to src/solver/exfil.h diff --git a/src/library/findroot.c b/src/solver/findroot.c similarity index 100% rename from src/library/findroot.c rename to src/solver/findroot.c diff --git a/src/library/findroot.h b/src/solver/findroot.h similarity index 100% rename from src/library/findroot.h rename to src/solver/findroot.h diff --git a/src/library/flowrout.c b/src/solver/flowrout.c similarity index 100% rename from src/library/flowrout.c rename to src/solver/flowrout.c diff --git a/src/library/forcmain.c b/src/solver/forcmain.c similarity index 100% rename from src/library/forcmain.c rename to src/solver/forcmain.c diff --git a/src/library/funcs.h b/src/solver/funcs.h similarity index 100% rename from src/library/funcs.h rename to src/solver/funcs.h diff --git a/src/library/gage.c b/src/solver/gage.c similarity index 100% rename from src/library/gage.c rename to src/solver/gage.c diff --git a/src/library/globals.h b/src/solver/globals.h similarity index 100% rename from src/library/globals.h rename to src/solver/globals.h diff --git a/src/library/gwater.c b/src/solver/gwater.c similarity index 100% rename from src/library/gwater.c rename to src/solver/gwater.c diff --git a/src/library/hash.c b/src/solver/hash.c similarity index 100% rename from src/library/hash.c rename to src/solver/hash.c diff --git a/src/library/hash.h b/src/solver/hash.h similarity index 100% rename from src/library/hash.h rename to src/solver/hash.h diff --git a/src/library/headers.h b/src/solver/headers.h similarity index 100% rename from src/library/headers.h rename to src/solver/headers.h diff --git a/src/library/hotstart.c b/src/solver/hotstart.c similarity index 100% rename from src/library/hotstart.c rename to src/solver/hotstart.c diff --git a/src/library/iface.c b/src/solver/iface.c similarity index 100% rename from src/library/iface.c rename to src/solver/iface.c diff --git a/src/library/infil.c b/src/solver/infil.c similarity index 100% rename from src/library/infil.c rename to src/solver/infil.c diff --git a/src/library/infil.h b/src/solver/infil.h similarity index 100% rename from src/library/infil.h rename to src/solver/infil.h diff --git a/src/library/inflow.c b/src/solver/inflow.c similarity index 100% rename from src/library/inflow.c rename to src/solver/inflow.c diff --git a/src/library/input.c b/src/solver/input.c similarity index 100% rename from src/library/input.c rename to src/solver/input.c diff --git a/src/library/inputrpt.c b/src/solver/inputrpt.c similarity index 100% rename from src/library/inputrpt.c rename to src/solver/inputrpt.c diff --git a/src/library/keywords.c b/src/solver/keywords.c similarity index 100% rename from src/library/keywords.c rename to src/solver/keywords.c diff --git a/src/library/keywords.h b/src/solver/keywords.h similarity index 100% rename from src/library/keywords.h rename to src/solver/keywords.h diff --git a/src/library/kinwave.c b/src/solver/kinwave.c similarity index 100% rename from src/library/kinwave.c rename to src/solver/kinwave.c diff --git a/src/library/landuse.c b/src/solver/landuse.c similarity index 100% rename from src/library/landuse.c rename to src/solver/landuse.c diff --git a/src/library/lid.c b/src/solver/lid.c similarity index 100% rename from src/library/lid.c rename to src/solver/lid.c diff --git a/src/library/lid.h b/src/solver/lid.h similarity index 100% rename from src/library/lid.h rename to src/solver/lid.h diff --git a/src/library/lidproc.c b/src/solver/lidproc.c similarity index 100% rename from src/library/lidproc.c rename to src/solver/lidproc.c diff --git a/src/library/link.c b/src/solver/link.c similarity index 100% rename from src/library/link.c rename to src/solver/link.c diff --git a/src/library/macros.h b/src/solver/macros.h similarity index 100% rename from src/library/macros.h rename to src/solver/macros.h diff --git a/src/library/massbal.c b/src/solver/massbal.c similarity index 100% rename from src/library/massbal.c rename to src/solver/massbal.c diff --git a/src/library/mathexpr.c b/src/solver/mathexpr.c similarity index 100% rename from src/library/mathexpr.c rename to src/solver/mathexpr.c diff --git a/src/library/mathexpr.h b/src/solver/mathexpr.h similarity index 100% rename from src/library/mathexpr.h rename to src/solver/mathexpr.h diff --git a/src/library/mempool.c b/src/solver/mempool.c similarity index 100% rename from src/library/mempool.c rename to src/solver/mempool.c diff --git a/src/library/mempool.h b/src/solver/mempool.h similarity index 100% rename from src/library/mempool.h rename to src/solver/mempool.h diff --git a/src/library/node.c b/src/solver/node.c similarity index 100% rename from src/library/node.c rename to src/solver/node.c diff --git a/src/library/objects.h b/src/solver/objects.h similarity index 100% rename from src/library/objects.h rename to src/solver/objects.h diff --git a/src/library/odesolve.c b/src/solver/odesolve.c similarity index 100% rename from src/library/odesolve.c rename to src/solver/odesolve.c diff --git a/src/library/odesolve.h b/src/solver/odesolve.h similarity index 100% rename from src/library/odesolve.h rename to src/solver/odesolve.h diff --git a/src/library/output.c b/src/solver/output.c similarity index 100% rename from src/library/output.c rename to src/solver/output.c diff --git a/src/library/project.c b/src/solver/project.c similarity index 100% rename from src/library/project.c rename to src/solver/project.c diff --git a/src/library/qualrout.c b/src/solver/qualrout.c similarity index 100% rename from src/library/qualrout.c rename to src/solver/qualrout.c diff --git a/src/library/rain.c b/src/solver/rain.c similarity index 100% rename from src/library/rain.c rename to src/solver/rain.c diff --git a/src/library/rdii.c b/src/solver/rdii.c similarity index 100% rename from src/library/rdii.c rename to src/solver/rdii.c diff --git a/src/library/report.c b/src/solver/report.c similarity index 100% rename from src/library/report.c rename to src/solver/report.c diff --git a/src/library/roadway.c b/src/solver/roadway.c similarity index 100% rename from src/library/roadway.c rename to src/solver/roadway.c diff --git a/src/library/routing.c b/src/solver/routing.c similarity index 100% rename from src/library/routing.c rename to src/solver/routing.c diff --git a/src/library/runoff.c b/src/solver/runoff.c similarity index 100% rename from src/library/runoff.c rename to src/solver/runoff.c diff --git a/src/library/shape.c b/src/solver/shape.c similarity index 100% rename from src/library/shape.c rename to src/solver/shape.c diff --git a/src/library/snow.c b/src/solver/snow.c similarity index 100% rename from src/library/snow.c rename to src/solver/snow.c diff --git a/src/library/stats.c b/src/solver/stats.c similarity index 100% rename from src/library/stats.c rename to src/solver/stats.c diff --git a/src/library/statsrpt.c b/src/solver/statsrpt.c similarity index 100% rename from src/library/statsrpt.c rename to src/solver/statsrpt.c diff --git a/src/library/subcatch.c b/src/solver/subcatch.c similarity index 100% rename from src/library/subcatch.c rename to src/solver/subcatch.c diff --git a/src/library/surfqual.c b/src/solver/surfqual.c similarity index 100% rename from src/library/surfqual.c rename to src/solver/surfqual.c diff --git a/src/library/swmm5.c b/src/solver/swmm5.c similarity index 100% rename from src/library/swmm5.c rename to src/solver/swmm5.c diff --git a/src/library/swmm5.def b/src/solver/swmm5.def similarity index 100% rename from src/library/swmm5.def rename to src/solver/swmm5.def diff --git a/src/library/table.c b/src/solver/table.c similarity index 100% rename from src/library/table.c rename to src/solver/table.c diff --git a/src/library/text.h b/src/solver/text.h similarity index 100% rename from src/library/text.h rename to src/solver/text.h diff --git a/src/library/toposort.c b/src/solver/toposort.c similarity index 100% rename from src/library/toposort.c rename to src/solver/toposort.c diff --git a/src/library/transect.c b/src/solver/transect.c similarity index 100% rename from src/library/transect.c rename to src/solver/transect.c diff --git a/src/library/treatmnt.c b/src/solver/treatmnt.c similarity index 100% rename from src/library/treatmnt.c rename to src/solver/treatmnt.c diff --git a/src/library/xsect.c b/src/solver/xsect.c similarity index 100% rename from src/library/xsect.c rename to src/solver/xsect.c diff --git a/src/library/xsect.dat b/src/solver/xsect.dat similarity index 100% rename from src/library/xsect.dat rename to src/solver/xsect.dat From e5e7f2c1afcff51a5f77a1738002d4ebc849384f Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 16 Oct 2019 16:37:04 -0400 Subject: [PATCH 011/266] Work in progress Setting up build and regression testing --- .gitignore | 5 +- make.bat | 51 ----------------- src/run/CMakeLists.txt | 19 +++++-- src/solver/CMakeLists.txt | 8 +++ tools/app-config.cmd | 41 ++++++++++++++ tools/before-nrtest.cmd | 115 ++++++++++++++++++++++++++++++++++++++ tools/make.cmd | 113 +++++++++++++++++++++++++++++++++++++ tools/run-nrtests.cmd | 113 +++++++++++++++++++++++++++++++++++++ 8 files changed, 409 insertions(+), 56 deletions(-) delete mode 100644 make.bat create mode 100644 tools/app-config.cmd create mode 100644 tools/before-nrtest.cmd create mode 100644 tools/make.cmd create mode 100644 tools/run-nrtests.cmd diff --git a/.gitignore b/.gitignore index 21ff70489..8f2b3edd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ -buildprod*/ +build*/ +nrtest*/ + +*_export.h # Eclipse Stuff .metadata/ diff --git a/make.bat b/make.bat deleted file mode 100644 index 14c90ce7a..000000000 --- a/make.bat +++ /dev/null @@ -1,51 +0,0 @@ -:: -:: makefile.bat - build swmm-solver -:: -:: Date Created: 7/11/2019 -:: -:: Author: Michael E. Tryby -:: US EPA - ORD/NRMRL -:: -:: Requires: -:: Build Tools for Visual Studio -:: https://visualstudio.microsoft.com/downloads/ -:: -:: CMake -:: https://cmake.org/download/ -:: -:: Note: -:: This script must be located at the root of the project folder -:: in order to work properly. -:: - -@echo off -echo INFO: Building swmm-solver ... - -set GENERATOR=Visual Studio 15 2017 - -:: Determine project path and strip trailing \ from path -set "PROJECT_PATH=%~dp0" -IF %PROJECT_PATH:~-1%==\ set "PROJECT_PATH=%PROJECT_PATH:~0,-1%" - -:: check for requirements -WHERE cmake -IF %ERRORLEVEL% NEQ 0 ECHO cmake not installed & EXIT /B 1 - -:: generate build system -IF exist buildprod_win32 ( cd buildprod_win32 ) ELSE ( mkdir buildprod_win32 & cd buildprod_win32 ) -cmake -G"%GENERATOR%" .. - -cd .. -IF exist buildprod_win64 ( cd buildprod_win64 ) ELSE ( mkdir buildprod_win64 & cd buildprod_win64 ) -cmake -G"%GENERATOR% Win64" .. - -:: perform build -cmake --build . --config Release --target install -cpack - -cd ..\buildprod_win32 -cmake --build . --config Release --target install -cpack - -:: return to project root -cd %PROJECT_PATH% diff --git a/src/run/CMakeLists.txt b/src/run/CMakeLists.txt index b4320380c..a0937db10 100644 --- a/src/run/CMakeLists.txt +++ b/src/run/CMakeLists.txt @@ -9,16 +9,27 @@ # Creates the EPANET command line executable -add_executable(run_swmm +add_executable(runswmm main.c ) -target_include_directories(run_swmm +target_include_directories(runswmm PUBLIC include ) -target_link_libraries(run_swmm +target_link_libraries(runswmm LINK_PUBLIC swmm5 ) -install(TARGETS run_swmm DESTINATION "${TOOL_DIST}") + +install(TARGETS runswmm + DESTINATION "${TOOL_DIST}" + ) + + +# copy runswmm to build tree for testing +add_custom_command(TARGET runswmm POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + $ + ${CMAKE_BINARY_DIR}/bin/$/$ + ) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index ba9bb5378..8ccf900f2 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -40,3 +40,11 @@ install(TARGETS swmm5 EXPORT swmm5Targets ) install(FILES ${SWMM_PUBLIC_HEADERS} DESTINATION "${INCLUDE_DIST}") + + +# copy swmm5 to build tree for testing +add_custom_command(TARGET swmm5 POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + $ + ${CMAKE_BINARY_DIR}/bin/$/$ + ) diff --git a/tools/app-config.cmd b/tools/app-config.cmd new file mode 100644 index 000000000..5d9e1a0c7 --- /dev/null +++ b/tools/app-config.cmd @@ -0,0 +1,41 @@ +:: +:: app-config.cmd - Generates nrtest app configuration file for SUT executable +:: +:: Date Created: 10/16/2019 +:: +:: Author: Michael E. Tryby +:: US EPA - ORD/CESER +:: +:: Arguments: +:: 1 - absolute path to test executable (valid path seperator for nrtest is "/") +:: 2 - (platform) +:: 3 - (build identifier for SUT) +:: 4 - (commit hash string) + +@echo off +setlocal + +:: swmm target created by the cmake build script +set TEST_CMD=run%PROJECT%.exe + +:: remove quotes from path and convert backward to forward slash +set ABS_BUILD_PATH=%~1 +set ABS_BUILD_PATH=%ABS_BUILD_PATH:\=/% + +IF [%2]==[] ( set "PLATFORM=unknown" +) ELSE ( set "PLATFORM=%~2" ) + +IF [%3]==[] ( set "BUILD_ID=unknown" +) ELSE ( set "BUILD_ID=%~3" ) + +IF [%4]==[] ( set "VERSION=unknown" +) ELSE ( set "VERSION=%~4" ) + + +echo { +echo "name" : "%PROJECT%", +echo "version" : "%VERSION%", +echo "description" : "%PLATFORM% %BUILD_ID%", +echo "setup_script" : "", +echo "exe" : "%ABS_BUILD_PATH%/%TEST_CMD%" +echo } diff --git a/tools/before-nrtest.cmd b/tools/before-nrtest.cmd new file mode 100644 index 000000000..8a41c7e61 --- /dev/null +++ b/tools/before-nrtest.cmd @@ -0,0 +1,115 @@ +:: +:: before-test.cmd - Stages test and benchmark files for nrtest +:: +:: Date Created: 10/16/2019 +:: Date Updated: +:: +:: Author: Michael E. Tryby +:: US EPA - ORD/CESER +:: +:: Dependencies: +:: curl +:: 7z +:: +:: Environment Variables: +:: PROJECT +:: BUILD_HOME - defaults to "build" +:: TEST_HOME - defaults to "nrtests" +:: PLATFORM +:: +:: Arguments: +:: 1 - (RELEASE_TAG) release tag for benchmark version (defaults to latest tag) +:: +:: Note: +:: Tests and benchmark files are stored in the project-nrtests repo. +:: This script retrieves them using a stable URL associated with a GitHub +:: release, stages the files, and sets up the environment for nrtest to run. +:: + +::@echo off + +:: determine project directory +set "CUR_DIR=%CD%" +set "SCRIPT_HOME=%~dp0" +cd %SCRIPT_HOME% +cd .. + +setlocal + + +:: check that dependencies are installed +where curl > nul +if %ERRORLEVEL% neq 0 ( echo "ERROR: curl not installed" & exit /B 1 ) +where 7z > nul +if %ERRORLEVEL% neq 0 ( echo "ERROR: 7zip not installed" & exit /B 1 ) + + +:: set URL to github repo with test files +:: set "NRTESTS_URL=https://github.com/michaeltryby/%PROJECT%-nrtests" +set "NRTESTS_URL=https://github.com/OpenWaterAnalytics/swmm-example-networks" + +:: if release tag isn't provided latest tag will be retrieved +if [%1] == [] (set "RELEASE_TAG=" +) else (set "RELEASE_TAG=%~1") + + +:: check BUILD_HOME and TEST_HOME and apply defaults +if not defined BUILD_HOME ( set "BUILD_HOME=build" ) +if not defined TEST_HOME ( set "TEST_HOME=nrtests" ) +if not defined PLATFORM ( echo "ERROR: PLATFORM must be defined" & exit /B 1 ) + + +echo INFO: Staging files for regression testing + + +:: determine latest tag in the tests repo +if [%RELEASE_TAG%] == [] ( + for /F delims^=^"^ tokens^=2 %%g in ('curl --silent %NRTESTS_URL%/releases/latest') do ( + set "RELEASE_TAG=%%~nxg" + ) +) + +if defined RELEASE_TAG ( + set "TESTFILES_URL=%NRTESTS_URL%/archive/%RELEASE_TAG%.zip" + set "BENCHFILES_URL=%NRTESTS_URL%/releases/download/%RELEASE_TAG%/benchmark-%PLATFORM%.zip" +) else ( echo ERROR: tag %RELEASE_TAG% is invalid & exit /B 1 ) + + +:: create a clean directory for staging regression tests +if exist %TEST_HOME% ( + rmdir /s /q %TEST_HOME% +) +mkdir %TEST_HOME% +if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to make %TEST_HOME% dir" & exit /B 1 ) +cd %TEST_HOME% + + +:: retrieve nrtest cases and benchmark results for regression testing +curl -fsSL -o nrtestfiles.zip %TESTFILES_URL% +curl -fsSL -o benchmark.zip %BENCHFILES_URL% + + +:: extract tests, scripts, benchmarks, and manifest +7z x nrtestfiles.zip * > nul +7z x benchmark.zip -obenchmark\ > nul +7z e benchmark.zip -o. manifest.json -r > nul + + +:: set up symlinks for tests directory +:: mklink /D .\tests .\%PROJECT%-nrtests-%RELEASE_TAG:~1%\public > nul +mklink /D .\tests .\swmm-example-networks-%RELEASE_TAG:~1%\swmm-tests > nul + +endlocal + + +:: determine REF_BUILD_ID from manifest file +for /F delims^=^"^ tokens^=4 %%d in ( 'findstr %PLATFORM% %TEST_HOME%\manifest.json' ) do ( + for /F "tokens=2" %%r in ( 'echo %%d' ) do ( set "REF_BUILD_ID=%%r" ) +) +if not defined REF_BUILD_ID ( echo "WARNING: REF_BUILD_ID could not be determined" &^ + set "REF_BUILD_ID=unknown" +) + + +:: return to users current directory +cd %CUR_DIR% diff --git a/tools/make.cmd b/tools/make.cmd new file mode 100644 index 000000000..f31f649d3 --- /dev/null +++ b/tools/make.cmd @@ -0,0 +1,113 @@ +:: +:: make.cmd - builds project +:: +:: Date Created: 10/15/2019 +:: Date Updated: +:: +:: Author: Michael E. Tryby +:: US EPA - ORD/CESER +:: +:: Requires: +:: Build Tools for Visual Studio download: +:: https://visualstudio.microsoft.com/downloads/ +:: +:: CMake download: +:: https://cmake.org/download/ +:: +:: Environment Variables: +:: PROJECT +:: BUILD_HOME - defaults to build +:: PLATFORM +:: +:: Optional Arguments: +:: /g ("GENERATOR") defaults to "Visual Studio 15 2017" +:: /t builds and runs unit tests (requires Boost) +:: + + +::echo off + + +:: set global defaults +set "PROJECT=swmm" +set "BUILD_HOME=build" +set "TEST_HOME=nrtests" +set "PLATFORM=win32" + +:: determine project directory +set "CUR_DIR=%CD%" +set "SCRIPT_HOME=%~dp0" +cd %SCRIPT_HOME% +cd .. + +:: check for requirements +where cmake > nul +if %ERRORLEVEL% NEQ 0 ( echo "ERROR: cmake not installed" & exit /B 1 ) + + +setlocal EnableDelayedExpansion + + +echo INFO: Building %PROJECT% ... + + +:: set local defaults +set "GENERATOR=Visual Studio 15 2017" +set "TESTING=0" + +:: process arguments +:loop +if NOT [%1]==[] ( + if "%1"=="/g" ( + set "GENERATOR=%~2" + shift + ) + if "%1"=="/t" ( + set "TESTING=1" + ) + shift + goto :loop +) + + +:: if generator has changed delete the build folder +if exist %BUILD_HOME% ( + for /F "tokens=*" %%f in ( 'findstr CMAKE_GENERATOR:INTERNAL %BUILD_HOME%\CmakeCache.txt' ) do ( + for /F "delims=:= tokens=3" %%m in ( 'echo %%f' ) do ( + set CACHE_GEN=%%m + if not "!CACHE_GEN!" == "!GENERATOR!" ( rmdir /s /q %BUILD_HOME% & mkdir %BUILD_HOME% ) + ) + ) +) else ( + mkdir %BUILD_HOME%^ + & if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to make %BUILD_HOME% dir" & exit /B 1 ) +) + + +:: perform the build +cd %BUILD_HOME% +if %TESTING% EQU 1 ( + cmake -G"%GENERATOR%" -DBUILD_TESTS=ON -DBOOST_ROOT=C:\local\boost_1_67_0 ..^ + && cmake --build . --config Debug^ + & echo. && ctest -C Debug --output-on-failure +) else ( + cmake -G"%GENERATOR%" -DBUILD_TESTS=OFF ..^ + && cmake --build . --config Release --target package^ + && move %PROJECT%-solver*.zip %PROJECT_PATH% +) + + +endlocal + + +:: determine platform from CmakeCache.txt file +for /F "tokens=*" %%f in ( 'findstr CMAKE_SHARED_LINKER_FLAGS:STRING %BUILD_HOME%\CmakeCache.txt' ) do ( + for /F "delims=: tokens=3" %%m in ( 'echo %%f' ) do ( + if "%%m" == "X86" ( set "PLATFORM=win32" ) else if "%%m" == "x64" ( set "PLATFORM=win64" ) + ) +) +if not defined PLATFORM ( echo "ERROR: PLATFORM could not be determined" & exit /B 1 ) + + +:: return to users current dir +cd %CUR_DIR% diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd new file mode 100644 index 000000000..c78b6af19 --- /dev/null +++ b/tools/run-nrtests.cmd @@ -0,0 +1,113 @@ +:: +:: run_nrtest.cmd - Runs numerical regression test +:: +:: Date Created: 10/16/2019 +:: Date Updated: +:: +:: Author: Michael E. Tryby +:: US EPA - ORD/CESER +:: +:: Dependencies: +:: python -m pip install -r requirements.txt +:: +:: Environment Variables: +:: PROJECT +:: BUILD_HOME - relative path +:: TEST_HOME - relative path +:: PLATFORM +:: REF_BUILD_ID +:: +:: Arguments: +:: 1 - (SUT_VERSION) - optional argument +:: 2 - (SUT_BUILD_ID) - optional argument +:: + +::@echo off +setlocal EnableDelayedExpansion + + +:: Check that required environment variables are set +if not defined BUILD_HOME ( echo "ERROR: BUILD_HOME must be defined" & exit /B 1 ) +if not defined TEST_HOME ( echo "ERROR: TEST_HOME must be defined" & exit /B 1 ) +if not defined PLATFORM ( echo "ERROR: PLATFORM must be defined" & exit /B 1 ) +if not defined REF_BUILD_ID ( echo "ERROR: REF_BUILD_ID must be defined" & exit /B 1 ) + + +:: determine project directory +set "CUR_DIR=%CD%" +set "SCRIPT_HOME=%~dp0" +cd %SCRIPT_HOME% +pushd .. +set PROJ_DIR=%CD% +popd + + +cd %PROJ_DIR%\%TEST_HOME% + +:: Process optional arguments +if [%1]==[] (set "SUT_VERSION=unknown" +) else ( set "SUT_VERSION=%~1" ) + +if [%2]==[] ( set "SUT_BUILD_ID=local" +) else ( set "SUT_BUILD_ID=%~2" ) + + +:: check if app config file exists +if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( + mkdir apps + call %SCRIPT_HOME%\app-config.cmd %PROJ_DIR%\%BUILD_HOME%\bin\Release^ + %PLATFORM% %SUT_BUILD_ID% %SUT_VERSION% > apps\%PROJECT%-%SUT_BUILD_ID%.json +) + + +:: recursively build test list +set TESTS= +for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( + set FULL_PATH=%%T + set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! +) + + +:: determine location of python Scripts folder +for /F "tokens=*" %%G in ('where python.exe') do ( + set PYTHON_DIR=%%~dpG + goto break_loop_1 +) +:break_loop_1 +set "NRTEST_SCRIPT_PATH=%PYTHON_DIR%Scripts" + + +:: build nrtest execute command +set NRTEST_EXECUTE_CMD=python.exe %NRTEST_SCRIPT_PATH%\nrtest execute +set TEST_APP_PATH=apps\%PROJECT%-%SUT_BUILD_ID%.json +set TEST_OUTPUT_PATH=benchmark\%PROJECT%-%SUT_BUILD_ID% + +:: build nrtest compare command +set NRTEST_COMPARE_CMD=python.exe %NRTEST_SCRIPT_PATH%\nrtest compare +set REF_OUTPUT_PATH=benchmark\%PROJECT%-%REF_BUILD_ID% +set RTOL_VALUE=0.01 +set ATOL_VALUE=0.0 + +:: change current directory to test suite +::cd %TEST_HOME% + +:: if present clean test benchmark results +if exist %TEST_OUTPUT_PATH% ( + rmdir /s /q %TEST_OUTPUT_PATH% +) + +:: perform nrtest execute +echo INFO: Creating SUT %SUT_BUILD_ID% artifacts +set NRTEST_COMMAND=%NRTEST_EXECUTE_CMD% %TEST_APP_PATH% %TESTS% -o %TEST_OUTPUT_PATH% +:: if there is an error exit the script with error value 1 +%NRTEST_COMMAND% || exit /B 1 + +echo. + +:: perform nrtest compare +echo INFO: Comparing SUT artifacts to REF %REF_BUILD_ID% +set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --rtol %RTOL_VALUE% --atol %ATOL_VALUE% -o benchmark\receipt.json +%NRTEST_COMMAND% + +:: Return user to their current dir +cd %CUR_DIR% From 0afc41b25d23151c69144a7825be3f99db3545fc Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 17 Oct 2019 10:49:25 -0400 Subject: [PATCH 012/266] Work in progress Making script independent of project --- tools/app-config.cmd | 7 +++++++ tools/before-nrtest.cmd | 14 +++++++------- tools/make.cmd | 6 ------ tools/run-nrtests.cmd | 1 + 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/tools/app-config.cmd b/tools/app-config.cmd index 5d9e1a0c7..5dae867a2 100644 --- a/tools/app-config.cmd +++ b/tools/app-config.cmd @@ -6,15 +6,22 @@ :: Author: Michael E. Tryby :: US EPA - ORD/CESER :: +:: Environment Variables: +:: PROJECT +:: :: Arguments: :: 1 - absolute path to test executable (valid path seperator for nrtest is "/") :: 2 - (platform) :: 3 - (build identifier for SUT) :: 4 - (commit hash string) +:: @echo off setlocal +if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) + + :: swmm target created by the cmake build script set TEST_CMD=run%PROJECT%.exe diff --git a/tools/before-nrtest.cmd b/tools/before-nrtest.cmd index 8a41c7e61..27b5a349f 100644 --- a/tools/before-nrtest.cmd +++ b/tools/before-nrtest.cmd @@ -14,7 +14,6 @@ :: Environment Variables: :: PROJECT :: BUILD_HOME - defaults to "build" -:: TEST_HOME - defaults to "nrtests" :: PLATFORM :: :: Arguments: @@ -28,6 +27,9 @@ ::@echo off +:: set global default +set "TEST_HOME=nrtests" + :: determine project directory set "CUR_DIR=%CD%" set "SCRIPT_HOME=%~dp0" @@ -53,9 +55,9 @@ if [%1] == [] (set "RELEASE_TAG=" ) else (set "RELEASE_TAG=%~1") -:: check BUILD_HOME and TEST_HOME and apply defaults -if not defined BUILD_HOME ( set "BUILD_HOME=build" ) -if not defined TEST_HOME ( set "TEST_HOME=nrtests" ) +:: check env variables and apply defaults +if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) +if not defined BUILD_HOME ( echo "ERROR: BUILD_HOME must be defined" & exit /B 1 ) if not defined PLATFORM ( echo "ERROR: PLATFORM must be defined" & exit /B 1 ) @@ -106,9 +108,7 @@ endlocal for /F delims^=^"^ tokens^=4 %%d in ( 'findstr %PLATFORM% %TEST_HOME%\manifest.json' ) do ( for /F "tokens=2" %%r in ( 'echo %%d' ) do ( set "REF_BUILD_ID=%%r" ) ) -if not defined REF_BUILD_ID ( echo "WARNING: REF_BUILD_ID could not be determined" &^ - set "REF_BUILD_ID=unknown" -) +if not defined REF_BUILD_ID ( echo "ERROR: REF_BUILD_ID could not be determined" & exit /B 1 ) :: return to users current directory diff --git a/tools/make.cmd b/tools/make.cmd index f31f649d3..f0c7ac93c 100644 --- a/tools/make.cmd +++ b/tools/make.cmd @@ -14,11 +14,6 @@ :: CMake download: :: https://cmake.org/download/ :: -:: Environment Variables: -:: PROJECT -:: BUILD_HOME - defaults to build -:: PLATFORM -:: :: Optional Arguments: :: /g ("GENERATOR") defaults to "Visual Studio 15 2017" :: /t builds and runs unit tests (requires Boost) @@ -31,7 +26,6 @@ :: set global defaults set "PROJECT=swmm" set "BUILD_HOME=build" -set "TEST_HOME=nrtests" set "PLATFORM=win32" :: determine project directory diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index c78b6af19..fb02a11ae 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -27,6 +27,7 @@ setlocal EnableDelayedExpansion :: Check that required environment variables are set +if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) if not defined BUILD_HOME ( echo "ERROR: BUILD_HOME must be defined" & exit /B 1 ) if not defined TEST_HOME ( echo "ERROR: TEST_HOME must be defined" & exit /B 1 ) if not defined PLATFORM ( echo "ERROR: PLATFORM must be defined" & exit /B 1 ) From 146fc977b41d16e9dcba4eb7e70d57df96ed6a4a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 17 Oct 2019 11:40:52 -0400 Subject: [PATCH 013/266] Work in progress Migrating nrtest-swmm package to swmm-python repo --- tools/nrtest-swmm/nrtest_swmm/__init__.py | 112 -- tools/nrtest-swmm/setup.py | 44 - tools/swmm-reader/main.py | 165 --- tools/swmm-reader/setup.py | 29 - tools/swmm-reader/swmm_reader/__init__.py | 43 - tools/swmm-reader/swmm_reader/outputapi.py | 1069 ------------------ tools/swmm-reader/swmm_reader/swmm_reader.py | 105 -- 7 files changed, 1567 deletions(-) delete mode 100644 tools/nrtest-swmm/nrtest_swmm/__init__.py delete mode 100644 tools/nrtest-swmm/setup.py delete mode 100644 tools/swmm-reader/main.py delete mode 100644 tools/swmm-reader/setup.py delete mode 100644 tools/swmm-reader/swmm_reader/__init__.py delete mode 100644 tools/swmm-reader/swmm_reader/outputapi.py delete mode 100644 tools/swmm-reader/swmm_reader/swmm_reader.py diff --git a/tools/nrtest-swmm/nrtest_swmm/__init__.py b/tools/nrtest-swmm/nrtest_swmm/__init__.py deleted file mode 100644 index dd8abd1c6..000000000 --- a/tools/nrtest-swmm/nrtest_swmm/__init__.py +++ /dev/null @@ -1,112 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Numerical regression testing (nrtest) plugin for comparing SWMM binary results -files and SWMM text based report files. -''' - -# system imports -import itertools as it - -# third party imports -import header_detail_footer as hdf -import numpy as np - -# project imports -import swmm_reader as sr - - -__author__ = "Michael Tryby" -__copyright__ = "None" -__credits__ = "Colleen Barr, Maurizio Cingi, Mark Gray, David Hall, Bryant McDonnell" -__license__ = "CC0 1.0 Universal" - -__version__ = "0.2.0" -__date__ = "September 20, 2016" - -__maintainer__ = "Michael Tryby" -__email__ = "tryby.michael@epa.gov" -__status = "Development" - - -def swmm_allclose_compare(path_test, path_ref, rtol, atol): - ''' - Compares results in two SWMM binary files. Using the comparison criteria - described in the numpy assert_allclose documentation. - - (test_value - ref_value) <= atol + rtol * abs(ref_value) - - Returns true if all of the results in the two binary files meet the - comparison criteria; otherwise, an AssertionError is thrown. - - Numpy allclose is quite expensive to evaluate. Test and reference results - are checked to see if they are equal before being compared using the - allclose criteria. This reduces comparison times significantly. - - Arguments: - path_test - path to result file being tested - path_ref - path to reference result file - rtol - relative tolerance - atol - absolute tolerance - - Returns: - True or raises an error - - Raises: - ValueError() - AssertionError() - ... - ''' - for (test, ref) in it.izip(sr.swmm_output_generator(path_test), - sr.swmm_output_generator(path_ref)): - - if test.size != ref.size: - raise ValueError('Inconsistent lengths') - - # Skip over results if they are equal - if (np.array_equal(test, ref)): - continue - - else: - np.testing.assert_allclose(test, ref, rtol, atol) - - return True - -#def swmm_better_compare(): -# ''' -# If for some reason you don't like numpy.testing.assert_allclose() add a -# better function here. Be sure to add the entry point to the setup file so -# nrtest can find it at runtime. -# ''' -# pass - -def swmm_report_compare(path_test, path_ref, rtol, atol): - ''' - Compares results in two report files ignoring contents of header and footer. - - Arguments: - path_test - path to result file being tested - path_ref - path to reference result file - rtol - ignored - atol - ignored - - Returns: - True or False - - Raises: - HeaderError() - FooterError() - RunTimeError() - ... - ''' - HEADER = 4 - FOOTER = 4 - - with open(path_test ,'r') as ftest, open(path_ref, 'r') as fref: - - for (test_line, ref_line) in it.izip(hdf.parse(ftest, HEADER, FOOTER)[1], - hdf.parse(fref, HEADER, FOOTER)[1]): - - if test_line != ref_line: - return False - - return True diff --git a/tools/nrtest-swmm/setup.py b/tools/nrtest-swmm/setup.py deleted file mode 100644 index 91a2af8c7..000000000 --- a/tools/nrtest-swmm/setup.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- - -''' -Created on Aug 11, 2016 - -@author: mtryby -''' - -try: - from setuptools import setup -except ImportError: - from distutils.core import setup - - -entry_points = { - 'nrtest.compare': [ - 'swmm allclose = nrtest_swmm:swmm_allclose_compare', - 'swmm report = nrtest_swmm:swmm_report_compare', - # Add the entry point for new comparison functions here - ] -} - - -setup( - name='nrtest-swmm', - version='0.2.0', - description="SWMM extension for nrtest", - - author="Michael E. Tryby", - author_email='tryby.michael@epa.gov', - url='https://github.com/USEPA', - - packages=['nrtest_swmm',], - entry_points=entry_points, - include_package_data=True, - install_requires=[ - 'header_detail_footer>=2.3', - 'nrtest>=0.2.0', - 'numpy>=1.6.0', - 'swmm_reader>=0.2.0', - ], - zipsafe=True, - keywords='nrtest_swmm' -) diff --git a/tools/swmm-reader/main.py b/tools/swmm-reader/main.py deleted file mode 100644 index 25571bc89..000000000 --- a/tools/swmm-reader/main.py +++ /dev/null @@ -1,165 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Provides entry point main. Useful for development and testing purposes. -''' - -import cStringIO -import itertools as it -import time - -import header_detail_footer as hdf -import numpy as np - -import swmm_reader as sr - - -def result_compare(path_test, path_ref, comp_args): - - isclose = True - close = 0 - notclose = 0 - equal = 0 - total = 0 - output = cStringIO.StringIO() - eps = np.finfo(float).eps - - start = time.time() - - test_reader = sr.swmm_output_generator(path_test) - ref_reader = sr.swmm_output_generator(path_ref) - - for test, ref in it.izip(test_reader, ref_reader): - total += 1 - if total%100000 == 0: - print(total) - - if test.size != ref.size: - raise ValueError('Inconsistent lengths') - - # Skip results if they are zero or equal - if np.array_equal(test, ref): - equal += 1 - continue - else: - try: - np.testing.assert_allclose(test, ref, comp_args[0], comp_args[1]) - close += 1 - - except AssertionError as ae: - notclose += 1 - output.write(str(ae)) - output.write('\n\n') - continue - - stop = time.time() - - print(output.getvalue()) - output.close() - - print('equal: %d close: %d notclose: %d total: %d in %f (sec)\n' % - (equal, close, notclose, total, (stop - start))) - - if notclose > 0: - print('%d differences found\n' % notclose) - isclose = False - - return isclose - -def array_zero(test, ref): - if not test.any() and not ref.any(): - return True - return False - -def report_compare(path_test, path_ref, (comp_args)): - ''' - Compares results in two report files ignoring contents of header and footer. - ''' - with open(path_test ,'r') as ftest, open(path_ref, 'r') as fref: - for (test_line, ref_line) in it.izip(hdf.parse(ftest, 4, 4)[1], hdf.parse(fref, 4, 4)[1]): - if test_line != ref_line: - return False - - return True - - -import logging -from os import listdir -from os.path import exists, isfile, isdir, join - -from nrtest.testsuite import TestSuite -from nrtest.compare import compare_testsuite, validate_testsuite -from nrtest.execute import execute_testsuite - -def nrtest_compare(path_test, path_ref, rtol, atol): - - ts_new = TestSuite.read_benchmark(path_test) - ts_old = TestSuite.read_benchmark(path_ref) - - if not validate_testsuite(ts_new) or not validate_testsuite(ts_old): - exit(1) - - try: - logging.info('Found %i tests' % len(ts_new.tests)) - compatible = compare_testsuite(ts_new, ts_old, rtol, atol) - except KeyboardInterrupt: - logging.warning('Process interrupted by user') - compatible = False - else: - logging.info('Finished') - - # Non-zero exit code indicates failure - exit(not compatible) - -def nrtest_execute(app_path, test_path, output_path): - - - for p in test_path + [app_path]: - if not exists(p): - logging.error('Could not find path: "%s"' % p) - - test_dirs = [p for p in test_path if isdir(p)] - test_files = [p for p in test_path if isfile(p)] - test_files += [join(d, p) for d in test_dirs for p in listdir(d) - if p.endswith('.json')] - - test_files = list(set(test_files)) # remove duplicates - - ts = TestSuite.read_config(app_path, test_files, output_path) - - if not validate_testsuite(ts): - exit(1) - - try: - logging.info('Found %i tests' % len(test_files)) - success = execute_testsuite(ts) - ts.write_manifest() - except KeyboardInterrupt: - logging.warning('Process interrupted by user') - success = False - else: - logging.info('Finished') - - # Non-zero exit code indicates failure - exit(not success) - - -if __name__ == "__main__": -# app_path = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\apps\\swmm-5111_x64.json" -# test_path = "C:\\Users\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\tests\\update_5111\\events_example.json" -# output_path = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\benchmarks\\v5111_x64" -# nrtest_execute(app_path, [test_path], output_path) - -# test_path = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\benchmarks\\v5111_x64" -# ref_path = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\benchmarks\\v5111_x86" -# nrtest_compare(test_path, ref_path, 0.001, 0.0) - - -# path_test = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\benchmarks\\v5111\\Example_4\\Example4.out" -# path_ref = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\benchmarks\\v5110\\Example_4\\Example4.out" -# result_compare(path_test, path_ref, (0.001, 0.0)) - - path_test = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\benchmarks\\v5111_bf\\lid_cat\\lid_cat.out" - path_ref = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\benchmarks\\v5110\\lid_cat\\lid_cat.out" - print(result_compare(path_test, path_ref, (1.0, 0.0))) - - \ No newline at end of file diff --git a/tools/swmm-reader/setup.py b/tools/swmm-reader/setup.py deleted file mode 100644 index 2995909da..000000000 --- a/tools/swmm-reader/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- - -try: - from setuptools import setup -except ImportError: - from distutils.core import setup - - -setup( - name = "swmm-reader", - version = "0.2.0", - description = "Tools for reading SWMM binary results file", - - author="Michael E. Tryby", - author_email='tryby.michael@epa.gov', - url='https://github.com/USEPA', - - packages = ['swmm_reader'], - - install_requires = ['numpy', 'enum34'], - - package_data = { - 'swmm_reader':['*.dll'] - }, - include_package_data = True, - - zip_safe = False, - keywords='swmm_reader' -) diff --git a/tools/swmm-reader/swmm_reader/__init__.py b/tools/swmm-reader/swmm_reader/__init__.py deleted file mode 100644 index a85c0a0e1..000000000 --- a/tools/swmm-reader/swmm_reader/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -''' -The function swmm_output_generator is used to iterate over a swmm binary file. -''' - -# project import -from .swmm_reader import * - - -__author__ = "Michael Tryby" -__copyright = "None" -__credits__ = "Colleen Barr, Maurizio Cingi, Mark Gray, David Hall, Bryant McDonnell" -__license__ = "CC0 1.0 Universal" - -__version__ = "0.2.0" -__maintainer__ = "Michael Tryby" -__email__= "tryby.michael@epa.gov" -__status = "Development" - - -def swmm_output_generator(path_ref): - ''' - swmm_output_generator is designed to iterate over a swmm binary file and - yield element results. It is useful for comparing contents of binary files - for numerical regression testing. - - The generator yields a numpy array containing the SWMM element result. - - Arguments: - path_ref - path to result file - - Raises: - SWMM_OutputReaderError() - ... - ''' - with SWMM_OutputReader(path_ref) as sor: - - for period_index in range(0, sor.report_periods()): - for element_type in ElementType: - for element_index in range(0, sor.element_count(element_type)): - - yield sor.element_result(element_type, period_index, element_index) - \ No newline at end of file diff --git a/tools/swmm-reader/swmm_reader/outputapi.py b/tools/swmm-reader/swmm_reader/outputapi.py deleted file mode 100644 index cbcf4df49..000000000 --- a/tools/swmm-reader/swmm_reader/outputapi.py +++ /dev/null @@ -1,1069 +0,0 @@ -# -*- coding: utf-8 -*- - -'''Wrapper for outputapi.h - -Generated with: -C:\Users\mtryby\dev\Anaconda2\Scripts\ctypesgen.py -a -l libswmm-output -o outputapi.py .\src\outputapi.h - -Do not modify this file ... - -THIS FILE HAS BEEN MODIFIED! See WindowsLibraryLoader.genplatformpaths() starting on line 560 -''' - -__docformat__ = 'restructuredtext' - -# Begin preamble - -import ctypes, os, sys -from ctypes import * - -_int_types = (c_int16, c_int32) -if hasattr(ctypes, 'c_int64'): - # Some builds of ctypes apparently do not have c_int64 - # defined; it's a pretty good bet that these builds do not - # have 64-bit pointers. - _int_types += (c_int64,) -for t in _int_types: - if sizeof(t) == sizeof(c_size_t): - c_ptrdiff_t = t -del t -del _int_types - -class c_void(Structure): - # c_void_p is a buggy return type, converting to int, so - # POINTER(None) == c_void_p is actually written as - # POINTER(c_void), so it can be treated as a real pointer. - _fields_ = [('dummy', c_int)] - -def POINTER(obj): - p = ctypes.POINTER(obj) - - # Convert None to a real NULL pointer to work around bugs - # in how ctypes handles None on 64-bit platforms - if not isinstance(p.from_param, classmethod): - def from_param(cls, x): - if x is None: - return cls() - else: - return x - p.from_param = classmethod(from_param) - - return p - -class UserString: - def __init__(self, seq): - if isinstance(seq, basestring): - self.data = seq - elif isinstance(seq, UserString): - self.data = seq.data[:] - else: - self.data = str(seq) - def __str__(self): return str(self.data) - def __repr__(self): return repr(self.data) - def __int__(self): return int(self.data) - def __long__(self): return long(self.data) - def __float__(self): return float(self.data) - def __complex__(self): return complex(self.data) - def __hash__(self): return hash(self.data) - - def __cmp__(self, string): - if isinstance(string, UserString): - return cmp(self.data, string.data) - else: - return cmp(self.data, string) - def __contains__(self, char): - return char in self.data - - def __len__(self): return len(self.data) - def __getitem__(self, index): return self.__class__(self.data[index]) - def __getslice__(self, start, end): - start = max(start, 0); end = max(end, 0) - return self.__class__(self.data[start:end]) - - def __add__(self, other): - if isinstance(other, UserString): - return self.__class__(self.data + other.data) - elif isinstance(other, basestring): - return self.__class__(self.data + other) - else: - return self.__class__(self.data + str(other)) - def __radd__(self, other): - if isinstance(other, basestring): - return self.__class__(other + self.data) - else: - return self.__class__(str(other) + self.data) - def __mul__(self, n): - return self.__class__(self.data*n) - __rmul__ = __mul__ - def __mod__(self, args): - return self.__class__(self.data % args) - - # the following methods are defined in alphabetical order: - def capitalize(self): return self.__class__(self.data.capitalize()) - def center(self, width, *args): - return self.__class__(self.data.center(width, *args)) - def count(self, sub, start=0, end=sys.maxint): - return self.data.count(sub, start, end) - def decode(self, encoding=None, errors=None): # XXX improve this? - if encoding: - if errors: - return self.__class__(self.data.decode(encoding, errors)) - else: - return self.__class__(self.data.decode(encoding)) - else: - return self.__class__(self.data.decode()) - def encode(self, encoding=None, errors=None): # XXX improve this? - if encoding: - if errors: - return self.__class__(self.data.encode(encoding, errors)) - else: - return self.__class__(self.data.encode(encoding)) - else: - return self.__class__(self.data.encode()) - def endswith(self, suffix, start=0, end=sys.maxint): - return self.data.endswith(suffix, start, end) - def expandtabs(self, tabsize=8): - return self.__class__(self.data.expandtabs(tabsize)) - def find(self, sub, start=0, end=sys.maxint): - return self.data.find(sub, start, end) - def index(self, sub, start=0, end=sys.maxint): - return self.data.index(sub, start, end) - def isalpha(self): return self.data.isalpha() - def isalnum(self): return self.data.isalnum() - def isdecimal(self): return self.data.isdecimal() - def isdigit(self): return self.data.isdigit() - def islower(self): return self.data.islower() - def isnumeric(self): return self.data.isnumeric() - def isspace(self): return self.data.isspace() - def istitle(self): return self.data.istitle() - def isupper(self): return self.data.isupper() - def join(self, seq): return self.data.join(seq) - def ljust(self, width, *args): - return self.__class__(self.data.ljust(width, *args)) - def lower(self): return self.__class__(self.data.lower()) - def lstrip(self, chars=None): return self.__class__(self.data.lstrip(chars)) - def partition(self, sep): - return self.data.partition(sep) - def replace(self, old, new, maxsplit=-1): - return self.__class__(self.data.replace(old, new, maxsplit)) - def rfind(self, sub, start=0, end=sys.maxint): - return self.data.rfind(sub, start, end) - def rindex(self, sub, start=0, end=sys.maxint): - return self.data.rindex(sub, start, end) - def rjust(self, width, *args): - return self.__class__(self.data.rjust(width, *args)) - def rpartition(self, sep): - return self.data.rpartition(sep) - def rstrip(self, chars=None): return self.__class__(self.data.rstrip(chars)) - def split(self, sep=None, maxsplit=-1): - return self.data.split(sep, maxsplit) - def rsplit(self, sep=None, maxsplit=-1): - return self.data.rsplit(sep, maxsplit) - def splitlines(self, keepends=0): return self.data.splitlines(keepends) - def startswith(self, prefix, start=0, end=sys.maxint): - return self.data.startswith(prefix, start, end) - def strip(self, chars=None): return self.__class__(self.data.strip(chars)) - def swapcase(self): return self.__class__(self.data.swapcase()) - def title(self): return self.__class__(self.data.title()) - def translate(self, *args): - return self.__class__(self.data.translate(*args)) - def upper(self): return self.__class__(self.data.upper()) - def zfill(self, width): return self.__class__(self.data.zfill(width)) - -class MutableString(UserString): - """mutable string objects - - Python strings are immutable objects. This has the advantage, that - strings may be used as dictionary keys. If this property isn't needed - and you insist on changing string values in place instead, you may cheat - and use MutableString. - - But the purpose of this class is an educational one: to prevent - people from inventing their own mutable string class derived - from UserString and than forget thereby to remove (override) the - __hash__ method inherited from UserString. This would lead to - errors that would be very hard to track down. - - A faster and better solution is to rewrite your program using lists.""" - def __init__(self, string=""): - self.data = string - def __hash__(self): - raise TypeError("unhashable type (it is mutable)") - def __setitem__(self, index, sub): - if index < 0: - index += len(self.data) - if index < 0 or index >= len(self.data): raise IndexError - self.data = self.data[:index] + sub + self.data[index+1:] - def __delitem__(self, index): - if index < 0: - index += len(self.data) - if index < 0 or index >= len(self.data): raise IndexError - self.data = self.data[:index] + self.data[index+1:] - def __setslice__(self, start, end, sub): - start = max(start, 0); end = max(end, 0) - if isinstance(sub, UserString): - self.data = self.data[:start]+sub.data+self.data[end:] - elif isinstance(sub, basestring): - self.data = self.data[:start]+sub+self.data[end:] - else: - self.data = self.data[:start]+str(sub)+self.data[end:] - def __delslice__(self, start, end): - start = max(start, 0); end = max(end, 0) - self.data = self.data[:start] + self.data[end:] - def immutable(self): - return UserString(self.data) - def __iadd__(self, other): - if isinstance(other, UserString): - self.data += other.data - elif isinstance(other, basestring): - self.data += other - else: - self.data += str(other) - return self - def __imul__(self, n): - self.data *= n - return self - -class String(MutableString, Union): - - _fields_ = [('raw', POINTER(c_char)), - ('data', c_char_p)] - - def __init__(self, obj=""): - if isinstance(obj, (str, unicode, UserString)): - self.data = str(obj) - else: - self.raw = obj - - def __len__(self): - return self.data and len(self.data) or 0 - - def from_param(cls, obj): - # Convert None or 0 - if obj is None or obj == 0: - return cls(POINTER(c_char)()) - - # Convert from String - elif isinstance(obj, String): - return obj - - # Convert from str - elif isinstance(obj, str): - return cls(obj) - - # Convert from c_char_p - elif isinstance(obj, c_char_p): - return obj - - # Convert from POINTER(c_char) - elif isinstance(obj, POINTER(c_char)): - return obj - - # Convert from raw pointer - elif isinstance(obj, int): - return cls(cast(obj, POINTER(c_char))) - - # Convert from object - else: - return String.from_param(obj._as_parameter_) - from_param = classmethod(from_param) - -def ReturnString(obj, func=None, arguments=None): - return String.from_param(obj) - -# As of ctypes 1.0, ctypes does not support custom error-checking -# functions on callbacks, nor does it support custom datatypes on -# callbacks, so we must ensure that all callbacks return -# primitive datatypes. -# -# Non-primitive return values wrapped with UNCHECKED won't be -# typechecked, and will be converted to c_void_p. -def UNCHECKED(type): - if (hasattr(type, "_type_") and isinstance(type._type_, str) - and type._type_ != "P"): - return type - else: - return c_void_p - -# ctypes doesn't have direct support for variadic functions, so we have to write -# our own wrapper class -class _variadic_function(object): - def __init__(self,func,restype,argtypes): - self.func=func - self.func.restype=restype - self.argtypes=argtypes - def _as_parameter_(self): - # So we can pass this variadic function as a function pointer - return self.func - def __call__(self,*args): - fixed_args=[] - i=0 - for argtype in self.argtypes: - # Typecheck what we can - fixed_args.append(argtype.from_param(args[i])) - i+=1 - return self.func(*fixed_args+list(args[i:])) - -# End preamble - -_libs = {} -_libdirs = [] - -# Begin loader - -# ---------------------------------------------------------------------------- -# Copyright (c) 2008 David James -# Copyright (c) 2006-2008 Alex Holkner -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. -# * Neither the name of pyglet nor the names of its -# contributors may be used to endorse or promote products -# derived from this software without specific prior written -# permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# ---------------------------------------------------------------------------- - -import os.path, re, sys, glob -import ctypes -import ctypes.util - -def _environ_path(name): - if name in os.environ: - return os.environ[name].split(":") - else: - return [] - -class LibraryLoader(object): - def __init__(self): - self.other_dirs=[] - - def load_library(self,libname): - """Given the name of a library, load it.""" - paths = self.getpaths(libname) - - for path in paths: - if os.path.exists(path): - return self.load(path) - - raise ImportError("%s not found." % libname) - - def load(self,path): - """Given a path to a library, load it.""" - try: - # Darwin requires dlopen to be called with mode RTLD_GLOBAL instead - # of the default RTLD_LOCAL. Without this, you end up with - # libraries not being loadable, resulting in "Symbol not found" - # errors - if sys.platform == 'darwin': - return ctypes.CDLL(path, ctypes.RTLD_GLOBAL) - else: - return ctypes.cdll.LoadLibrary(path) - except OSError,e: - raise ImportError(e) - - def getpaths(self,libname): - """Return a list of paths where the library might be found.""" - if os.path.isabs(libname): - yield libname - - else: - for path in self.getplatformpaths(libname): - yield path - - path = ctypes.util.find_library(libname) - if path: yield path - - def getplatformpaths(self, libname): - return [] - -# Darwin (Mac OS X) - -class DarwinLibraryLoader(LibraryLoader): - name_formats = ["lib%s.dylib", "lib%s.so", "lib%s.bundle", "%s.dylib", - "%s.so", "%s.bundle", "%s"] - - def getplatformpaths(self,libname): - if os.path.pathsep in libname: - names = [libname] - else: - names = [format % libname for format in self.name_formats] - - for dir in self.getdirs(libname): - for name in names: - yield os.path.join(dir,name) - - def getdirs(self,libname): - '''Implements the dylib search as specified in Apple documentation: - - http://developer.apple.com/documentation/DeveloperTools/Conceptual/ - DynamicLibraries/Articles/DynamicLibraryUsageGuidelines.html - - Before commencing the standard search, the method first checks - the bundle's ``Frameworks`` directory if the application is running - within a bundle (OS X .app). - ''' - - dyld_fallback_library_path = _environ_path("DYLD_FALLBACK_LIBRARY_PATH") - if not dyld_fallback_library_path: - dyld_fallback_library_path = [os.path.expanduser('~/lib'), - '/usr/local/lib', '/usr/lib'] - - dirs = [] - - if '/' in libname: - dirs.extend(_environ_path("DYLD_LIBRARY_PATH")) - else: - dirs.extend(_environ_path("LD_LIBRARY_PATH")) - dirs.extend(_environ_path("DYLD_LIBRARY_PATH")) - - dirs.extend(self.other_dirs) - dirs.append(".") - - if hasattr(sys, 'frozen') and sys.frozen == 'macosx_app': - dirs.append(os.path.join( - os.environ['RESOURCEPATH'], - '..', - 'Frameworks')) - - dirs.extend(dyld_fallback_library_path) - - return dirs - -# Posix - -class PosixLibraryLoader(LibraryLoader): - _ld_so_cache = None - - def _create_ld_so_cache(self): - # Recreate search path followed by ld.so. This is going to be - # slow to build, and incorrect (ld.so uses ld.so.cache, which may - # not be up-to-date). Used only as fallback for distros without - # /sbin/ldconfig. - # - # We assume the DT_RPATH and DT_RUNPATH binary sections are omitted. - - directories = [] - for name in ("LD_LIBRARY_PATH", - "SHLIB_PATH", # HPUX - "LIBPATH", # OS/2, AIX - "LIBRARY_PATH", # BE/OS - ): - if name in os.environ: - directories.extend(os.environ[name].split(os.pathsep)) - directories.extend(self.other_dirs) - directories.append(".") - - try: directories.extend([dir.strip() for dir in open('/etc/ld.so.conf')]) - except IOError: pass - - directories.extend(['/lib', '/usr/lib', '/lib64', '/usr/lib64']) - - cache = {} - lib_re = re.compile(r'lib(.*)\.s[ol]') - ext_re = re.compile(r'\.s[ol]$') - for dir in directories: - try: - for path in glob.glob("%s/*.s[ol]*" % dir): - file = os.path.basename(path) - - # Index by filename - if file not in cache: - cache[file] = path - - # Index by library name - match = lib_re.match(file) - if match: - library = match.group(1) - if library not in cache: - cache[library] = path - except OSError: - pass - - self._ld_so_cache = cache - - def getplatformpaths(self, libname): - if self._ld_so_cache is None: - self._create_ld_so_cache() - - result = self._ld_so_cache.get(libname) - if result: yield result - - path = ctypes.util.find_library(libname) - if path: yield os.path.join("/lib",path) - -# Windows - -class _WindowsLibrary(object): - def __init__(self, path): - self.cdll = ctypes.cdll.LoadLibrary(path) - self.windll = ctypes.windll.LoadLibrary(path) - - def __getattr__(self, name): - try: return getattr(self.cdll,name) - except AttributeError: - try: return getattr(self.windll,name) - except AttributeError: - raise - -class WindowsLibraryLoader(LibraryLoader): - name_formats = ["%s.dll", "lib%s.dll", "%slib.dll"] - - def load_library(self, libname): - try: - result = LibraryLoader.load_library(self, libname) - except ImportError: - result = None - if os.path.sep not in libname: - for name in self.name_formats: - try: - result = getattr(ctypes.cdll, name % libname) - if result: - break - except WindowsError: - result = None - if result is None: - try: - result = getattr(ctypes.cdll, libname) - except WindowsError: - result = None - if result is None: - raise ImportError("%s not found." % libname) - return result - - def load(self, path): - return _WindowsLibrary(path) - -############################################################################### -###### THIS NEEDS TO BE HAND CODED OR LIBRARY LOAD WILL BREAK ON WINDOWS ###### - def getplatformpaths(self, libname): - if os.path.sep not in libname: - for name in self.name_formats: - # search the directory where this file is executing - dll_in_package_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), name % libname) - if os.path.exists(dll_in_package_dir): - yield dll_in_package_dir - # search in directories passed as arguments to wrapper generator - for dir in self.other_dirs: - dll_in_other_dirs = os.path.join(dir, name % libname) - if os.path.exists(dll_in_other_dirs): - yield dll_in_other_dirs - # searches current working directory - dll_in_current_dir = os.path.abspath(name % libname) - if os.path.exists(dll_in_current_dir): - yield dll_in_current_dir - # searches system path - path = ctypes.util.find_library(name % libname) - if path: - yield path -############################################################################### -############################################################################### - -# Platform switching - -# If your value of sys.platform does not appear in this dict, please contact -# the Ctypesgen maintainers. - -loaderclass = { - "darwin": DarwinLibraryLoader, - "cygwin": WindowsLibraryLoader, - "win32": WindowsLibraryLoader -} - -loader = loaderclass.get(sys.platform, PosixLibraryLoader)() - -def add_library_search_dirs(other_dirs): - loader.other_dirs = other_dirs - -load_library = loader.load_library - -del loaderclass - -# End loader - -add_library_search_dirs([]) - -# Begin libraries - -_libs["libswmm-output"] = load_library("libswmm-output") - -# 1 libraries -# End libraries - -# No modules - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 33 -class struct_SMOutputAPI(Structure): - pass - -SMOutputAPI = struct_SMOutputAPI # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 33 - -enum_anon_1 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 41 - -subcatchCount = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 41 - -nodeCount = (subcatchCount + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 41 - -linkCount = (nodeCount + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 41 - -pollutantCount = (linkCount + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 41 - -SMO_elementCount = enum_anon_1 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 41 - -enum_anon_2 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 47 - -flow_rate = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 47 - -concentration = (flow_rate + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 47 - -SMO_unit = enum_anon_2 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 47 - -enum_anon_3 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 54 - -getAttribute = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 54 - -getResult = (getAttribute + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 54 - -SMO_apiFunction = enum_anon_3 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 54 - -enum_anon_4 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 62 - -subcatch = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 62 - -node = (subcatch + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 62 - -link = (node + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 62 - -_sys = (link + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 62 - -SMO_elementType = enum_anon_4 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 62 - -enum_anon_5 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 69 - -reportStep = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 69 - -numPeriods = (reportStep + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 69 - -SMO_time = enum_anon_5 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 69 - -enum_anon_6 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -rainfall_subcatch = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -snow_depth_subcatch = (rainfall_subcatch + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -evap_loss = (snow_depth_subcatch + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -infil_loss = (evap_loss + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -runoff_rate = (infil_loss + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -gwoutflow_rate = (runoff_rate + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -gwtable_elev = (gwoutflow_rate + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -soil_moisture = (gwtable_elev + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -pollutant_conc_subcatch = (soil_moisture + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -SMO_subcatchAttribute = enum_anon_6 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 82 - -enum_anon_7 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -invert_depth = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -hydraulic_head = (invert_depth + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -stored_ponded_volume = (hydraulic_head + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -lateral_inflow = (stored_ponded_volume + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -total_inflow = (lateral_inflow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -flooding_losses = (total_inflow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -pollutant_conc_node = (flooding_losses + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -SMO_nodeAttribute = enum_anon_7 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 93 - -enum_anon_8 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -flow_rate_link = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -flow_depth = (flow_rate_link + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -flow_velocity = (flow_depth + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -flow_volume = (flow_velocity + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -capacity = (flow_volume + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -pollutant_conc_link = (capacity + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -SMO_linkAttribute = enum_anon_8 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 103 - -enum_anon_9 = c_int # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -air_temp = 0 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -rainfall_system = (air_temp + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -snow_depth_system = (rainfall_system + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -evap_infil_loss = (snow_depth_system + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -runoff_flow = (evap_infil_loss + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -dry_weather_inflow = (runoff_flow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -groundwater_inflow = (dry_weather_inflow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -RDII_inflow = (groundwater_inflow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -direct_inflow = (RDII_inflow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -total_lateral_inflow = (direct_inflow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -flood_losses = (total_lateral_inflow + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -outfall_flows = (flood_losses + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -volume_stored = (outfall_flows + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -evap_rate = (volume_stored + 1) # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -SMO_systemAttribute = enum_anon_9 # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 121 - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 138 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_init'): - continue - SMO_init = _lib.SMO_init - SMO_init.argtypes = [] - SMO_init.restype = POINTER(SMOutputAPI) - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 139 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_open'): - continue - SMO_open = _lib.SMO_open - SMO_open.argtypes = [POINTER(SMOutputAPI), String] - SMO_open.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 141 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getProjectSize'): - continue - SMO_getProjectSize = _lib.SMO_getProjectSize - SMO_getProjectSize.argtypes = [POINTER(SMOutputAPI), SMO_elementCount, POINTER(c_int)] - SMO_getProjectSize.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 143 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getUnits'): - continue - SMO_getUnits = _lib.SMO_getUnits - SMO_getUnits.argtypes = [POINTER(SMOutputAPI), SMO_unit, POINTER(c_int)] - SMO_getUnits.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 144 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getStartTime'): - continue - SMO_getStartTime = _lib.SMO_getStartTime - SMO_getStartTime.argtypes = [POINTER(SMOutputAPI), POINTER(c_double)] - SMO_getStartTime.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 145 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getTimes'): - continue - SMO_getTimes = _lib.SMO_getTimes - SMO_getTimes.argtypes = [POINTER(SMOutputAPI), SMO_time, POINTER(c_int)] - SMO_getTimes.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 147 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getElementName'): - continue - SMO_getElementName = _lib.SMO_getElementName - SMO_getElementName.argtypes = [POINTER(SMOutputAPI), SMO_elementType, c_int, String, c_int] - SMO_getElementName.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 150 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_newOutValueSeries'): - continue - SMO_newOutValueSeries = _lib.SMO_newOutValueSeries - SMO_newOutValueSeries.argtypes = [POINTER(SMOutputAPI), c_long, c_long, POINTER(c_long), POINTER(c_int)] - SMO_newOutValueSeries.restype = POINTER(c_float) - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 152 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_newOutValueArray'): - continue - SMO_newOutValueArray = _lib.SMO_newOutValueArray - SMO_newOutValueArray.argtypes = [POINTER(SMOutputAPI), SMO_apiFunction, SMO_elementType, POINTER(c_long), POINTER(c_int)] - SMO_newOutValueArray.restype = POINTER(c_float) - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 155 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getSubcatchSeries'): - continue - SMO_getSubcatchSeries = _lib.SMO_getSubcatchSeries - SMO_getSubcatchSeries.argtypes = [POINTER(SMOutputAPI), c_int, SMO_subcatchAttribute, c_long, c_long, POINTER(c_float)] - SMO_getSubcatchSeries.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 157 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getNodeSeries'): - continue - SMO_getNodeSeries = _lib.SMO_getNodeSeries - SMO_getNodeSeries.argtypes = [POINTER(SMOutputAPI), c_int, SMO_nodeAttribute, c_long, c_long, POINTER(c_float)] - SMO_getNodeSeries.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 159 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getLinkSeries'): - continue - SMO_getLinkSeries = _lib.SMO_getLinkSeries - SMO_getLinkSeries.argtypes = [POINTER(SMOutputAPI), c_int, SMO_linkAttribute, c_long, c_long, POINTER(c_float)] - SMO_getLinkSeries.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 161 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getSystemSeries'): - continue - SMO_getSystemSeries = _lib.SMO_getSystemSeries - SMO_getSystemSeries.argtypes = [POINTER(SMOutputAPI), SMO_systemAttribute, c_long, c_long, POINTER(c_float)] - SMO_getSystemSeries.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 164 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getSubcatchAttribute'): - continue - SMO_getSubcatchAttribute = _lib.SMO_getSubcatchAttribute - SMO_getSubcatchAttribute.argtypes = [POINTER(SMOutputAPI), c_long, SMO_subcatchAttribute, POINTER(c_float)] - SMO_getSubcatchAttribute.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 166 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getNodeAttribute'): - continue - SMO_getNodeAttribute = _lib.SMO_getNodeAttribute - SMO_getNodeAttribute.argtypes = [POINTER(SMOutputAPI), c_long, SMO_nodeAttribute, POINTER(c_float)] - SMO_getNodeAttribute.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 168 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getLinkAttribute'): - continue - SMO_getLinkAttribute = _lib.SMO_getLinkAttribute - SMO_getLinkAttribute.argtypes = [POINTER(SMOutputAPI), c_long, SMO_linkAttribute, POINTER(c_float)] - SMO_getLinkAttribute.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 170 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getSystemAttribute'): - continue - SMO_getSystemAttribute = _lib.SMO_getSystemAttribute - SMO_getSystemAttribute.argtypes = [POINTER(SMOutputAPI), c_long, SMO_systemAttribute, POINTER(c_float)] - SMO_getSystemAttribute.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 173 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getSubcatchResult'): - continue - SMO_getSubcatchResult = _lib.SMO_getSubcatchResult - SMO_getSubcatchResult.argtypes = [POINTER(SMOutputAPI), c_long, c_int, POINTER(c_float), POINTER(c_int)] - SMO_getSubcatchResult.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 175 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getNodeResult'): - continue - SMO_getNodeResult = _lib.SMO_getNodeResult - SMO_getNodeResult.argtypes = [POINTER(SMOutputAPI), c_long, c_int, POINTER(c_float), POINTER(c_int)] - SMO_getNodeResult.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 177 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getLinkResult'): - continue - SMO_getLinkResult = _lib.SMO_getLinkResult - SMO_getLinkResult.argtypes = [POINTER(SMOutputAPI), c_long, c_int, POINTER(c_float), POINTER(c_int)] - SMO_getLinkResult.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 179 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_getSystemResult'): - continue - SMO_getSystemResult = _lib.SMO_getSystemResult - SMO_getSystemResult.argtypes = [POINTER(SMOutputAPI), c_long, c_int, POINTER(c_float), POINTER(c_int)] - SMO_getSystemResult.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 182 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_free'): - continue - SMO_free = _lib.SMO_free - SMO_free.argtypes = [POINTER(c_float)] - SMO_free.restype = None - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 184 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_close'): - continue - SMO_close = _lib.SMO_close - SMO_close.argtypes = [POINTER(SMOutputAPI)] - SMO_close.restype = c_int - break - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 185 -for _lib in _libs.itervalues(): - if not hasattr(_lib, 'SMO_errMessage'): - continue - SMO_errMessage = _lib.SMO_errMessage - SMO_errMessage.argtypes = [c_int, String, c_int] - SMO_errMessage.restype = None - break - -__const = c_int # : 5 - -# : 8 -try: - CTYPESGEN = 1 -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 13 -try: - MAXFILENAME = 259 -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 14 -try: - MAXELENAME = 31 -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 17 -try: - ERR410 = 'Error 410: SMO_init() has not been called' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 18 -try: - ERR411 = 'Error 411: SMO_open() has not been called' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 19 -try: - ERR414 = 'Error 414: memory allocation failure' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 21 -try: - ERR421 = 'Input Error 421: invalid parameter code' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 22 -try: - ERR422 = 'Input Error 422: reporting period index out of range' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 23 -try: - ERR423 = 'Input Error 423: element index out of range' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 24 -try: - ERR424 = 'Input Error 424: no memory allocated for results' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 26 -try: - ERR434 = 'File Error 434: unable to open binary output file' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 27 -try: - ERR435 = 'File Error 435: invalid file - not created by SWMM' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 28 -try: - ERR436 = 'File Error 436: invalid file - contains no results' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 29 -try: - ERR437 = 'File Error 437: invalid file - model run issued warnings' -except: - pass - -# C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 31 -try: - ERR440 = 'ERROR 440: an unspecified error has occurred' -except: - pass - -SMOutputAPI = struct_SMOutputAPI # C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-output\\src\\outputapi.h: 33 - -# No inserted files - diff --git a/tools/swmm-reader/swmm_reader/swmm_reader.py b/tools/swmm-reader/swmm_reader/swmm_reader.py deleted file mode 100644 index 65579a02d..000000000 --- a/tools/swmm-reader/swmm_reader/swmm_reader.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- -''' -The module swmm_reader provides classes used to implement the swmm output -generator. -''' - -# system imports -import ctypes - -# third party imports -import enum -import numpy as np - -# project import -import outputapi - - -__author__ = "Michael Tryby" -__copyright = "None" -__credits__ = "Colleen Barr, Maurizio Cingi, Mark Gray, David Hall, Bryant McDonnell" -__license__ = "CC0 1.0 Universal" - -__version__ = "0.2.0" -__maintainer__ = "Michael Tryby" -__email__= "tryby.michael@epa.gov" -__status = "Development" - - -_err_max_char = 80 - -class ElementType(enum.Enum): - subcatch = outputapi.subcatch - node = outputapi.node - link = outputapi.link - system = outputapi._sys - -class SWMM_OutputReaderError(Exception): - ''' - Custom exception class for SWMM errors. - ''' - def __init__(self, error_code, error_message): - self.warning = False - self.args = (error_code,) - self.message = error_message - - def __str__(self): - return self.message - -class SWMM_OutputReader(): - ''' - Provides minimal functionality needed to implement the SWMM output generator. - ''' - def __init__(self, filename): - self.filepath = filename - self.ptr_api = ctypes.c_void_p - self.ptr_resultbuff = ctypes.c_void_p - self.bufflength = ctypes.c_long() - self.getElementResult = {ElementType.subcatch: outputapi.SMO_getSubcatchResult, - ElementType.node: outputapi.SMO_getNodeResult, - ElementType.link: outputapi.SMO_getLinkResult, - ElementType.system: outputapi.SMO_getSystemResult} - - def __enter__(self): - self.ptr_api = outputapi.SMO_init() - self._error_check(outputapi.SMO_open(self.ptr_api, ctypes.c_char_p(self.filepath.encode()))) - - # max system result is vector with 15 elements so should be adequate for result buffer - # TODO: What about when there are more than six pollutants defined? - error = ctypes.c_long() - self.ptr_resultbuff = outputapi.SMO_newOutValueArray(self.ptr_api, ctypes.c_int(outputapi.getResult), - ctypes.c_int(ElementType.system.value), - ctypes.byref(self.bufflength), ctypes.byref(error)) - self._error_check(error.value) - return self - - def __exit__(self, type, value, traceback): - outputapi.SMO_free(self.ptr_resultbuff) - self._error_check(outputapi.SMO_close(self.ptr_api)) - - def _error_message(self, code): - error_code = ctypes.c_int(code) - error_message = outputapi.String(ctypes.create_string_buffer(_err_max_char)) - outputapi.SMO_errMessage(error_code, error_message, _err_max_char) - return error_message.data - - def _error_check(self, err): - if err != 0: - raise SWMM_OutputReaderError(err, self._error_message(err)) - - def report_periods(self): - num_periods = ctypes.c_int() - self._error_check(outputapi.SMO_getTimes(self.ptr_api, outputapi.numPeriods, ctypes.byref(num_periods))) - return num_periods.value - - def element_count(self, element_type): - count = ctypes.c_int() - self._error_check(outputapi.SMO_getProjectSize(self.ptr_api, ctypes.c_int(element_type.value), ctypes.byref(count))) - return count.value - - def element_result(self, element_type, time_index, element_index): - time_idx = ctypes.c_long(time_index) - element_idx = ctypes.c_int(element_index) - count = ctypes.c_int() - self._error_check(self.getElementResult[element_type](self.ptr_api, time_idx, element_idx, self.ptr_resultbuff, ctypes.byref(count))) - return np.fromiter((self.ptr_resultbuff[i] for i in range(count.value)), np.float, count.value) From fc819a1ba4125b60138f6633fd47587e777b0dc6 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 18 Oct 2019 15:04:25 -0400 Subject: [PATCH 014/266] Work in progress --- CMakeLists.txt | 6 ++++++ tools/app-config.cmd | 14 ++++++++------ tools/before-nrtest.cmd | 8 ++++---- tools/requirements.txt | 22 ++++++++++++++++++++++ 4 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 tools/requirements.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 89caecc5f..39dce1505 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,26 +9,31 @@ cmake_minimum_required (VERSION 3.0) + project(swmm-solver VERSION 5.1.14 LANGUAGES C CXX ) + # Sets default install prefix when cmakecache is initialized for first time if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "..." FORCE) endif() + # Define install locations (will be prepended by install prefix) set(TOOL_DIST "bin") set(INCLUDE_DIST "include") set(LIBRARY_DIST "lib") set(CONFIG_DIST "cmake") + # Add project subdirectories add_subdirectory(src/solver) add_subdirectory(src/run) + # Create target import scripts so other cmake projects can use swmm libraries install( EXPORT @@ -39,6 +44,7 @@ install( swmm5-config.cmake ) + # Configure CPack driven installer package set(CPACK_GENERATOR "ZIP") set(CPACK_PACKAGE_VENDOR "US_EPA") diff --git a/tools/app-config.cmd b/tools/app-config.cmd index 5dae867a2..f1e48302d 100644 --- a/tools/app-config.cmd +++ b/tools/app-config.cmd @@ -2,6 +2,7 @@ :: app-config.cmd - Generates nrtest app configuration file for SUT executable :: :: Date Created: 10/16/2019 +:: Date Modified: 10/17/2019 :: :: Author: Michael E. Tryby :: US EPA - ORD/CESER @@ -29,14 +30,15 @@ set TEST_CMD=run%PROJECT%.exe set ABS_BUILD_PATH=%~1 set ABS_BUILD_PATH=%ABS_BUILD_PATH:\=/% -IF [%2]==[] ( set "PLATFORM=unknown" -) ELSE ( set "PLATFORM=%~2" ) +if [%2]==[] ( set "PLATFORM=unknown" +) else ( set "PLATFORM=%~2" ) -IF [%3]==[] ( set "BUILD_ID=unknown" -) ELSE ( set "BUILD_ID=%~3" ) +if [%3]==[] ( set "BUILD_ID=unknown" +) else ( set "BUILD_ID=%~3" ) -IF [%4]==[] ( set "VERSION=unknown" -) ELSE ( set "VERSION=%~4" ) +if "%4" == "unknown" ( + for /F "tokens=1" %%v in ( '%ABS_BUILD_PATH%/%TEST_CMD% --version' ) do ( set "VERSION=%%v" ) +) else ( set "VERSION=%~4" ) echo { diff --git a/tools/before-nrtest.cmd b/tools/before-nrtest.cmd index 27b5a349f..0dcdd6f43 100644 --- a/tools/before-nrtest.cmd +++ b/tools/before-nrtest.cmd @@ -47,8 +47,8 @@ if %ERRORLEVEL% neq 0 ( echo "ERROR: 7zip not installed" & exit /B 1 ) :: set URL to github repo with test files -:: set "NRTESTS_URL=https://github.com/michaeltryby/%PROJECT%-nrtests" -set "NRTESTS_URL=https://github.com/OpenWaterAnalytics/swmm-example-networks" +set "NRTESTS_URL=https://github.com/michaeltryby/%PROJECT%-nrtests" + :: if release tag isn't provided latest tag will be retrieved if [%1] == [] (set "RELEASE_TAG=" @@ -98,8 +98,8 @@ curl -fsSL -o benchmark.zip %BENCHFILES_URL% :: set up symlinks for tests directory -:: mklink /D .\tests .\%PROJECT%-nrtests-%RELEASE_TAG:~1%\public > nul -mklink /D .\tests .\swmm-example-networks-%RELEASE_TAG:~1%\swmm-tests > nul +mklink /D .\tests .\%PROJECT%-nrtests-%RELEASE_TAG:~1%\public > nul + endlocal diff --git a/tools/requirements.txt b/tools/requirements.txt new file mode 100644 index 000000000..e3b961478 --- /dev/null +++ b/tools/requirements.txt @@ -0,0 +1,22 @@ +# +# requirements.txt +# +# Date Created: 10/17/2019 +# Date Modified: +# +# Author: Michael E. Tryby +# US EPA ORD/CESER +# +# Useful for configuring a python environment to run nrtests on swmm. +# +# usage: +# pip install -r tools/requirements.txt +# + +nrtests + +-f https://github.com/michaeltryby/swmm-python/releases/download/v0.3.0-dev2/swmm.output-0.4.0.dev2-cp36-cp36m-win_amd64.whl +swmm.output + + +#nrtest-swmm From 3bfe297a54d403b71dcfde01d9b4fdd42bd3845f Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 18 Oct 2019 15:39:44 -0400 Subject: [PATCH 015/266] Work in progress Migrating swmm_output library from OWA/swmm repo develop branch Deprecating older version found in tools/ --- CMakeLists.txt | 1 + outfile/CMakeLists.txt | 59 ++ outfile/errormanager.c | 74 ++ outfile/errormanager.h | 27 + outfile/include/swmm_output.h | 80 ++ outfile/include/swmm_output_enums.h | 83 ++ outfile/messages.h | 31 + outfile/swmm_output.c | 1150 +++++++++++++++++++++++++++ tools/.gitignore | 23 - tools/swmm-output/src/outputapi.c | 1015 ----------------------- tools/swmm-output/src/outputapi.h | 190 ----- tools/swmm-output/test/main.c | 58 -- 12 files changed, 1505 insertions(+), 1286 deletions(-) create mode 100644 outfile/CMakeLists.txt create mode 100644 outfile/errormanager.c create mode 100644 outfile/errormanager.h create mode 100644 outfile/include/swmm_output.h create mode 100644 outfile/include/swmm_output_enums.h create mode 100644 outfile/messages.h create mode 100644 outfile/swmm_output.c delete mode 100644 tools/.gitignore delete mode 100644 tools/swmm-output/src/outputapi.c delete mode 100644 tools/swmm-output/src/outputapi.h delete mode 100644 tools/swmm-output/test/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 39dce1505..d0405f3cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ set(CONFIG_DIST "cmake") # Add project subdirectories +add_subdirectory(outfile) add_subdirectory(src/solver) add_subdirectory(src/run) diff --git a/outfile/CMakeLists.txt b/outfile/CMakeLists.txt new file mode 100644 index 000000000..9637976a1 --- /dev/null +++ b/outfile/CMakeLists.txt @@ -0,0 +1,59 @@ +# +# CMakeLists.txt - CMake configuration file for swmm/outfile +# + + +# configure file groups +set(SWMM_OUT_PUBLIC_HEADERS + include/swmm_output.h + include/swmm_output_enums.h + include/swmm_output_export.h + ) + + +# the binary output file API +add_library(swmm-output + SHARED + swmm_output.c + errormanager.c + ) + +target_include_directories(swmm-output + PUBLIC + $ + $ + PRIVATE + ${CMAKE_SOURCE_DIR}/ + ) + +include(GenerateExportHeader) +generate_export_header(swmm-output + BASE_NAME swmm_output + EXPORT_MACRO_NAME EXPORT_OUT_API + EXPORT_FILE_NAME swmm_output_export.h + STATIC_DEFINE SHARED_EXPORTS_BUILT_AS_STATIC + ) + +file(COPY ${CMAKE_CURRENT_BINARY_DIR}/swmm_output_export.h + DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/include + ) + + +install(TARGETS swmm-output EXPORT swmm-outputTargets + RUNTIME DESTINATION "${TOOL_DIST}" + LIBRARY DESTINATION "${TOOL_DIST}" + ARCHIVE DESTINATION "${LIBRARY_DIST}" + FRAMEWORK DESTINATION "${TOOL_DIST}" + ) + +install(FILES ${SWMM_OUT_PUBLIC_HEADERS} DESTINATION "${INCLUDE_DIST}") + + +# copy epanet-output to build tree for testing +if(BUILD_TESTS) + add_custom_command(TARGET swmm-output POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + $ + ${CMAKE_BINARY_DIR}/bin/$/$ + ) +endif() diff --git a/outfile/errormanager.c b/outfile/errormanager.c new file mode 100644 index 000000000..6d70e0094 --- /dev/null +++ b/outfile/errormanager.c @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// +// errormanager.c +// +// Purpose: Provides a simple interface for managing runtime error messages. +// +// Date 08/25/2017 +// +// Author: Michael E. Tryby +// US EPA - ORD/NRMRL +//----------------------------------------------------------------------------- +#include +#include +#include "errormanager.h" + +error_handle_t* new_errormanager(void (*p_error_message)(int, char*, int)) +// +// Purpose: Constructs a new error handle. +// +{ + error_handle_t* error_handle; + error_handle = (error_handle_t*)calloc(1, sizeof(error_handle_t)); + + error_handle->p_msg_lookup = p_error_message; + + return error_handle; +} + +void dst_errormanager(error_handle_t* error_handle) +// +// Purpose: Destroys the error handle. +// +{ + free(error_handle); +} + +int set_error(error_handle_t* error_handle, int errorcode) +// +// Purpose: Sets an error code in the handle. +// +{ + // If the error code is 0 no action is taken and 0 is returned. + // This is a feature not a bug. + if (errorcode) + error_handle->error_status = errorcode; + + return errorcode; +} + +char* check_error(error_handle_t* error_handle) +// +// Purpose: Returns the error message or NULL. +// +// Note: Caller must free memory allocated by check_error +// +{ + char* temp = NULL; + + if (error_handle->error_status != 0) { + temp = (char*) calloc(ERR_MAXMSG, sizeof(char)); + + if (temp) + error_handle->p_msg_lookup(error_handle->error_status, temp, ERR_MAXMSG); + } + return temp; +} + +void clear_error(error_handle_t* error_handle) +// +// Purpose: Clears the error from the handle. +// +{ + error_handle->error_status = 0; +} diff --git a/outfile/errormanager.h b/outfile/errormanager.h new file mode 100644 index 000000000..cfa97ba70 --- /dev/null +++ b/outfile/errormanager.h @@ -0,0 +1,27 @@ +/* + * errormanager.h + * + * Created on: Aug 25, 2017 + * + * Author: Michael E. Tryby + * US EPA - ORD/NRMRL + */ + +#ifndef ERRORMANAGER_H_ +#define ERRORMANAGER_H_ + +#define ERR_MAXMSG 256 + +typedef struct error_s { + int error_status; + void (*p_msg_lookup)(int, char*, int); +} error_handle_t; + +error_handle_t* new_errormanager(void (*p_error_message)(int, char*, int)); +void dst_errormanager(error_handle_t* error_handle); + +int set_error(error_handle_t* error_handle, int errorcode); +char* check_error(error_handle_t* error_handle); +void clear_error(error_handle_t* error_handle); + +#endif /* ERRORMANAGER_H_ */ diff --git a/outfile/include/swmm_output.h b/outfile/include/swmm_output.h new file mode 100644 index 000000000..6b70f31ed --- /dev/null +++ b/outfile/include/swmm_output.h @@ -0,0 +1,80 @@ +/* + * swmm_output.c - SWMM Output API + * + * Author: Colleen Barr + * US EPA - ORD/NHEERL + * + * Modified by: Michael E. Tryby, + * Bryant McDonnell + * + */ + +#ifndef SWMM_OUTPUT_H_ +#define SWMM_OUTPUT_H_ + +#define MAXFILENAME 259 // Max characters in file path +#define MAXELENAME 31 // Max characters in element name + +// This is an opaque pointer to struct. Do not access variables. +typedef void* SMO_Handle; + + +#include "swmm_output_enums.h" +#include "swmm_output_export.h" + + +#ifdef __cplusplus + extern "C" { +#endif + + +int DLLEXPORT SMO_init(SMO_Handle* p_handle); +int DLLEXPORT SMO_close(SMO_Handle* p_handle); +int DLLEXPORT SMO_open(SMO_Handle p_handle, const char* path); +int DLLEXPORT SMO_getVersion(SMO_Handle p_handle, int* version); +int DLLEXPORT SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, int* length); + +int DLLEXPORT SMO_getFlowUnits(SMO_Handle p_handle, int* unitFlag); +int DLLEXPORT SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, int* length); +int DLLEXPORT SMO_getStartDate(SMO_Handle p_handle, double* date); +int DLLEXPORT SMO_getTimes(SMO_Handle p_handle, SMO_time code, int* time); +int DLLEXPORT SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, + int elementIndex, char** elementName, int* size); + +int DLLEXPORT SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, + SMO_subcatchAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim); +int DLLEXPORT SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, SMO_nodeAttribute attr, + int startPeriod, int endPeriod, float** outValueSeries, int* dim); +int DLLEXPORT SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, SMO_linkAttribute attr, + int startPeriod, int endPeriod, float** outValueSeries, int* dim); +int DLLEXPORT SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, + int startPeriod, int endPeriod, float** outValueSeries, int* dim); + +int DLLEXPORT SMO_getSubcatchAttribute(SMO_Handle p_handle, int timeIndex, + SMO_subcatchAttribute attr, float** outValueArray, int* length); +int DLLEXPORT SMO_getNodeAttribute(SMO_Handle p_handle, int timeIndex, + SMO_nodeAttribute attr, float** outValueArray, int* length); +int DLLEXPORT SMO_getLinkAttribute(SMO_Handle p_handle, int timeIndex, + SMO_linkAttribute attr, float** outValueArray, int* length); +int DLLEXPORT SMO_getSystemAttribute(SMO_Handle p_handle, int timeIndex, + SMO_systemAttribute attr, float** outValueArray, int* length); + +int DLLEXPORT SMO_getSubcatchResult(SMO_Handle p_handle, int timeIndex, + int subcatchIndex, float** outValueArray, int* arrayLength); +int DLLEXPORT SMO_getNodeResult(SMO_Handle p_handle, int timeIndex, + int nodeIndex, float** outValueArray, int* arrayLength); +int DLLEXPORT SMO_getLinkResult(SMO_Handle p_handle, int timeIndex, + int linkIndex, float** outValueArray, int* arrayLength); +int DLLEXPORT SMO_getSystemResult(SMO_Handle p_handle, int timeIndex, + int dummyIndex, float** outValueArray, int* arrayLength); + +void DLLEXPORT SMO_free(void** array); +void DLLEXPORT SMO_clearError(SMO_Handle p_handle_in); +int DLLEXPORT SMO_checkError(SMO_Handle p_handle_in, char** msg_buffer); + + +#ifdef __cplusplus + } +#endif + +#endif /* SWMM_OUTPUT_H_ */ diff --git a/outfile/include/swmm_output_enums.h b/outfile/include/swmm_output_enums.h new file mode 100644 index 000000000..bb9004265 --- /dev/null +++ b/outfile/include/swmm_output_enums.h @@ -0,0 +1,83 @@ +/* + * swmm_output_enums.h + * + * Created on: October 18, 2019 + * + * Author: Michael E. Tryby + * US EPA - ORD/CESER + */ + + + #ifndef SWMM_OUTPUT_ENUMS_H_ + #define SWMM_OUTPUT_ENUMS_H_ + + +typedef enum { + SMO_flow_rate, + SMO_concentration +} SMO_unit; + +typedef enum { + SMO_subcatch, + SMO_node, + SMO_link, + SMO_sys + SMO_pollut +} SMO_elementType; + +typedef enum { + SMO_reportStep, + SMO_numPeriods +} SMO_time; + +typedef enum { + SMO_rainfall_subcatch, // (in/hr or mm/hr), + SMO_snow_depth_subcatch, // (in or mm), + SMO_evap_loss, // (in/hr or mm/hr), + SMO_infil_loss, // (in/hr or mm/hr), + SMO_runoff_rate, // (flow units), + SMO_gwoutflow_rate, // (flow units), + SMO_gwtable_elev, // (ft or m), + SMO_soil_moisture, // unsaturated zone moisture content (-), + SMO_pollutant_conc_subcatch // first pollutant +} SMO_subcatchAttribute; + +typedef enum { + SMO_invert_depth, // (ft or m), + SMO_hydraulic_head, // (ft or m), + SMO_stored_ponded_volume, // (ft3 or m3), + SMO_lateral_inflow, // (flow units), + SMO_total_inflow, // lateral + upstream (flow units), + SMO_flooding_losses, // (flow units), + SMO_pollutant_conc_node // first pollutant, +} SMO_nodeAttribute; + +typedef enum { + SMO_flow_rate_link, // (flow units), + SMO_flow_depth, // (ft or m), + SMO_flow_velocity, // (ft/s or m/s), + SMO_flow_volume, // (ft3 or m3), + SMO_capacity, // (fraction of conduit filled), + SMO_pollutant_conc_link // first pollutant, +} SMO_linkAttribute; + +typedef enum { + SMO_air_temp, // (deg. F or deg. C), + SMO_rainfall_system, // (in/hr or mm/hr), + SMO_snow_depth_system, // (in or mm), + SMO_evap_infil_loss, // (in/hr or mm/hr), + SMO_runoff_flow, // (flow units), + SMO_dry_weather_inflow, // (flow units), + SMO_groundwater_inflow, // (flow units), + SMO_RDII_inflow, // (flow units), + SMO_direct_inflow, // user defined (flow units), + SMO_total_lateral_inflow, // (sum of variables 4 to 8) //(flow units), + SMO_flood_losses, // (flow units), + SMO_outfall_flows, // (flow units), + SMO_volume_stored, // (ft3 or m3), + SMO_evap_rate // (in/day or mm/day), + //p_evap_rate // (in/day or mm/day) +} SMO_systemAttribute; + + +#endif /* SWMM_OUTPUT_ENUMS_H_ */ diff --git a/outfile/messages.h b/outfile/messages.h new file mode 100644 index 000000000..9fa691df3 --- /dev/null +++ b/outfile/messages.h @@ -0,0 +1,31 @@ +/* + * messages.h - SWMM + * + * Created on: Oct 20, 2017 + * + * Author: Michael E. Tryby + * US EPA - ORD/NRMRL + */ + +#ifndef SRC_MESSAGES_H_ +#define SRC_MESSAGES_H_ + +#define MAXMSG 56 + +/*------------------- Error Messages --------------------*/ +#define WARN10 "Warning: model run issued warnings" + +#define ERR411 "Error 411: memory allocation failure" + +#define ERR421 "Input Error 421: invalid parameter code" +#define ERR422 "Input Error 422: reporting period index out of range" +#define ERR423 "Input Error 423: element index out of range" +#define ERR424 "Input Error 424: no memory allocated for results" + +#define ERR434 "File Error 434: unable to open binary output file" +#define ERR435 "File Error 435: invalid file - not created by SWMM" +#define ERR436 "File Error 436: invalid file - contains no results" + +#define ERR440 "ERROR 440: an unspecified error has occurred" + +#endif /* SRC_MESSAGES_H_ */ diff --git a/outfile/swmm_output.c b/outfile/swmm_output.c new file mode 100644 index 000000000..dc4a22633 --- /dev/null +++ b/outfile/swmm_output.c @@ -0,0 +1,1150 @@ +/* + * swmm_output.c - SWMM Output API + * + * Author: Colleen Barr + * US EPA - ORD/NHEERL + * + * Modified by: Michael E. Tryby, + * Bryant McDonnell + * + */ + + +#include +#include +#include + +#include "errormanager.h" +#include "messages.h" + +#include "swmm_output_enums.h" +#include "swmm_output.h" + + + + +// NOTE: These depend on machine data model and may change when porting +// F_OFF Must be a 8 byte / 64 bit integer for large file support +#ifdef _WIN32 // Windows (32-bit and 64-bit) +#define F_OFF __int64 +#else // Other platforms +#define F_OFF off_t +#endif +#define INT4 int // Must be a 4 byte / 32 bit integer type +#define REAL4 float // Must be a 4 byte / 32 bit real type + +#define RECORDSIZE 4 // Memory alignment 4 byte word size for both int and real +#define DATESIZE 8 // Dates are stored as 8 byte word size + +#define NELEMENTTYPES 4 // Number of element types + +#define MEMCHECK(x) (((x) == NULL) ? 414 : 0 ) + +struct IDentry { + char* IDname; + int length; +}; +typedef struct IDentry idEntry; + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- + +typedef struct { + char name[MAXFILENAME + 1]; // file path/name + FILE* file; // FILE structure pointer + + struct IDentry *elementNames; // array of pointers to element names + + long Nperiods; // number of reporting periods + int FlowUnits; // flow units code + + int Nsubcatch; // number of subcatchments + int Nnodes; // number of drainage system nodes + int Nlinks; // number of drainage system links + int Npolluts; // number of pollutants tracked + + int SubcatchVars; // number of subcatch reporting variables + int NodeVars; // number of node reporting variables + int LinkVars; // number of link reporting variables + int SysVars; // number of system reporting variables + + double StartDate; // start date of simulation + int ReportStep; // reporting time step (seconds) + + F_OFF IDPos; // file position where object ID names start + F_OFF ObjPropPos; // file position where object properties start + F_OFF ResultsPos; // file position where results start + F_OFF BytesPerPeriod; // bytes used for results in each period + + error_handle_t* error_handle; +} data_t; + + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static void errorLookup(int errcode, char* errmsg, int length); +static int validateFile(data_t* p_data); +static void initElementNames(data_t* p_data); +static void clearElementNames(data_t* p_data); + +static double getTimeValue(data_t* p_data, int timeIndex); +static float getSubcatchValue(data_t* p_data, int timeIndex, int subcatchIndex, SMO_subcatchAttribute attr); +static float getNodeValue(data_t* p_data, int timeIndex, int nodeIndex, SMO_nodeAttribute attr); +static float getLinkValue(data_t* p_data, int timeIndex, int linkIndex, SMO_linkAttribute attr); +static float getSystemValue(data_t* p_data, int timeIndex, SMO_systemAttribute attr); + +static int _fopen(FILE **f, const char *name, const char *mode); +static int _fseek(FILE* stream, F_OFF offset, int whence); +static F_OFF _ftell(FILE* stream); + +static float* newFloatArray(int n); +static int* newIntArray(int n); +static char* newCharArray(int n); + + +int DLLEXPORT SMO_init(SMO_Handle* p_handle) +// Purpose: Initialized pointer for the opaque SMO_Handle. +// +// Returns: Error code 0 on success, -1 on failure +// +// Note: The existence of this function has been carefully considered. +// Don't change it. +// +{ + int errorcode = 0; + data_t* priv_data; + + // Allocate memory for private data + priv_data = (data_t*)calloc(1, sizeof(data_t)); + + if (priv_data != NULL){ + priv_data->error_handle = new_errormanager(&errorLookup); + *p_handle = priv_data; + } + else + errorcode = -1; + + // TODO: Need to handle errors during initialization better. + return errorcode; +} + +int DLLEXPORT SMO_close(SMO_Handle* p_handle) +// +// Purpose: Clean up after and close Output API +// +{ + data_t* p_data; + int errorcode = 0; + + p_data = (data_t*)*p_handle; + + if (p_data == NULL) + errorcode = -1; + + else + { + clearElementNames(p_data); + + dst_errormanager(p_data->error_handle); + + if (p_data->file != NULL) + fclose(p_data->file); + + free(p_data); + + *p_handle = NULL; + } + + return errorcode; +} + +int DLLEXPORT SMO_open(SMO_Handle p_handle, const char* path) +// +// Purpose: Open the output binary file and read the header. +// +{ + int err, errorcode = 0; + F_OFF offset; + + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) return -1; + else + { + strncpy(p_data->name, path, MAXFILENAME); + + // Attempt to open binary output file for reading only + if ((_fopen(&(p_data->file), path, "rb")) != 0) errorcode = 434; + + // --- validate the output file + else if ((err = validateFile(p_data)) != 0) errorcode = err; + + // If a warning is encountered read file header + if (errorcode < 400 ) { + // --- otherwise read additional parameters from start of file + fseek(p_data->file, 3*RECORDSIZE, SEEK_SET); + fread(&(p_data->Nsubcatch), RECORDSIZE, 1, p_data->file); + fread(&(p_data->Nnodes), RECORDSIZE, 1, p_data->file); + fread(&(p_data->Nlinks), RECORDSIZE, 1, p_data->file); + fread(&(p_data->Npolluts), RECORDSIZE, 1, p_data->file); + + // Compute offset for saved subcatch/node/link input values + offset = (p_data->Nsubcatch + 2) * RECORDSIZE // Subcatchment area + + (3 * p_data->Nnodes + 4) * RECORDSIZE // Node type, invert & max depth + + (5 * p_data->Nlinks + 6) * RECORDSIZE; // Link type, z1, z2, max depth & length + offset += p_data->ObjPropPos; + + // Read number & codes of computed variables + _fseek(p_data->file, offset, SEEK_SET); + fread(&(p_data->SubcatchVars), RECORDSIZE, 1, p_data->file); // # Subcatch variables + + _fseek(p_data->file, p_data->SubcatchVars*RECORDSIZE, SEEK_CUR); + fread(&(p_data->NodeVars), RECORDSIZE, 1, p_data->file); // # Node variables + + _fseek(p_data->file, p_data->NodeVars*RECORDSIZE, SEEK_CUR); + fread(&(p_data->LinkVars), RECORDSIZE, 1, p_data->file); // # Link variables + + _fseek(p_data->file, p_data->LinkVars*RECORDSIZE, SEEK_CUR); + fread(&(p_data->SysVars), RECORDSIZE, 1, p_data->file); // # System variables + + // --- read data just before start of output results + offset = p_data->ResultsPos - 3 * RECORDSIZE; + _fseek(p_data->file, offset, SEEK_SET); + fread(&(p_data->StartDate), DATESIZE, 1, p_data->file); + fread(&(p_data->ReportStep), RECORDSIZE, 1, p_data->file); + + // --- compute number of bytes of results values used per time period + p_data->BytesPerPeriod = DATESIZE + + (p_data->Nsubcatch*p_data->SubcatchVars + + p_data->Nnodes*p_data->NodeVars + + p_data->Nlinks*p_data->LinkVars + + p_data->SysVars)*RECORDSIZE; + } + } + // If error close the binary file + if (errorcode > 400) { + set_error(p_data->error_handle, errorcode); + SMO_close(&p_handle); + } + + return errorcode; +} + +int DLLEXPORT SMO_getVersion(SMO_Handle p_handle, int* version) +// +// Input: p_handle = pointer to SMO_Handle struct +// Output: version SWMM version +// Returns: error code +// +// Purpose: Returns SWMM version that wrote binary file +// +{ + int errorcode = 0; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) return -1; + else + { + fseek(p_data->file, 1*RECORDSIZE, SEEK_SET); + if (fread(version, RECORDSIZE, 1, p_data->file) != 1) + errorcode = 436; + } + + return set_error(p_data->error_handle, errorcode); +} + +int DLLEXPORT SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, int* length) +// +// Purpose: Returns project size. +// +{ + int errorcode = 0; + int* temp = newIntArray(NELEMENTTYPES); + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else + { + temp[0] = p_data->Nsubcatch; + temp[1] = p_data->Nnodes; + temp[2] = p_data->Nlinks; + temp[3] = p_data->Npolluts; + + *elementCount = temp; + *length = NELEMENTTYPES; + } + + return set_error(p_data->error_handle, errorcode); +} + +int DLLEXPORT SMO_getFlowUnits(SMO_Handle p_handle, int* unitFlag) +// +// Purpose: Returns unit flag for flow. +// +// Returns: +// 0: CFS (cubic feet per second) +// 1: GPM (gallons per minute) +// 2: MGD (million gallons per day) +// 3: CMS (cubic meters per second) +// 4: LPS (liters per second) +// 5: MLD (million liters per day) +// +{ + int errorcode = 0; + data_t* p_data; + + *unitFlag = -1; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) return -1; + else + { + fseek(p_data->file, 2*RECORDSIZE, SEEK_SET); + fread(unitFlag, RECORDSIZE, 1, p_data->file); + } + + return set_error(p_data->error_handle, errorcode); +} + +int DLLEXPORT SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, int* length) +// +// Purpose: +// Return integer flag representing the units that the given pollutant is +// measured in. Concentration units are located after the pollutant ID +// names and before the object properties start, and are stored for each +// pollutant. They're stored as 4-byte integers with the following codes: +// 0: mg/L +// 1: ug/L +// 2: count/L +// +// Args: +// pollutantIndex: valid values are 0 to Npolluts-1 +{ + int errorcode = 0; + int* temp; + F_OFF offset; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else if (MEMCHECK(temp = newIntArray(p_data->Npolluts))) errorcode = 414; + else + { + offset = p_data->ObjPropPos - (p_data->Npolluts * RECORDSIZE); + _fseek(p_data->file, offset, SEEK_SET); + fread(temp, RECORDSIZE, p_data->Npolluts, p_data->file); + + *unitFlag = temp; + *length = p_data->Npolluts; + } + + return set_error(p_data->error_handle, errorcode); +} + +int DLLEXPORT SMO_getStartDate(SMO_Handle p_handle, double* date) +// +// Purpose: Returns start date. +// +{ + int errorcode = 0; + data_t* p_data; + + *date = -1.0; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else + *date = p_data->StartDate; + + return set_error(p_data->error_handle, errorcode); +} + + +int DLLEXPORT SMO_getTimes(SMO_Handle p_handle, SMO_time code, int* time) +// +// Purpose: Returns step size and number of periods. +// +{ + int errorcode = 0; + data_t* p_data; + + *time = -1; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else + { + switch (code) + { + case SMO_reportStep: *time = p_data->ReportStep; + break; + case SMO_numPeriods: *time = p_data->Nperiods; + break; + default: errorcode = 421; + } + } + + return errorcode; +} + +int DLLEXPORT SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, + int index, char** name, int* length) +// +// Purpose: Given an element index returns the element name. +// +{ + int idx = -1, + errorcode = 0; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = 410; + else if (p_data->file == NULL) errorcode = 411; + else + { + // Lazy initialize the name array on demand + if (p_data->elementNames == NULL) initElementNames(p_data); + + switch (type) + { + case SMO_subcatch: + if (index < 0 || index >= p_data->Nsubcatch) + errorcode = 423; + else + idx = index; + break; + + case SMO_node: + if (index < 0 || index >= p_data->Nnodes) + errorcode = 423; + else + idx = p_data->Nsubcatch + index; + break; + + case SMO_link: + if (index < 0 || index >= p_data->Nlinks) + errorcode = 423; + else + idx = p_data->Nsubcatch + p_data->Nnodes + index; + break; + + case SMO_sys: + if (index < 0 || index >= p_data->Npolluts) + errorcode = 423; + else + idx = p_data->Nsubcatch + p_data->Nnodes + p_data->Nlinks + index; + break; + + default: + errorcode = 421; + } + + if (!errorcode) { + *length = p_data->elementNames[idx].length; + *name = newCharArray(*length + 1); + // Writes IDname and an additional null character to name + strncpy(*name, p_data->elementNames[idx].IDname, (*length + 1)*sizeof(char)); + } + } + + return errorcode; +} + + +int DLLEXPORT SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, + SMO_subcatchAttribute attr, int startPeriod, int endPeriod, + float** outValueSeries, int* dim) +// +// Purpose: Get time series results for particular attribute. Specify series +// start and length using timeIndex and length respectively. +// +{ + int k, length, errorcode = 0; + float* temp; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else if (subcatchIndex < 0 || subcatchIndex > p_data->Nsubcatch) errorcode = 420; + else if (startPeriod < 0 || startPeriod >= p_data->Nperiods || + endPeriod <= startPeriod) errorcode = 422; + // Check memory for outValues + else if MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) errorcode = 411; + else + { + // loop over and build time series + for (k = 0; k < length; k++) + temp[k] = getSubcatchValue(p_data, startPeriod + k, + subcatchIndex, attr); + + *outValueSeries = temp; + *dim = length; + } + + return set_error(p_data->error_handle, errorcode); +} + + +int DLLEXPORT SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, SMO_nodeAttribute attr, + int startPeriod, int endPeriod, float** outValueSeries, int* dim) +// +// Purpose: Get time series results for particular attribute. Specify series +// start and length using timeIndex and length respectively. +// +{ + int k, length, errorcode = 0; + float* temp; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else if (nodeIndex < 0 || nodeIndex > p_data->Nnodes) errorcode = 420; + else if (startPeriod < 0 || startPeriod >= p_data->Nperiods || + endPeriod <= startPeriod) errorcode = 422; + // Check memory for outValues + else if MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) errorcode = 411; + else + { + // loop over and build time series + for (k = 0; k < length; k++) + temp[k] = getNodeValue(p_data, startPeriod + k, + nodeIndex, attr); + + *outValueSeries = temp; + *dim = length; + } + + return set_error(p_data->error_handle, errorcode); +} + + +int DLLEXPORT SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, SMO_linkAttribute attr, + int startPeriod, int endPeriod, float** outValueSeries, int* dim) +// +// Purpose: Get time series results for particular attribute. Specify series +// start and length using timeIndex and length respectively. +// +{ + int k, length, errorcode = 0; + float* temp; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else if (linkIndex < 0 || linkIndex > p_data->Nlinks) errorcode = 420; + else if (startPeriod < 0 || startPeriod >= p_data->Nperiods || + endPeriod <= startPeriod) errorcode = 422; + // Check memory for outValues + else if MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) errorcode = 411; + else + { + // loop over and build time series + for (k = 0; k < length; k++) + temp[k] = getLinkValue(p_data, startPeriod + k, linkIndex, attr); + + *outValueSeries = temp; + *dim = length; + } + + return set_error(p_data->error_handle, errorcode); +} + + + +int DLLEXPORT SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, + int startPeriod, int endPeriod, float** outValueSeries, int* dim) +// +// Purpose: Get time series results for particular attribute. Specify series +// start and length using timeIndex and length respectively. +// +{ + int k, length, errorcode = 0; + float* temp; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else if (startPeriod < 0 || startPeriod >= p_data->Nperiods || + endPeriod <= startPeriod) errorcode = 422; + // Check memory for outValues + else if MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) errorcode = 411; + else + { + // loop over and build time series + for (k = 0; k < length; k++) + temp[k] = getSystemValue(p_data, startPeriod + k, attr); + + *outValueSeries = temp; + *dim = length; + } + + return set_error(p_data->error_handle, errorcode); +} + +int DLLEXPORT SMO_getSubcatchAttribute(SMO_Handle p_handle, int periodIndex, + SMO_subcatchAttribute attr, float** outValueArray, int* length) +// +// Purpose: For all subcatchments at given time, get a particular attribute. +// +{ + int k, errorcode = 0; + float* temp; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; + // Check memory for outValues + else if MEMCHECK(temp = newFloatArray(p_data->Nsubcatch)) errorcode = 411; + else + { + // loop over and pull result + for (k = 0; k < p_data->Nsubcatch; k++) + temp[k] = getSubcatchValue(p_data, periodIndex, k, attr); + + *outValueArray = temp; + *length = p_data->Nsubcatch; + } + + return set_error(p_data->error_handle, errorcode); +} + + + +int DLLEXPORT SMO_getNodeAttribute(SMO_Handle p_handle, int periodIndex, + SMO_nodeAttribute attr, float** outValueArray, int* length) +// +// Purpose: For all nodes at given time, get a particular attribute. +// +{ + int k, errorcode = 0; + float* temp; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; + // Check memory for outValues + else if MEMCHECK(temp = newFloatArray(p_data->Nnodes)) errorcode = 411; + else + { + // loop over and pull result + for (k = 0; k < p_data->Nnodes; k++) + temp[k] = getNodeValue(p_data, periodIndex, k, attr); + + *outValueArray = temp; + *length = p_data->Nnodes; + } + + return set_error(p_data->error_handle, errorcode); +} + +int DLLEXPORT SMO_getLinkAttribute(SMO_Handle p_handle, int periodIndex, + SMO_linkAttribute attr, float** outValueArray, int* length) +// +// Purpose: For all links at given time, get a particular attribute. +// +{ + int k, errorcode = 0; + float* temp; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; + // Check memory for outValues + else if MEMCHECK(temp = newFloatArray(p_data->Nlinks)) errorcode = 411; + else + { + // loop over and pull result + for (k = 0; k < p_data->Nlinks; k++) + temp[k] = getLinkValue(p_data, periodIndex, k, attr); + + *outValueArray = temp; + *length = p_data->Nlinks; + } + + return set_error(p_data->error_handle, errorcode); +} + + +int DLLEXPORT SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, + SMO_systemAttribute attr, float** outValueArray, int* length) +// +// Purpose: For the system at given time, get a particular attribute. +// +{ + int errorcode = 0; + float temp; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; + else + { + // don't need to loop since there's only one system + temp = getSystemValue(p_data, periodIndex, attr); + + *outValueArray = &temp; + *length = 1; + } + + return set_error(p_data->error_handle, errorcode); +} + +int DLLEXPORT SMO_getSubcatchResult(SMO_Handle p_handle, int periodIndex, + int subcatchIndex, float** outValueArray, int* arrayLength) +// +// Purpose: For a subcatchment at given time, get all attributes. +// +{ + int errorcode = 0; + float* temp; + F_OFF offset; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; + else if (subcatchIndex < 0 || subcatchIndex > p_data->Nsubcatch) errorcode = 423; + else if MEMCHECK(temp = newFloatArray(p_data->SubcatchVars)) errorcode = 411; + else + { + // --- compute offset into output file + offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + 2 * RECORDSIZE; + // add offset for subcatchment + offset += (subcatchIndex*p_data->SubcatchVars)*RECORDSIZE; + + _fseek(p_data->file, offset, SEEK_SET); + fread(temp, RECORDSIZE, p_data->SubcatchVars, p_data->file); + + *outValueArray = temp; + *arrayLength = p_data->SubcatchVars; + } + + return set_error(p_data->error_handle, errorcode); +} + + +int DLLEXPORT SMO_getNodeResult(SMO_Handle p_handle, int periodIndex, + int nodeIndex, float** outValueArray, int* arrayLength) +// +// Purpose: For a node at given time, get all attributes. +// +{ + int errorcode = 0; + float* temp; + F_OFF offset; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; + else if (nodeIndex < 0 || nodeIndex > p_data->Nnodes) errorcode = 423; + else if MEMCHECK(temp = newFloatArray(p_data->NodeVars)) errorcode = 411; + else + { + // calculate byte offset to start time for series + offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + 2 * RECORDSIZE; + // add offset for subcatchment and node + offset += (p_data->Nsubcatch*p_data->SubcatchVars + nodeIndex*p_data->NodeVars)*RECORDSIZE; + + _fseek(p_data->file, offset, SEEK_SET); + fread(temp, RECORDSIZE, p_data->NodeVars, p_data->file); + + *outValueArray = temp; + *arrayLength = p_data->NodeVars; + } + + return set_error(p_data->error_handle, errorcode); +} + +int DLLEXPORT SMO_getLinkResult(SMO_Handle p_handle, int periodIndex, + int linkIndex, float** outValueArray, int* arrayLength) +// +// Purpose: For a link at given time, get all attributes. +// +{ + int errorcode = 0; + float* temp; + F_OFF offset; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; + else if (linkIndex < 0 || linkIndex > p_data->Nlinks) errorcode = 423; + else if MEMCHECK(temp = newFloatArray(p_data->LinkVars)) errorcode = 411; + else + { + // calculate byte offset to start time for series + offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + 2 * RECORDSIZE; + // add offset for subcatchment and node and link + offset += (p_data->Nsubcatch*p_data->SubcatchVars + + p_data->Nnodes*p_data->NodeVars + linkIndex*p_data->LinkVars)*RECORDSIZE; + + _fseek(p_data->file, offset, SEEK_SET); + fread(temp, RECORDSIZE, p_data->LinkVars, p_data->file); + + *outValueArray = temp; + *arrayLength = p_data->LinkVars; + } + + return set_error(p_data->error_handle, errorcode); +} + +int DLLEXPORT SMO_getSystemResult(SMO_Handle p_handle, int periodIndex, + int dummyIndex, float** outValueArray, int* arrayLength) +// +// Purpose: For the system at given time, get all attributes. +// +{ + int errorcode = 0; + float* temp; + F_OFF offset; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; + else if MEMCHECK(temp = newFloatArray(p_data->SysVars)) errorcode = 411; + { + // calculate byte offset to start time for series + offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + 2 * RECORDSIZE; + // add offset for subcatchment and node and link (system starts after the last link) + offset += (p_data->Nsubcatch*p_data->SubcatchVars + p_data->Nnodes*p_data->NodeVars + + p_data->Nlinks*p_data->LinkVars)*RECORDSIZE; + + _fseek(p_data->file, offset, SEEK_SET); + fread(temp, RECORDSIZE, p_data->SysVars, p_data->file); + + *outValueArray = temp; + *arrayLength = p_data->SysVars; + } + + return set_error(p_data->error_handle, errorcode); +} + +void DLLEXPORT SMO_free(void** array) +// +// Purpose: Frees memory allocated by API calls +// +{ + if (array != NULL) { + free(*array); + *array = NULL; + } +} + +void DLLEXPORT SMO_clearError(SMO_Handle p_handle) +{ + data_t* p_data; + + p_data = (data_t*)p_handle; + clear_error(p_data->error_handle); +} + +int DLLEXPORT SMO_checkError(SMO_Handle p_handle, char** msg_buffer) +{ + int errorcode = 0; + char *temp = NULL; + data_t* p_data; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) return -1; + else + { + errorcode = p_data->error_handle->error_status; + if (errorcode) + temp = check_error(p_data->error_handle); + + *msg_buffer = temp; + } + + return errorcode; +} + +void errorLookup(int errcode, char* dest_msg, int dest_len) +// +// Purpose: takes error code returns error message +// +{ + const char* msg; + + switch (errcode) + { + case 10: msg = WARN10; + break; + case 411: msg = ERR411; + break; + case 421: msg = ERR421; + break; + case 422: msg = ERR422; + break; + case 423: msg = ERR423; + break; + case 424: msg = ERR424; + break; + case 434: msg = ERR434; + break; + case 435: msg = ERR435; + break; + case 436: msg = ERR436; + break; + default: msg = ERR440; + } + + strncpy(dest_msg, msg, MAXMSG); +} + + +// Local functions: +int validateFile(data_t* p_data) +{ + INT4 magic1, magic2, errcode; + int errorcode = 0; + + // --- fast forward to end and read epilogue + _fseek(p_data->file, -6 * RECORDSIZE, SEEK_END); + fread(&(p_data->IDPos), RECORDSIZE, 1, p_data->file); + fread(&(p_data->ObjPropPos), RECORDSIZE, 1, p_data->file); + fread(&(p_data->ResultsPos), RECORDSIZE, 1, p_data->file); + fread(&(p_data->Nperiods), RECORDSIZE, 1, p_data->file); + fread(&errcode, RECORDSIZE, 1, p_data->file); + fread(&magic2, RECORDSIZE, 1, p_data->file); + + // --- rewind and read magic number from beginning of the file + _fseek(p_data->file, 0L, SEEK_SET); + fread(&magic1, RECORDSIZE, 1, p_data->file); + + // Is this a valid SWMM binary output file? + if (magic1 != magic2) errorcode = 435; + // Does the binary file contain results? + else if (p_data->Nperiods <= 0) errorcode = 436; + // Were there problems with the model run? + else if (errcode != 0) errorcode = 10; + + return errorcode; +} + +void initElementNames(data_t* p_data) +{ + int j, numNames; + + numNames = p_data->Nsubcatch + p_data->Nnodes + p_data->Nlinks + p_data->Npolluts; + + // allocate memory for array of idEntries + p_data->elementNames = (idEntry*)calloc(numNames, sizeof(idEntry)); + + // Position the file to the start of the ID entries + _fseek(p_data->file, p_data->IDPos, SEEK_SET); + + for(j=0;jelementNames[j].length), RECORDSIZE, 1, p_data->file); + + p_data->elementNames[j].IDname = + (char*)calloc(p_data->elementNames[j].length + 1, sizeof(char)); + + fread(p_data->elementNames[j].IDname, sizeof(char), + p_data->elementNames[j].length, p_data->file); + } +} + +void clearElementNames(data_t* p_data) +{ + int i, n; + + if (p_data->elementNames != NULL) + { + n = p_data->Nsubcatch + p_data->Nnodes + p_data->Nlinks + p_data->Npolluts; + + for(i = 0; i < n; i++) + free(p_data->elementNames[i].IDname); + + free(p_data->elementNames); + } +} + +double getTimeValue(data_t* p_data, int timeIndex) +{ + F_OFF offset; + double value; + + // --- compute offset into output file + offset = p_data->ResultsPos + timeIndex*p_data->BytesPerPeriod; + + // --- re-position the file and read the result + _fseek(p_data->file, offset, SEEK_SET); + fread(&value, RECORDSIZE * 2, 1, p_data->file); + + return value; +} + +float getSubcatchValue(data_t* p_data, int timeIndex, int subcatchIndex, + SMO_subcatchAttribute attr) +{ + F_OFF offset; + float value; + + // --- compute offset into output file + offset = p_data->ResultsPos + timeIndex*p_data->BytesPerPeriod + 2*RECORDSIZE; + // offset for subcatch + offset += RECORDSIZE*(subcatchIndex*p_data->SubcatchVars + attr); + + // --- re-position the file and read the result + _fseek(p_data->file, offset, SEEK_SET); + fread(&value, RECORDSIZE, 1, p_data->file); + + return value; +} + +float getNodeValue(data_t* p_data, int timeIndex, int nodeIndex, + SMO_nodeAttribute attr) +{ + F_OFF offset; + float value; + + // --- compute offset into output file + offset = p_data->ResultsPos + timeIndex*p_data->BytesPerPeriod + 2*RECORDSIZE; + // offset for node + offset += RECORDSIZE*(p_data->Nsubcatch*p_data->SubcatchVars + nodeIndex*p_data->NodeVars + attr); + + // --- re-position the file and read the result + _fseek(p_data->file, offset, SEEK_SET); + fread(&value, RECORDSIZE, 1, p_data->file); + + return value; +} + +float getLinkValue(data_t* p_data, int timeIndex, int linkIndex, + SMO_linkAttribute attr) +{ + F_OFF offset; + float value; + + // --- compute offset into output file + offset = p_data->ResultsPos + timeIndex*p_data->BytesPerPeriod + 2*RECORDSIZE; + // offset for link + offset += RECORDSIZE*(p_data->Nsubcatch*p_data->SubcatchVars + p_data->Nnodes*p_data->NodeVars + + linkIndex*p_data->LinkVars + attr); + + // --- re-position the file and read the result + _fseek(p_data->file, offset, SEEK_SET); + fread(&value, RECORDSIZE, 1, p_data->file); + + return value; +} + +float getSystemValue(data_t* p_data, int timeIndex, + SMO_systemAttribute attr) +{ + F_OFF offset; + float value; + + // --- compute offset into output file + offset = p_data->ResultsPos + timeIndex*p_data->BytesPerPeriod + 2*RECORDSIZE; + // offset for system + offset += RECORDSIZE*(p_data->Nsubcatch*p_data->SubcatchVars + p_data->Nnodes*p_data->NodeVars + + p_data->Nlinks*p_data->LinkVars + attr); + + // --- re-position the file and read the result + _fseek(p_data->file, offset, SEEK_SET); + fread(&value, RECORDSIZE, 1, p_data->file); + + return value; +} + +int _fopen(FILE **f, const char *name, const char *mode) { + // + // Purpose: Substitute for fopen_s on platforms where it doesn't exist + // Note: fopen_s is part of C++11 standard + // + int ret = 0; +#ifdef _MSC_VER + ret = (int)fopen_s(f, name, mode); +#else + *f = fopen(name, mode); + if (!*f) + ret = -1; +#endif + return ret; +} + +int _fseek(FILE* stream, F_OFF offset, int whence) +// +// Purpose: Selects platform fseek() for large file support +// +{ +#ifdef _MSC_VER +#define FSEEK64 _fseeki64 +#else +#define FSEEK64 fseeko +#endif + + return FSEEK64(stream, offset, whence); +} + +F_OFF _ftell(FILE* stream) +// +// Purpose: Selects platform ftell() for large file support +// +{ +#ifdef _MSC_VER +#define FTELL64 _ftelli64 +#else +#define FTELL64 ftello +#endif + return FTELL64(stream); +} + +float* newFloatArray(int n) +// +// Warning: Caller must free memory allocated by this function. +// +{ + return (float*) malloc((n)*sizeof(float)); +} + +int* newIntArray(int n) +// +// Warning: Caller must free memory allocated by this function. +// +{ + return (int*) malloc((n)*sizeof(int)); +} + +char* newCharArray(int n) +// +// Warning: Caller must free memory allocated by this function. +// +{ + return (char*) malloc((n)*sizeof(char)); +} diff --git a/tools/.gitignore b/tools/.gitignore deleted file mode 100644 index 5a45dbdff..000000000 --- a/tools/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# Python compiler files -*.py[c] - -# Python distribution and packaging -build/ -dist/ -temp/ -*.cfg -*.egg-info/ - - -# C compiler -*.o -*.dll -*.exe - -# Eclipse project files and directories -.metadata/ -.settings/ -Release/ -.project -.cproject -.pydevproject diff --git a/tools/swmm-output/src/outputapi.c b/tools/swmm-output/src/outputapi.c deleted file mode 100644 index fe5f0372f..000000000 --- a/tools/swmm-output/src/outputapi.c +++ /dev/null @@ -1,1015 +0,0 @@ -/* -* outputAPI.c -* -* Author: Colleen Barr -* Modified by: Michael E. Tryby, -* Bryant McDonnell -* -* -*/ - -#include -#include -#include -#include "outputAPI.h" -//#include "datetime.h" - - -// NOTE: These depend on machine data model and may change when porting -#define F_OFF off_t // Must be a 8 byte / 64 bit integer for large file support -#define INT4 int // Must be a 4 byte / 32 bit integer type -#define REAL4 float // Must be a 4 byte / 32 bit real type - -#define RECORDSIZE 4 // Memory alignment 4 byte word size for both int and real -#define DATESIZE 8 // Dates are stored as 8 byte word size - -#define MEMCHECK(x) (((x) == NULL) ? 414 : 0 ) - -struct IDentry { - char* IDname; - int length; -}; -typedef struct IDentry idEntry; - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- - -struct SMOutputAPI { - char name[MAXFILENAME + 1]; // file path/name - FILE* file; // FILE structure pointer - - struct IDentry *elementNames; // array of pointers to element names - - long Nperiods; // number of reporting periods - int FlowUnits; // flow units code - - int Nsubcatch; // number of subcatchments - int Nnodes; // number of drainage system nodes - int Nlinks; // number of drainage system links - int Npolluts; // number of pollutants tracked - - int SubcatchVars; // number of subcatch reporting variables - int NodeVars; // number of node reporting variables - int LinkVars; // number of link reporting variables - int SysVars; // number of system reporting variables - - double StartDate; // start date of simulation - int ReportStep; // reporting time step (seconds) - - F_OFF IDPos; // file position where object ID names start - F_OFF ObjPropPos; // file position where object properties start - F_OFF ResultsPos; // file position where results start - F_OFF BytesPerPeriod; // bytes used for results in each period -}; - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -int validateFile(SMOutputAPI* smoapi); -void initElementNames(SMOutputAPI* smoapi); - -double getTimeValue(SMOutputAPI* smoapi, long timeIndex); -float getSubcatchValue(SMOutputAPI* smoapi, long timeIndex, int subcatchIndex, SMO_subcatchAttribute attr); -float getNodeValue(SMOutputAPI* smoapi, long timeIndex, int nodeIndex, SMO_nodeAttribute attr); -float getLinkValue(SMOutputAPI* smoapi, long timeIndex, int linkIndex, SMO_linkAttribute attr); -float getSystemValue(SMOutputAPI* smoapi, long timeIndex, SMO_systemAttribute attr); - - -SMOutputAPI* DLLEXPORT SMO_init(void) -// -// Purpose: Returns an initialized pointer for the opaque SMOutputAPI -// structure. -// -{ - SMOutputAPI *smoapi = malloc(sizeof(struct SMOutputAPI)); - smoapi->elementNames = NULL; - - return smoapi; -} - -int DLLEXPORT SMO_open(SMOutputAPI* smoapi, const char* path) -// -// Purpose: Open the output binary file and read the header. -// -{ - int version, err, errorcode = 0; - F_OFF offset; - - if (smoapi == NULL) errorcode = 410; - else - { - strncpy(smoapi->name, path, MAXFILENAME); - - // --- open the output file - if ((smoapi->file = fopen(path, "rb")) == NULL) errorcode = 434; - // --- validate the output file - else if ((err = validateFile(smoapi)) != 0) errorcode = err; - - else { - // --- otherwise read additional parameters from start of file - fread(&version, RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->FlowUnits), RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->Nsubcatch), RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->Nnodes), RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->Nlinks), RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->Npolluts), RECORDSIZE, 1, smoapi->file); - - // Skip over saved subcatch/node/link input values - offset = (smoapi->Nsubcatch + 2) * RECORDSIZE // Subcatchment area - + (3 * smoapi->Nnodes + 4) * RECORDSIZE // Node type, invert & max depth - + (5 * smoapi->Nlinks + 6) * RECORDSIZE; // Link type, z1, z2, max depth & length - offset += smoapi->ObjPropPos; - - // Read number & codes of computed variables - fseeko64(smoapi->file, offset, SEEK_SET); - fread(&(smoapi->SubcatchVars), RECORDSIZE, 1, smoapi->file); // # Subcatch variables - - fseeko64(smoapi->file, smoapi->SubcatchVars*RECORDSIZE, SEEK_CUR); - fread(&(smoapi->NodeVars), RECORDSIZE, 1, smoapi->file); // # Node variables - - fseeko64(smoapi->file, smoapi->NodeVars*RECORDSIZE, SEEK_CUR); - fread(&(smoapi->LinkVars), RECORDSIZE, 1, smoapi->file); // # Link variables - - fseeko64(smoapi->file, smoapi->LinkVars*RECORDSIZE, SEEK_CUR); - fread(&(smoapi->SysVars), RECORDSIZE, 1, smoapi->file); // # System variables - - // --- read data just before start of output results - offset = smoapi->ResultsPos - 3 * RECORDSIZE; - fseeko64(smoapi->file, offset, SEEK_SET); - fread(&(smoapi->StartDate), DATESIZE, 1, smoapi->file); - fread(&(smoapi->ReportStep), RECORDSIZE, 1, smoapi->file); - - // --- compute number of bytes of results values used per time period - smoapi->BytesPerPeriod = DATESIZE + - (smoapi->Nsubcatch*smoapi->SubcatchVars + - smoapi->Nnodes*smoapi->NodeVars + - smoapi->Nlinks*smoapi->LinkVars + - smoapi->SysVars)*RECORDSIZE; - } - } - - if (errorcode) - SMO_close(smoapi); - - return errorcode; - -} - -int DLLEXPORT SMO_getProjectSize(SMOutputAPI* smoapi, SMO_elementCount code, int* count) -// -// Purpose: Returns project size. -// -{ - int errorcode = 0; - - *count = -1; - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else - { - switch (code) - { - case subcatchCount: - *count = smoapi->Nsubcatch; - break; - - case nodeCount: - *count = smoapi->Nnodes; - break; - - case linkCount: - *count = smoapi->Nlinks; - break; - - case pollutantCount: - *count = smoapi->Npolluts; - break; - - default: - errorcode = 421; - } - } - - return errorcode; -} - -int DLLEXPORT SMO_getUnits(SMOutputAPI* smoapi, SMO_unit code, int* unitFlag) -// -// Purpose: Returns flow rate units. -// -// Returns: -// 0: CFS (cubic feet per second) -// 1: GPM (gallons per minute) -// 2: MGD (million gallons per day) -// 3: CMS (cubic meters per second) -// 4: LPS (liters per second) -// 5: MLD (million liters per day) -// -{ - int errorcode = 0; - - *unitFlag = -1; - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else - { - switch (code) - { - case flow_rate: *unitFlag = smoapi->FlowUnits; - break; -// case concentration: *unitFlag = ConcUnits; -// break; - default: errorcode = 421; - } - } - - return errorcode; -} - - -int DLLEXPORT SMO_getPollutantUnits(SMOutputAPI* smoapi, int pollutantIndex, int* unitFlag) -// -// Purpose: Return integer flag representing the units that the given pollutant is measured in. -// Concentration units are located after the pollutant ID names and before the object properties start, -// and are stored for each pollutant. They're stored as 4-byte integers with the following codes: -// 0: mg/L -// 1: ug/L -// 2: count/L -// -// pollutantIndex: valid values are 0 to Npolluts-1 -{ - int errorcode = 0; - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (pollutantIndex < 0 || pollutantIndex >= smoapi->Npolluts) errorcode = 423; - else - { - int offset = smoapi->ObjPropPos - (smoapi->Npolluts - pollutantIndex) * RECORDSIZE; - fseek(smoapi->file, offset, SEEK_SET); - fread(unitFlag, RECORDSIZE, 1, smoapi->file); - } - - return errorcode; -} - - -int DLLEXPORT SMO_getStartTime(SMOutputAPI* smoapi, double* time) -// -// Purpose: Returns start date. -// -{ - int errorcode = 0; - - *time = -1; - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else - *time = smoapi->StartDate; - - return errorcode; -} - - -int DLLEXPORT SMO_getTimes(SMOutputAPI* smoapi, SMO_time code, int* time) -// -// Purpose: Returns step size and number of periods. -// -{ - int errorcode = 0; - - *time = -1; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else - { - switch (code) - { - case reportStep: *time = smoapi->ReportStep; - break; - case numPeriods: *time = smoapi->Nperiods; - break; - default: errorcode = 421; - } - } - - return errorcode; -} - -int DLLEXPORT SMO_getElementName(SMOutputAPI* smoapi, SMO_elementType type, - int index, char* name, int length) -// -// Purpose: Given an element index returns the element name. -// -// Note: The caller is responsible for allocating memory for the char array -// name. The caller passes the length of the array allocated. The name may -// be truncated if an array of adequate length is not passed. -// -{ - int idx, errorcode = 0; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else - { - // Initialize the name array if necessary - if (smoapi->elementNames == NULL) initElementNames(smoapi); - - switch (type) - { - case subcatch: - if (index < 0 || index >= smoapi->Nsubcatch) - errorcode = 423; - else - idx = index; - break; - - case node: - if (index < 0 || index >= smoapi->Nnodes) - errorcode = 423; - else - idx = smoapi->Nsubcatch + index; - break; - - case link: - if (index < 0 || index >= smoapi->Nlinks) - errorcode = 423; - else - idx = smoapi->Nsubcatch + smoapi->Nnodes + index; - break; - - case sys: - if (index < 0 || index >= smoapi->Npolluts) - errorcode = 423; - else - idx = smoapi->Nsubcatch + smoapi->Nnodes + smoapi->Nlinks + index; - break; - - default: - errorcode = 421; - } - - if (!errorcode) - strncpy(name, smoapi->elementNames[idx].IDname, length); - } - - return errorcode; -} - - -float* DLLEXPORT SMO_newOutValueSeries(SMOutputAPI* smoapi, long startPeriod, - long endPeriod, long* length, int* errcode) -// -// Purpose: Allocates memory for outValue Series. -// -// Warning: Caller must free memory allocated by this function using SMO_free(). -// -{ - long size; - float* array; - - if (smoapi == NULL) *errcode = 410; - else if (smoapi->file == NULL) *errcode = 411; - else if (startPeriod < 0 || endPeriod >= smoapi->Nperiods || - endPeriod <= startPeriod) *errcode = 422; - else - { - - size = endPeriod - startPeriod; - if (size > smoapi->Nperiods) - size = smoapi->Nperiods; - - array = (float*)calloc(size, sizeof(float)); - *errcode = (MEMCHECK(array)); - - *length = size; - return array; - } - - return NULL; -} - - -float* DLLEXPORT SMO_newOutValueArray(SMOutputAPI* smoapi, SMO_apiFunction func, - SMO_elementType type, long* length, int* errcode) -// -// Purpose: Allocates memory for outValue Array. -// -// Warning: Caller must free memory allocated by this function using SMO_free(). -// -{ - long size; - float* array; - - if (smoapi == NULL) *errcode = 410; - else if (smoapi->file == NULL) *errcode = 411; - else - { - switch (func) - { - case getAttribute: - if (type == subcatch) - size = smoapi->Nsubcatch; - else if (type == node) - size = smoapi->Nnodes; - else if (type == link) - size = smoapi->Nlinks; - else // system - size = 1; - break; - - case getResult: - if (type == subcatch) - size = smoapi->SubcatchVars; - else if (type == node) - size = smoapi->NodeVars; - else if (type == link) - size = smoapi->LinkVars; - else // system - size = smoapi->SysVars; - break; - - default: *errcode = 421; - return NULL; - } - - // Allocate memory for outValues - array = (float*)calloc(size, sizeof(float)); - *errcode = (MEMCHECK(array)); - - *length = size; - return array; - } - - return NULL; -} - - -int DLLEXPORT SMO_getSubcatchSeries(SMOutputAPI* smoapi, int subcatchIndex, - SMO_subcatchAttribute attr, long startPeriod, long length, float* outValueSeries) -// -// Purpose: Get time series results for particular attribute. Specify series -// start and length using timeIndex and length respectively. -// -{ - int errorcode = 0; - - long k; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (subcatchIndex < 0 || subcatchIndex > smoapi->Nsubcatch) errorcode = 420; - else if (startPeriod < 0 || startPeriod >= smoapi->Nperiods || - length > smoapi->Nperiods) errorcode = 422; - else if (outValueSeries == NULL) errorcode = 424; - else - { - // loop over and build time series - for (k = 0; k < length; k++) - outValueSeries[k] = getSubcatchValue(smoapi, startPeriod + k, - subcatchIndex, attr); - } - - return errorcode; -} - - -int DLLEXPORT SMO_getNodeSeries(SMOutputAPI* smoapi, int nodeIndex, SMO_nodeAttribute attr, - long startPeriod, long length, float* outValueSeries) -// -// Purpose: Get time series results for particular attribute. Specify series -// start and length using timeIndex and length respectively. -// -{ - int errorcode = 0; - - long k; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (nodeIndex < 0 || nodeIndex > smoapi->Nnodes) errorcode = 420; - else if (startPeriod < 0 || startPeriod >= smoapi->Nperiods || - length > smoapi->Nperiods) errorcode = 422; - else if (outValueSeries == NULL) errorcode = 424; - else - { - // loop over and build time series - for (k = 0; k < length; k++) - outValueSeries[k] = getNodeValue(smoapi, startPeriod + k, - nodeIndex, attr); - } - - return errorcode; -} - - -int DLLEXPORT SMO_getLinkSeries(SMOutputAPI* smoapi, int linkIndex, SMO_linkAttribute attr, - long startPeriod, long length, float* outValueSeries) -// -// Purpose: Get time series results for particular attribute. Specify series -// start and length using timeIndex and length respectively. -// -{ - int errorcode = 0; - - long k; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (linkIndex < 0 || linkIndex > smoapi->Nlinks) errorcode = 420; - else if (startPeriod < 0 || startPeriod >= smoapi->Nperiods || - length > smoapi->Nperiods) errorcode = 422; - else if (outValueSeries == NULL) errorcode = 424; - else - { - // loop over and build time series - for (k = 0; k < length; k++) - outValueSeries[k] = getLinkValue(smoapi, startPeriod + k, linkIndex, attr); - } - - return errorcode; -} - - - -int DLLEXPORT SMO_getSystemSeries(SMOutputAPI* smoapi, SMO_systemAttribute attr, - long startPeriod, long length, float *outValueSeries) -// -// Purpose: Get time series results for particular attribute. Specify series -// start and length using timeIndex and length respectively. -// -{ - int errorcode = 0; - - long k; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (startPeriod < 0 || startPeriod >= smoapi->Nperiods || - length > smoapi->Nperiods) errorcode = 422; - else if (outValueSeries == NULL) errorcode = 424; - else - { - // loop over and build time series - for (k = 0; k < length; k++) - outValueSeries[k] = getSystemValue(smoapi, startPeriod + k, attr); - } - - return errorcode; -} - -int DLLEXPORT SMO_getSubcatchAttribute(SMOutputAPI* smoapi, long periodIndex, - SMO_subcatchAttribute attr, float* outValueArray) -// -// Purpose: For all subcatchments at given time, get a particular attribute. -// -{ - int errorcode = 0; - - long k; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (outValueArray == NULL) errorcode = 424; - else - { - // loop over and pull result - for (k = 0; k < smoapi->Nsubcatch; k++) - outValueArray[k] = getSubcatchValue(smoapi, periodIndex, k, attr); - } - - return errorcode; -} - - - -int DLLEXPORT SMO_getNodeAttribute(SMOutputAPI* smoapi, long periodIndex, - SMO_nodeAttribute attr, float* outValueArray) -// -// Purpose: For all nodes at given time, get a particular attribute. -// -{ - int errorcode = 0; - - long k; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (outValueArray == NULL) errorcode = 424; - else - { - // loop over and pull result - for (k = 0; k < smoapi->Nnodes; k++) - outValueArray[k] = getNodeValue(smoapi, periodIndex, k, attr); - } - - return errorcode; -} - -int DLLEXPORT SMO_getLinkAttribute(SMOutputAPI* smoapi, long periodIndex, - SMO_linkAttribute attr, float* outValueArray) -// -// Purpose: For all links at given time, get a particular attribute. -// -{ - int errorcode = 0; - - long k; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (outValueArray == NULL) errorcode = 424; - else - { - // loop over and pull result - for (k = 0; k < smoapi->Nlinks; k++) - outValueArray[k] = getLinkValue(smoapi, periodIndex, k, attr); - } - - return errorcode; -} - - -int DLLEXPORT SMO_getSystemAttribute(SMOutputAPI* smoapi, long periodIndex, - SMO_systemAttribute attr, float* outValueArray) -// -// Purpose: For the system at given time, get a particular attribute. -// -{ - int errorcode = 0; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (outValueArray == NULL) errorcode = 424; - else - { - // don't need to loop since there's only one system - outValueArray[0] = getSystemValue(smoapi, periodIndex, attr); - } - - return errorcode; -} - -int DLLEXPORT SMO_getSubcatchResult(SMOutputAPI* smoapi, long periodIndex, int subcatchIndex, - float* outValueArray, int* arrayLength) -// -// Purpose: For a subcatchment at given time, get all attributes. -// -{ - int errorcode = 0; - - F_OFF offset; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (subcatchIndex < 0 || subcatchIndex > smoapi->Nsubcatch) errorcode = 423; - else if (outValueArray == NULL) errorcode = 424; - else - { - // --- compute offset into output file - offset = smoapi->ResultsPos + (periodIndex)*smoapi->BytesPerPeriod + 2 * RECORDSIZE; - // add offset for subcatchment - offset += (subcatchIndex*smoapi->SubcatchVars)*RECORDSIZE; - - fseeko64(smoapi->file, offset, SEEK_SET); - fread(outValueArray, RECORDSIZE, smoapi->SubcatchVars, smoapi->file); - *arrayLength = smoapi->SubcatchVars; - } - - return errorcode; -} - - -int DLLEXPORT SMO_getNodeResult(SMOutputAPI* smoapi, long periodIndex, int nodeIndex, - float* outValueArray, int* arrayLength) -// -// Purpose: For a node at given time, get all attributes. -// -{ - int errorcode = 0; - - F_OFF offset; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (nodeIndex < 0 || nodeIndex > smoapi->Nnodes) errorcode = 423; - else if (outValueArray == NULL) errorcode = 424; - else - { - // calculate byte offset to start time for series - offset = smoapi->ResultsPos + (periodIndex)*smoapi->BytesPerPeriod + 2 * RECORDSIZE; - // add offset for subcatchment and node - offset += (smoapi->Nsubcatch*smoapi->SubcatchVars + nodeIndex*smoapi->NodeVars)*RECORDSIZE; - - fseeko64(smoapi->file, offset, SEEK_SET); - fread(outValueArray, RECORDSIZE, smoapi->NodeVars, smoapi->file); - *arrayLength = smoapi->NodeVars; - } - - return errorcode; -} - - -int DLLEXPORT SMO_getLinkResult(SMOutputAPI* smoapi, long periodIndex, int linkIndex, - float* outValueArray, int* arrayLength) -// -// Purpose: For a link at given time, get all attributes. -// -{ - int errorcode = 0; - - F_OFF offset; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (linkIndex < 0 || linkIndex > smoapi->Nlinks) errorcode = 423; - else if (outValueArray == NULL) errorcode = 424; - else - { - // calculate byte offset to start time for series - offset = smoapi->ResultsPos + (periodIndex)*smoapi->BytesPerPeriod + 2 * RECORDSIZE; - // add offset for subcatchment and node and link - offset += (smoapi->Nsubcatch*smoapi->SubcatchVars - + smoapi->Nnodes*smoapi->NodeVars + linkIndex*smoapi->LinkVars)*RECORDSIZE; - - fseeko64(smoapi->file, offset, SEEK_SET); - fread(outValueArray, RECORDSIZE, smoapi->LinkVars, smoapi->file); - *arrayLength = smoapi->LinkVars; - } - - return errorcode; -} - -int DLLEXPORT SMO_getSystemResult(SMOutputAPI* smoapi, long periodIndex, int dummyIndex, - float* outValueArray, int* arrayLength) -// -// Purpose: For the system at given time, get all attributes. -// -{ - int errorcode = 0; - - F_OFF offset; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else if (periodIndex < 0 || periodIndex >= smoapi->Nperiods) errorcode = 422; - else if (outValueArray == NULL) errorcode = 424; - { - // calculate byte offset to start time for series - offset = smoapi->ResultsPos + (periodIndex)*smoapi->BytesPerPeriod + 2 * RECORDSIZE; - // add offset for subcatchment and node and link (system starts after the last link) - offset += (smoapi->Nsubcatch*smoapi->SubcatchVars + smoapi->Nnodes*smoapi->NodeVars - + smoapi->Nlinks*smoapi->LinkVars)*RECORDSIZE; - - fseeko64(smoapi->file, offset, SEEK_SET); - fread(outValueArray, RECORDSIZE, smoapi->SysVars, smoapi->file); - *arrayLength = smoapi->SysVars; - } - - return errorcode; -} - -void DLLEXPORT SMO_free(float *array) -// -// Purpose: frees memory allocated using SMO_newOutValueSeries() or -// SMO_newOutValueArray(). -// -{ - if (array != NULL) - free(array); -} - - -int DLLEXPORT SMO_close(SMOutputAPI* smoapi) -// -// Purpose: Clean up after and close Output API -// -{ - int i, n, errorcode = 0; - - if (smoapi == NULL) errorcode = 410; - else if (smoapi->file == NULL) errorcode = 411; - else - { - if (smoapi->elementNames != NULL) - { - n = smoapi->Nsubcatch + smoapi->Nnodes + smoapi->Nlinks + smoapi->Npolluts; - - for(i = 0; i < n; i++) - free(smoapi->elementNames[i].IDname); - } - - fclose(smoapi->file); - free(smoapi); - smoapi = NULL; - } - - return errorcode; -} - -void DLLEXPORT SMO_errMessage(int errcode, char* errmsg, int n) -// -// Purpose: takes error code returns error message -// -// ERR410 "Error 410: SMO_init has not been called" -// ERR411 "Error 411: SMO_open has not been called" -// ERR414 "Error 414: memory allocation failure" -// -// ERR421 "Input Error 421: invalid parameter code" -// ERR422 "Input Error 422: reporting period index out of range" -// ERR423 "Input Error 423: element index out of range" -// ERR424 "Input Error 424: no memory allocated for results" -// -// ERR434 "File Error 434: unable to open binary output file" -// ERR435 "File Error 435: invalid file - not created by SWMM" -// ERR436 "File Error 436: invalid file - contains no results" -// ERR437 "File Error 437: invalid file - model run issued warnings" -// -// ERR440 "ERROR 440: an unspecified error has occurred" -{ - switch (errcode) - { - case 410: - strncpy(errmsg, ERR410, n); - break; - case 411: - strncpy(errmsg, ERR411, n); - break; - case 414: - strncpy(errmsg, ERR414, n); - break; - case 421: - strncpy(errmsg, ERR421, n); - break; - case 422: - strncpy(errmsg, ERR422, n); - break; - case 423: - strncpy(errmsg, ERR423, n); - break; - case 424: - strncpy(errmsg, ERR424, n); - break; - case 434: - strncpy(errmsg, ERR434, n); - break; - case 435: - strncpy(errmsg, ERR435, n); - break; - case 436: - strncpy(errmsg, ERR436, n); - break; - case 437: - strncpy(errmsg, ERR437, n); - break; - default: - strncpy(errmsg, ERR440, n); - } -} - - -// Local functions: -int validateFile(SMOutputAPI* smoapi) -{ - INT4 magic1, magic2, errcode; - int errorcode = 0; - - // --- fast forward to end and read epilogue - fseeko64(smoapi->file, -6 * RECORDSIZE, SEEK_END); - fread(&(smoapi->IDPos), RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->ObjPropPos), RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->ResultsPos), RECORDSIZE, 1, smoapi->file); - fread(&(smoapi->Nperiods), RECORDSIZE, 1, smoapi->file); - fread(&errcode, RECORDSIZE, 1, smoapi->file); - fread(&magic2, RECORDSIZE, 1, smoapi->file); - - // --- rewind and read magic number from beginning of the file - fseeko(smoapi->file, 0L, SEEK_SET); - fread(&magic1, RECORDSIZE, 1, smoapi->file); - - // Is this a valid SWMM binary output file? - if (magic1 != magic2) errorcode = 435; - // Does the binary file contain results? - else if (smoapi->Nperiods <= 0) errorcode = 436; - // Were there problems with the model run? - else if (errcode != 0) errorcode = 437; - - return errorcode; -} - -void initElementNames(SMOutputAPI* smoapi) -{ - int j, numNames; - - numNames = smoapi->Nsubcatch + smoapi->Nnodes + smoapi->Nlinks + smoapi->Npolluts; - - // allocate memory for array of idEntries - smoapi->elementNames = (idEntry*)calloc(numNames, sizeof(idEntry)); - - // Position the file to the start of the ID entries - fseeko64(smoapi->file, smoapi->IDPos, SEEK_SET); - - for(j=0;jelementNames[j].length), RECORDSIZE, 1, smoapi->file); - smoapi->elementNames[j].IDname = calloc(smoapi->elementNames[j].length + 1, sizeof(char)); - fread(smoapi->elementNames[j].IDname, sizeof(char), smoapi->elementNames[j].length, smoapi->file); - } -} - -double getTimeValue(SMOutputAPI* smoapi, long timeIndex) -{ - F_OFF offset; - double value; - - // --- compute offset into output file - offset = smoapi->ResultsPos + timeIndex*smoapi->BytesPerPeriod; - - // --- re-position the file and read the result - fseeko64(smoapi->file, offset, SEEK_SET); - fread(&value, RECORDSIZE * 2, 1, smoapi->file); - - return value; -} - -float getSubcatchValue(SMOutputAPI* smoapi, long timeIndex, int subcatchIndex, - SMO_subcatchAttribute attr) -{ - F_OFF offset; - float value; - - // --- compute offset into output file - offset = smoapi->ResultsPos + timeIndex*smoapi->BytesPerPeriod + 2*RECORDSIZE; - // offset for subcatch - offset += RECORDSIZE*(subcatchIndex*smoapi->SubcatchVars + attr); - - // --- re-position the file and read the result - fseeko64(smoapi->file, offset, SEEK_SET); - fread(&value, RECORDSIZE, 1, smoapi->file); - - return value; -} - -float getNodeValue(SMOutputAPI* smoapi, long timeIndex, int nodeIndex, - SMO_nodeAttribute attr) -{ - F_OFF offset; - float value; - - // --- compute offset into output file - offset = smoapi->ResultsPos + timeIndex*smoapi->BytesPerPeriod + 2*RECORDSIZE; - // offset for node - offset += RECORDSIZE*(smoapi->Nsubcatch*smoapi->SubcatchVars + nodeIndex*smoapi->NodeVars + attr); - - // --- re-position the file and read the result - fseeko64(smoapi->file, offset, SEEK_SET); - fread(&value, RECORDSIZE, 1, smoapi->file); - - return value; -} - -float getLinkValue(SMOutputAPI* smoapi, long timeIndex, int linkIndex, - SMO_linkAttribute attr) -{ - F_OFF offset; - float value; - - // --- compute offset into output file - offset = smoapi->ResultsPos + timeIndex*smoapi->BytesPerPeriod + 2*RECORDSIZE; - // offset for link - offset += RECORDSIZE*(smoapi->Nsubcatch*smoapi->SubcatchVars + smoapi->Nnodes*smoapi->NodeVars + - linkIndex*smoapi->LinkVars + attr); - - // --- re-position the file and read the result - fseeko64(smoapi->file, offset, SEEK_SET); - fread(&value, RECORDSIZE, 1, smoapi->file); - - return value; -} - -float getSystemValue(SMOutputAPI* smoapi, long timeIndex, - SMO_systemAttribute attr) -{ - F_OFF offset; - float value; - - // --- compute offset into output file - offset = smoapi->ResultsPos + timeIndex*smoapi->BytesPerPeriod + 2*RECORDSIZE; - // offset for system - offset += RECORDSIZE*(smoapi->Nsubcatch*smoapi->SubcatchVars + smoapi->Nnodes*smoapi->NodeVars + - smoapi->Nlinks*smoapi->LinkVars + attr); - - // --- re-position the file and read the result - fseeko64(smoapi->file, offset, SEEK_SET); - fread(&value, RECORDSIZE, 1, smoapi->file); - - return value; -} diff --git a/tools/swmm-output/src/outputapi.h b/tools/swmm-output/src/outputapi.h deleted file mode 100644 index 2428fa0df..000000000 --- a/tools/swmm-output/src/outputapi.h +++ /dev/null @@ -1,190 +0,0 @@ -/* -* outputAPI.h -* -* Author: Colleen Barr -* Modified by: Michael Tryby, -* Bryant McDonnell -* -* -*/ - -#ifndef OUTPUTAPI_H_ -#define OUTPUTAPI_H_ - -#define MAXFILENAME 259 // Max characters in file path -#define MAXELENAME 31 // Max characters in element name - -/*------------------- Error Messages --------------------*/ -#define ERR410 "Error 410: SMO_init() has not been called" -#define ERR411 "Error 411: SMO_open() has not been called" -#define ERR414 "Error 414: memory allocation failure" - -#define ERR421 "Input Error 421: invalid parameter code" -#define ERR422 "Input Error 422: reporting period index out of range" -#define ERR423 "Input Error 423: element index out of range" -#define ERR424 "Input Error 424: no memory allocated for results" - -#define ERR434 "File Error 434: unable to open binary output file" -#define ERR435 "File Error 435: invalid file - not created by SWMM" -#define ERR436 "File Error 436: invalid file - contains no results" -#define ERR437 "File Error 437: invalid file - model run issued warnings" - -#define ERR440 "ERROR 440: an unspecified error has occurred" - - -typedef struct SMOutputAPI SMOutputAPI; // opaque pointer - -typedef enum { - subcatchCount, - nodeCount, - linkCount, - pollutantCount - -} SMO_elementCount; - -typedef enum { - flow_rate, - concentration - -} SMO_unit; - -typedef enum { - //getSeries, - getAttribute, - getResult - -} SMO_apiFunction; - -typedef enum { - subcatch, - node, - link, - sys - -} SMO_elementType; - -typedef enum { -// reportStart, - reportStep, - numPeriods - -} SMO_time; - -typedef enum { - rainfall_subcatch, // (in/hr or mm/hr), - snow_depth_subcatch, // (in or mm), - evap_loss, // (in/hr or mm/hr), - infil_loss, // (in/hr or mm/hr), - runoff_rate, // (flow units), - gwoutflow_rate, // (flow units), - gwtable_elev, // (ft or m), - soil_moisture, // unsaturated zone moisture content (-), - pollutant_conc_subcatch // first pollutant - -} SMO_subcatchAttribute; - -typedef enum { - invert_depth, // (ft or m), - hydraulic_head, // (ft or m), - stored_ponded_volume, // (ft3 or m3), - lateral_inflow, // (flow units), - total_inflow, // lateral + upstream (flow units), - flooding_losses, // (flow units), - pollutant_conc_node // first pollutant, - -} SMO_nodeAttribute; - -typedef enum { - flow_rate_link, // (flow units), - flow_depth, // (ft or m), - flow_velocity, // (ft/s or m/s), - flow_volume, // (ft3 or m3), - capacity, // (fraction of conduit filled), - pollutant_conc_link // first pollutant, - -} SMO_linkAttribute; - -typedef enum { - air_temp, // (deg. F or deg. C), - rainfall_system, // (in/hr or mm/hr), - snow_depth_system, // (in or mm), - evap_infil_loss, // (in/hr or mm/hr), - runoff_flow, // (flow units), - dry_weather_inflow, // (flow units), - groundwater_inflow, // (flow units), - RDII_inflow, // (flow units), - direct_inflow, // user defined (flow units), - total_lateral_inflow, // (sum of variables 4 to 8) //(flow units), - flood_losses, // (flow units), - outfall_flows, // (flow units), - volume_stored, // (ft3 or m3), - evap_rate // (in/day or mm/day), - //p_evap_rate // (in/day or mm/day) -} SMO_systemAttribute; - - -#ifdef WINDOWS - #ifdef __cplusplus - #define DLLEXPORT extern "C" __declspec(dllexport) __stdcall - #else - #define DLLEXPORT __declspec(dllexport) __stdcall - #endif -#else - #ifdef __cplusplus - #define DLLEXPORT extern "C" - #else - #define DLLEXPORT - #endif -#endif - -SMOutputAPI* DLLEXPORT SMO_init(void); -int DLLEXPORT SMO_open(SMOutputAPI* smoapi, const char* path); - -int DLLEXPORT SMO_getProjectSize(SMOutputAPI* smoapi, SMO_elementCount code, - int* count); - -int DLLEXPORT SMO_getUnits(SMOutputAPI* smoapi, SMO_unit code, int* unitFlag); -int DLLEXPORT SMO_getStartTime(SMOutputAPI* smoapi, double* time); -int DLLEXPORT SMO_getTimes(SMOutputAPI* smoapi, SMO_time code, int* time); - -int DLLEXPORT SMO_getElementName(SMOutputAPI* smoapi, SMO_elementType type, - int elementIndex, char* elementName, int length); - -float* DLLEXPORT SMO_newOutValueSeries(SMOutputAPI* smoapi, long seriesStart, - long seriesLength, long* length, int* errcode); -float* DLLEXPORT SMO_newOutValueArray(SMOutputAPI* smoapi, SMO_apiFunction func, - SMO_elementType type, long* length, int* errcode); - -int DLLEXPORT SMO_getSubcatchSeries(SMOutputAPI* smoapi, int subcatchIndex, - SMO_subcatchAttribute attr, long timeIndex, long length, float* outValueSeries); -int DLLEXPORT SMO_getNodeSeries(SMOutputAPI* smoapi, int nodeIndex, SMO_nodeAttribute attr, - long timeIndex, long length, float* outValueSeries); -int DLLEXPORT SMO_getLinkSeries(SMOutputAPI* smoapi, int linkIndex, SMO_linkAttribute attr, - long timeIndex, long length, float* outValueSeries); -int DLLEXPORT SMO_getSystemSeries(SMOutputAPI* smoapi, SMO_systemAttribute attr, - long timeIndex, long length, float *outValueSeries); - -int DLLEXPORT SMO_getSubcatchAttribute(SMOutputAPI* smoapi, long timeIndex, - SMO_subcatchAttribute attr, float* outValueArray); -int DLLEXPORT SMO_getNodeAttribute(SMOutputAPI* smoapi, long timeIndex, - SMO_nodeAttribute attr, float* outValueArray); -int DLLEXPORT SMO_getLinkAttribute(SMOutputAPI* smoapi, long timeIndex, - SMO_linkAttribute attr, float* outValueArray); -int DLLEXPORT SMO_getSystemAttribute(SMOutputAPI* smoapi, long timeIndex, - SMO_systemAttribute attr, float* outValueArray); - -int DLLEXPORT SMO_getSubcatchResult(SMOutputAPI* smoapi, long timeIndex, - int subcatchIndex, float* outValueArray, int* arrayLength); -int DLLEXPORT SMO_getNodeResult(SMOutputAPI* smoapi, long timeIndex, - int nodeIndex, float* outValueArray, int* arrayLength); -int DLLEXPORT SMO_getLinkResult(SMOutputAPI* smoapi, long timeIndex, - int linkIndex, float* outValueArray, int* arrayLength); -int DLLEXPORT SMO_getSystemResult(SMOutputAPI* smoapi, long timeIndex, - int dummyIndex, float* outValueArray, int* arrayLength); - -void DLLEXPORT SMO_free(float *array); - -int DLLEXPORT SMO_close(SMOutputAPI* smoapi); -void DLLEXPORT SMO_errMessage(int errcode, char* errmsg, int n); - -#endif /* OUTPUTAPI_H_ */ diff --git a/tools/swmm-output/test/main.c b/tools/swmm-output/test/main.c deleted file mode 100644 index 6584c237b..000000000 --- a/tools/swmm-output/test/main.c +++ /dev/null @@ -1,58 +0,0 @@ -/* -* main.c -* -* Author: Colleen Barr -* Modified by: Michael E. Tryby -* -*/ - -#include -#include -#include -#include "..\src\outputAPI.h" - -int testGetSubcatchResult(char* path) -{ - int error = 0; - long length; - float* array; - - SMOutputAPI* smoapi = SMO_init(); - error = SMO_open(smoapi, path); - - array = SMO_newOutValueArray(smoapi, getResult, subcatch, &length, &error); - error = SMO_getSubcatchResult(smoapi, 1, 0, array); - - if (!error) - { - for (int i = 0; i < length; i++) - printf("%f\n", array[i]); - } - printf("\n"); - - SMO_free(array); - error = SMO_close(smoapi); - - return error; -} - -int main(int argc, char* argv[]) -{ - int length, error = 0; - char path[MAXFILENAME] = "C:\\Users\\mtryby\\Workspace\\GitRepo\\Local\\swmm-testsuite\\Benchmarks\\v517\\Example1\\Example1.out"; - char name[MAXELENAME]; - SMOutputAPI* smoapi = SMO_init(); - - error = SMO_open(smoapi, path); - - length = MAXELENAME; - SMO_getElementName(smoapi, subcatch, 0, name, &length); - - length = MAXELENAME; - SMO_getElementName(smoapi, sys, 0, name, &length); - - testGetSubcatchResult(path); - - return error; - -} From 20f5d87f75ab9853c86452800ba9e37d83551c74 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 18 Oct 2019 15:46:50 -0400 Subject: [PATCH 016/266] Work in progress Fixing compiler error --- outfile/include/swmm_output.h | 52 ++++++++++++++--------------- outfile/include/swmm_output_enums.h | 2 +- outfile/swmm_output.c | 52 ++++++++++++++--------------- 3 files changed, 52 insertions(+), 54 deletions(-) diff --git a/outfile/include/swmm_output.h b/outfile/include/swmm_output.h index 6b70f31ed..7aac0bbad 100644 --- a/outfile/include/swmm_output.h +++ b/outfile/include/swmm_output.h @@ -28,49 +28,49 @@ typedef void* SMO_Handle; #endif -int DLLEXPORT SMO_init(SMO_Handle* p_handle); -int DLLEXPORT SMO_close(SMO_Handle* p_handle); -int DLLEXPORT SMO_open(SMO_Handle p_handle, const char* path); -int DLLEXPORT SMO_getVersion(SMO_Handle p_handle, int* version); -int DLLEXPORT SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, int* length); - -int DLLEXPORT SMO_getFlowUnits(SMO_Handle p_handle, int* unitFlag); -int DLLEXPORT SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, int* length); -int DLLEXPORT SMO_getStartDate(SMO_Handle p_handle, double* date); -int DLLEXPORT SMO_getTimes(SMO_Handle p_handle, SMO_time code, int* time); -int DLLEXPORT SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, +int EXPORT_OUT_API SMO_init(SMO_Handle* p_handle); +int EXPORT_OUT_API SMO_close(SMO_Handle* p_handle); +int EXPORT_OUT_API SMO_open(SMO_Handle p_handle, const char* path); +int EXPORT_OUT_API SMO_getVersion(SMO_Handle p_handle, int* version); +int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, int* length); + +int EXPORT_OUT_API SMO_getFlowUnits(SMO_Handle p_handle, int* unitFlag); +int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, int* length); +int EXPORT_OUT_API SMO_getStartDate(SMO_Handle p_handle, double* date); +int EXPORT_OUT_API SMO_getTimes(SMO_Handle p_handle, SMO_time code, int* time); +int EXPORT_OUT_API SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, int elementIndex, char** elementName, int* size); -int DLLEXPORT SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, +int EXPORT_OUT_API SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, SMO_subcatchAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim); -int DLLEXPORT SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, SMO_nodeAttribute attr, +int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, SMO_nodeAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim); -int DLLEXPORT SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, SMO_linkAttribute attr, +int EXPORT_OUT_API SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, SMO_linkAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim); -int DLLEXPORT SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, +int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim); -int DLLEXPORT SMO_getSubcatchAttribute(SMO_Handle p_handle, int timeIndex, +int EXPORT_OUT_API SMO_getSubcatchAttribute(SMO_Handle p_handle, int timeIndex, SMO_subcatchAttribute attr, float** outValueArray, int* length); -int DLLEXPORT SMO_getNodeAttribute(SMO_Handle p_handle, int timeIndex, +int EXPORT_OUT_API SMO_getNodeAttribute(SMO_Handle p_handle, int timeIndex, SMO_nodeAttribute attr, float** outValueArray, int* length); -int DLLEXPORT SMO_getLinkAttribute(SMO_Handle p_handle, int timeIndex, +int EXPORT_OUT_API SMO_getLinkAttribute(SMO_Handle p_handle, int timeIndex, SMO_linkAttribute attr, float** outValueArray, int* length); -int DLLEXPORT SMO_getSystemAttribute(SMO_Handle p_handle, int timeIndex, +int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int timeIndex, SMO_systemAttribute attr, float** outValueArray, int* length); -int DLLEXPORT SMO_getSubcatchResult(SMO_Handle p_handle, int timeIndex, +int EXPORT_OUT_API SMO_getSubcatchResult(SMO_Handle p_handle, int timeIndex, int subcatchIndex, float** outValueArray, int* arrayLength); -int DLLEXPORT SMO_getNodeResult(SMO_Handle p_handle, int timeIndex, +int EXPORT_OUT_API SMO_getNodeResult(SMO_Handle p_handle, int timeIndex, int nodeIndex, float** outValueArray, int* arrayLength); -int DLLEXPORT SMO_getLinkResult(SMO_Handle p_handle, int timeIndex, +int EXPORT_OUT_API SMO_getLinkResult(SMO_Handle p_handle, int timeIndex, int linkIndex, float** outValueArray, int* arrayLength); -int DLLEXPORT SMO_getSystemResult(SMO_Handle p_handle, int timeIndex, +int EXPORT_OUT_API SMO_getSystemResult(SMO_Handle p_handle, int timeIndex, int dummyIndex, float** outValueArray, int* arrayLength); -void DLLEXPORT SMO_free(void** array); -void DLLEXPORT SMO_clearError(SMO_Handle p_handle_in); -int DLLEXPORT SMO_checkError(SMO_Handle p_handle_in, char** msg_buffer); +void EXPORT_OUT_API SMO_free(void** array); +void EXPORT_OUT_API SMO_clearError(SMO_Handle p_handle_in); +int EXPORT_OUT_API SMO_checkError(SMO_Handle p_handle_in, char** msg_buffer); #ifdef __cplusplus diff --git a/outfile/include/swmm_output_enums.h b/outfile/include/swmm_output_enums.h index bb9004265..35d440404 100644 --- a/outfile/include/swmm_output_enums.h +++ b/outfile/include/swmm_output_enums.h @@ -21,7 +21,7 @@ typedef enum { SMO_subcatch, SMO_node, SMO_link, - SMO_sys + SMO_sys, SMO_pollut } SMO_elementType; diff --git a/outfile/swmm_output.c b/outfile/swmm_output.c index dc4a22633..8b5de5a02 100644 --- a/outfile/swmm_output.c +++ b/outfile/swmm_output.c @@ -21,8 +21,6 @@ #include "swmm_output.h" - - // NOTE: These depend on machine data model and may change when porting // F_OFF Must be a 8 byte / 64 bit integer for large file support #ifdef _WIN32 // Windows (32-bit and 64-bit) @@ -104,7 +102,7 @@ static int* newIntArray(int n); static char* newCharArray(int n); -int DLLEXPORT SMO_init(SMO_Handle* p_handle) +int EXPORT_OUT_API SMO_init(SMO_Handle* p_handle) // Purpose: Initialized pointer for the opaque SMO_Handle. // // Returns: Error code 0 on success, -1 on failure @@ -130,7 +128,7 @@ int DLLEXPORT SMO_init(SMO_Handle* p_handle) return errorcode; } -int DLLEXPORT SMO_close(SMO_Handle* p_handle) +int EXPORT_OUT_API SMO_close(SMO_Handle* p_handle) // // Purpose: Clean up after and close Output API // @@ -160,7 +158,7 @@ int DLLEXPORT SMO_close(SMO_Handle* p_handle) return errorcode; } -int DLLEXPORT SMO_open(SMO_Handle p_handle, const char* path) +int EXPORT_OUT_API SMO_open(SMO_Handle p_handle, const char* path) // // Purpose: Open the output binary file and read the header. // @@ -234,7 +232,7 @@ int DLLEXPORT SMO_open(SMO_Handle p_handle, const char* path) return errorcode; } -int DLLEXPORT SMO_getVersion(SMO_Handle p_handle, int* version) +int EXPORT_OUT_API SMO_getVersion(SMO_Handle p_handle, int* version) // // Input: p_handle = pointer to SMO_Handle struct // Output: version SWMM version @@ -259,7 +257,7 @@ int DLLEXPORT SMO_getVersion(SMO_Handle p_handle, int* version) return set_error(p_data->error_handle, errorcode); } -int DLLEXPORT SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, int* length) +int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, int* length) // // Purpose: Returns project size. // @@ -285,7 +283,7 @@ int DLLEXPORT SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, int* l return set_error(p_data->error_handle, errorcode); } -int DLLEXPORT SMO_getFlowUnits(SMO_Handle p_handle, int* unitFlag) +int EXPORT_OUT_API SMO_getFlowUnits(SMO_Handle p_handle, int* unitFlag) // // Purpose: Returns unit flag for flow. // @@ -315,7 +313,7 @@ int DLLEXPORT SMO_getFlowUnits(SMO_Handle p_handle, int* unitFlag) return set_error(p_data->error_handle, errorcode); } -int DLLEXPORT SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, int* length) +int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, int* length) // // Purpose: // Return integer flag representing the units that the given pollutant is @@ -351,7 +349,7 @@ int DLLEXPORT SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, int* le return set_error(p_data->error_handle, errorcode); } -int DLLEXPORT SMO_getStartDate(SMO_Handle p_handle, double* date) +int EXPORT_OUT_API SMO_getStartDate(SMO_Handle p_handle, double* date) // // Purpose: Returns start date. // @@ -371,7 +369,7 @@ int DLLEXPORT SMO_getStartDate(SMO_Handle p_handle, double* date) } -int DLLEXPORT SMO_getTimes(SMO_Handle p_handle, SMO_time code, int* time) +int EXPORT_OUT_API SMO_getTimes(SMO_Handle p_handle, SMO_time code, int* time) // // Purpose: Returns step size and number of periods. // @@ -399,7 +397,7 @@ int DLLEXPORT SMO_getTimes(SMO_Handle p_handle, SMO_time code, int* time) return errorcode; } -int DLLEXPORT SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, +int EXPORT_OUT_API SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, int index, char** name, int* length) // // Purpose: Given an element index returns the element name. @@ -464,7 +462,7 @@ int DLLEXPORT SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, } -int DLLEXPORT SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, +int EXPORT_OUT_API SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, SMO_subcatchAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim) // @@ -499,7 +497,7 @@ int DLLEXPORT SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, } -int DLLEXPORT SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, SMO_nodeAttribute attr, +int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, SMO_nodeAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim) // // Purpose: Get time series results for particular attribute. Specify series @@ -533,7 +531,7 @@ int DLLEXPORT SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, SMO_nodeAttr } -int DLLEXPORT SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, SMO_linkAttribute attr, +int EXPORT_OUT_API SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, SMO_linkAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim) // // Purpose: Get time series results for particular attribute. Specify series @@ -567,7 +565,7 @@ int DLLEXPORT SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, SMO_linkAttr -int DLLEXPORT SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, +int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim) // // Purpose: Get time series results for particular attribute. Specify series @@ -598,7 +596,7 @@ int DLLEXPORT SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, return set_error(p_data->error_handle, errorcode); } -int DLLEXPORT SMO_getSubcatchAttribute(SMO_Handle p_handle, int periodIndex, +int EXPORT_OUT_API SMO_getSubcatchAttribute(SMO_Handle p_handle, int periodIndex, SMO_subcatchAttribute attr, float** outValueArray, int* length) // // Purpose: For all subcatchments at given time, get a particular attribute. @@ -629,7 +627,7 @@ int DLLEXPORT SMO_getSubcatchAttribute(SMO_Handle p_handle, int periodIndex, -int DLLEXPORT SMO_getNodeAttribute(SMO_Handle p_handle, int periodIndex, +int EXPORT_OUT_API SMO_getNodeAttribute(SMO_Handle p_handle, int periodIndex, SMO_nodeAttribute attr, float** outValueArray, int* length) // // Purpose: For all nodes at given time, get a particular attribute. @@ -658,7 +656,7 @@ int DLLEXPORT SMO_getNodeAttribute(SMO_Handle p_handle, int periodIndex, return set_error(p_data->error_handle, errorcode); } -int DLLEXPORT SMO_getLinkAttribute(SMO_Handle p_handle, int periodIndex, +int EXPORT_OUT_API SMO_getLinkAttribute(SMO_Handle p_handle, int periodIndex, SMO_linkAttribute attr, float** outValueArray, int* length) // // Purpose: For all links at given time, get a particular attribute. @@ -688,7 +686,7 @@ int DLLEXPORT SMO_getLinkAttribute(SMO_Handle p_handle, int periodIndex, } -int DLLEXPORT SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, +int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, SMO_systemAttribute attr, float** outValueArray, int* length) // // Purpose: For the system at given time, get a particular attribute. @@ -714,7 +712,7 @@ int DLLEXPORT SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, return set_error(p_data->error_handle, errorcode); } -int DLLEXPORT SMO_getSubcatchResult(SMO_Handle p_handle, int periodIndex, +int EXPORT_OUT_API SMO_getSubcatchResult(SMO_Handle p_handle, int periodIndex, int subcatchIndex, float** outValueArray, int* arrayLength) // // Purpose: For a subcatchment at given time, get all attributes. @@ -749,7 +747,7 @@ int DLLEXPORT SMO_getSubcatchResult(SMO_Handle p_handle, int periodIndex, } -int DLLEXPORT SMO_getNodeResult(SMO_Handle p_handle, int periodIndex, +int EXPORT_OUT_API SMO_getNodeResult(SMO_Handle p_handle, int periodIndex, int nodeIndex, float** outValueArray, int* arrayLength) // // Purpose: For a node at given time, get all attributes. @@ -783,7 +781,7 @@ int DLLEXPORT SMO_getNodeResult(SMO_Handle p_handle, int periodIndex, return set_error(p_data->error_handle, errorcode); } -int DLLEXPORT SMO_getLinkResult(SMO_Handle p_handle, int periodIndex, +int EXPORT_OUT_API SMO_getLinkResult(SMO_Handle p_handle, int periodIndex, int linkIndex, float** outValueArray, int* arrayLength) // // Purpose: For a link at given time, get all attributes. @@ -818,7 +816,7 @@ int DLLEXPORT SMO_getLinkResult(SMO_Handle p_handle, int periodIndex, return set_error(p_data->error_handle, errorcode); } -int DLLEXPORT SMO_getSystemResult(SMO_Handle p_handle, int periodIndex, +int EXPORT_OUT_API SMO_getSystemResult(SMO_Handle p_handle, int periodIndex, int dummyIndex, float** outValueArray, int* arrayLength) // // Purpose: For the system at given time, get all attributes. @@ -851,7 +849,7 @@ int DLLEXPORT SMO_getSystemResult(SMO_Handle p_handle, int periodIndex, return set_error(p_data->error_handle, errorcode); } -void DLLEXPORT SMO_free(void** array) +void EXPORT_OUT_API SMO_free(void** array) // // Purpose: Frees memory allocated by API calls // @@ -862,7 +860,7 @@ void DLLEXPORT SMO_free(void** array) } } -void DLLEXPORT SMO_clearError(SMO_Handle p_handle) +void EXPORT_OUT_API SMO_clearError(SMO_Handle p_handle) { data_t* p_data; @@ -870,7 +868,7 @@ void DLLEXPORT SMO_clearError(SMO_Handle p_handle) clear_error(p_data->error_handle); } -int DLLEXPORT SMO_checkError(SMO_Handle p_handle, char** msg_buffer) +int EXPORT_OUT_API SMO_checkError(SMO_Handle p_handle, char** msg_buffer) { int errorcode = 0; char *temp = NULL; From d39d04a554eda8c3a76becf579de76b756372757 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 18 Oct 2019 15:53:30 -0400 Subject: [PATCH 017/266] Update swmm_output.c Combiing element and count enum types Modifying getProjectSize() to make it easier to use the array it returns for iterating over elements --- outfile/swmm_output.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/outfile/swmm_output.c b/outfile/swmm_output.c index 8b5de5a02..a464269e6 100644 --- a/outfile/swmm_output.c +++ b/outfile/swmm_output.c @@ -34,7 +34,7 @@ #define RECORDSIZE 4 // Memory alignment 4 byte word size for both int and real #define DATESIZE 8 // Dates are stored as 8 byte word size -#define NELEMENTTYPES 4 // Number of element types +#define NELEMENTTYPES 5 // Number of element types #define MEMCHECK(x) (((x) == NULL) ? 414 : 0 ) @@ -274,7 +274,8 @@ int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, i temp[0] = p_data->Nsubcatch; temp[1] = p_data->Nnodes; temp[2] = p_data->Nlinks; - temp[3] = p_data->Npolluts; + temp[3] = 1; //Nsystems + temp[4] = p_data->Npolluts; *elementCount = temp; *length = NELEMENTTYPES; From 8b9adfd5ec456676b5eade55a9b6514112282882 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 18 Oct 2019 16:30:25 -0400 Subject: [PATCH 018/266] Work in progress Adding test_output Configuring cmake build --- CMakeLists.txt | 12 ++ tests/CMakeLists.txt | 29 +++ tests/outfile/CMakeLists.txt | 18 ++ tests/outfile/data/Example1.out | Bin 0 -> 44018 bytes tests/outfile/test_output.cpp | 359 ++++++++++++++++++++++++++++++++ 5 files changed, 418 insertions(+) create mode 100644 tests/CMakeLists.txt create mode 100644 tests/outfile/CMakeLists.txt create mode 100644 tests/outfile/data/Example1.out create mode 100644 tests/outfile/test_output.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d0405f3cd..f11b88d9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,10 @@ cmake_minimum_required (VERSION 3.0) +if("${CMAKE_BINARY_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") + message(FATAL_ERROR "In-source builds are disabled.") +endif() + project(swmm-solver VERSION 5.1.14 @@ -29,11 +33,19 @@ set(LIBRARY_DIST "lib") set(CONFIG_DIST "cmake") +option(BUILD_TESTS "Build component tests (requires Boost)" OFF) + + # Add project subdirectories add_subdirectory(outfile) add_subdirectory(src/solver) add_subdirectory(src/run) +if(BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + # Create target import scripts so other cmake projects can use swmm libraries install( diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..4e5a06dbf --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# CMakeLists.txt - CMake configuration file for epanet/tests +# + + +#Prep ourselves for compiling with boost +if(WIN32) + set(Boost_USE_STATIC_LIBS ON) +else() + set(Boost_USE_STATIC_LIBS OFF) + add_definitions(-DBOOST_ALL_DYN_LINK) +endif() + +find_package(Boost COMPONENTS unit_test_framework system thread filesystem) +include_directories (${Boost_INCLUDE_DIRS}) + + +add_subdirectory(outfile) + + +# Setting up tests to run from build tree +set(TEST_BIN_DIRECTORY "${CMAKE_BINARY_DIR}/bin/$") + + +# ctest doesn't like tests added in subdirectories so adding them here +add_test(NAME test_output + COMMAND "${TEST_BIN_DIRECTORY}/test_output" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/outfile/data + ) diff --git a/tests/outfile/CMakeLists.txt b/tests/outfile/CMakeLists.txt new file mode 100644 index 000000000..64dcea739 --- /dev/null +++ b/tests/outfile/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# CMakeLists.txt - CMake configuration file for tests/outfile +# + + +add_executable(test_output + test_output.cpp + ) +target_include_directories(test_output + PUBLIC ../../outfile/include + ) +target_link_libraries(test_output + ${Boost_LIBRARIES} + swmm-output + ) + +set_target_properties(test_output + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) diff --git a/tests/outfile/data/Example1.out b/tests/outfile/data/Example1.out new file mode 100644 index 0000000000000000000000000000000000000000..78b15a8b5c3f6075f1415dad14fd66f6a3bfa11d GIT binary patch literal 44018 zcmeFa2UHZx);0`C&Pj64Ifv=oJ%9lt1`H@D<}3yjFc1}qiWm@(AOa#Fii(I4Fd--g zFe{iqMa8Vg^iS2yNTcU?&%NJW@BhAQoms19YWk_#wfD2D9=dv(nehYa3^->*;6s}C ztjH@0yduUcqP*e^o?O6_D|m7PPwwE!13YP<(>VIX2eO!cZ6UK95F|V%(EdiYq5fSITf&zRO9uq*kmlKqT$XTzR z_n;QZJ2v2790P4Y5%v|n7okN&HviQ3uZ{`(3P(S|viI*EWBVfP3&uUm{qc@p0mr;# z1qT6NV!Q&l0UQB-BzZ-OSHMb;5xnwV;9U8uwxB(D{FObhqQK*?aQ1k>H#uIB=M}ID z!6P!fomS=kroKZyr!5euG2p z_ze#A*)m0ufKYL_4YfQ*Y97&4>jwr?DLNEb?i3{{(=5y7)au2=-<_m z9*^29N=l-kR*CTD%8v(`@1DbcFES@Af_Z5_kmj{uAOEFY>T?P1qXDhIv>WSS%)Gn?8Ly8Q-|Amj-X&Q^*}OE{Ap1XJI4 zApNnd5B02#uT_AXJ;8d=etJ#>6FPVxy{=G`dOEE~%S`GjA)cjH#b|`4(Tmb`sYe#z zJcs{S(s=~pjjNiO*7Jq*GSy;&i38_8A|ik*AzwnCrUBdsoDcCI>jiwO6%=xnhQ~2) zx7Sj`+I#$ud~ZgBmh1AaF<4Y79c*1mZPMoBAol&w2Sa>tpZm7k2R7GZii?u14*x&#VX{GE7us=U8VGhrGsgMfnUh9d%I|zI5)3@mfqwN>^?K$ z%YHg-%MrTtt}3<0{42fmX}VXy)kUOQZ8Nh=w1@_CV&2qIq{xVPdVJ(uE@iiy_&Owl zah4lMcfU5GDSJBGPsF2xF?Wh4za)P0^TEcBeC`I3aFyC`h}IVR zQRXRnv6?gWzN3*2*}mSZ+BTb97hT89pP5B3eMmCIgwa&w2W_V6@&~Utr3SJk>mZ|| znMQ*-u_tc}xgxiimR@s;%h6UMzdl*YIM;a7!-EQ`7q@ql-j|Z-*JHc5-Ilgwon0uC z)$L7}p4vgXlgxvTY z@Z9q-Y?{kFUP$n?pr@T&mNuz$YJIPEcZ_P7hn1UtX%~A;uU9+5aW1pvHU2N#`7Nko zmK;y}rQO9ZmwMT=jh@FG5hZ)I+d(Ce=ChCY(iWe|1Gtxl81XcyCl1hVypC_Lc6UzM zb1TH8@VwLSwwSAKmqtU8_{6NoIVl0^AAt6Y|E^D!>_WT9v8ovw_Z?w3TUU zT1+!i&7|l}{#piH3HcK8^yonsnU&q6RU@YG`D{s=%Pb=Z`c!)w6(YmO0XNBClwkg~ zC(L8|KOkt?C2178u4gSjOI-u;jAiMJRnk0qgku_|HLypk_Iaj3{O-72j8?>2x^G7? z>5#(LD!`SHFCkASS|^kGLwmI9g7pB%)B8#Xu5Fzhu*J()k5;)jm%;o=jXA)~Roy_> z&1#_B&H4U=*ppK~L;Sq)K_-9y271EuN^;(2J`Qp!%&Rb;4ujGti-A4yWliDxU*oom z37)u?Rwm*pL-8f>Zw1KVij*uEpIYG|M(NN-diI%g`tAZ1YWB+4^!C7|UbS_5$v1E2 zGVfCf8pQG}FqjPbaFWhme1Tc+U_~ZqJ8<**%P|Ws^rs{**^p?xj)saC+oK|O1evEU#Bv<`ieUB$o(OGEq01m!-QpI(A)#eYSRt0G4h$NP8vw9 z-*uDTJFwIXC2PoR*MrQ%+6{E;%MHvdzj&%W)0Ww#Xya|NRhIfbeHW7vx0VKTBD3Qn zSu&)79u}|THStprX%jS;d7(wn=^u6J9f!L}|LMi_7I(y}%x(=yotewH&LwD{7$v5& zB#jEPKTUhIDtiYt`%w~!^O#H0Bt7c#Q7TTPl9cH_Nt^Dixtv zXcd3`y%X^wwIpj{E<{)la15*ukf&KSbD>s&J>VL?R{humiSo2+2w@B7im>|@ziG_! zC7wVFezYsd&?fVb81-uRHU2#F?MnVH?PibK(`$`CS}>J)dfek*wlj)4%~UpR|D|10 z#I#=H8l+5T_J(kJwF@T_Nb5BTy~Z_69m^d|*#iyw5eI0uBwMRjJE>+*?qzwkU)mLM zB6_vUYZ$@R$+u#r=ieaRbop8ZxD)axIlY^G`I3R-SrCOP*C ze=P&9gnS8knm@mSR1~aLkdsRb_|b(itpmU`7vTfCgZnrIX!>FY^nh5(JF+3=RzVEW>hlNnp8R^*oX8_;%gP) zO30Uxr`NN6$T5R^w92i7&!p|2=T2kpuJfcpEGMS> zka{;O>6!Jrm}-AnQoYrayGuolIkZoU>de$2*Pjz-DlDfn3ue_26CQeUHT2c~RI7Ge z%Vs!9E9in@Mf6^KRcf;8eVWrX(JKrMAx+;LV1}n|psj7M&^`-1sTX^iY3ajTyfU5? zkVc8s%#a1?w49eO^Kjq<>eK=~W;9pSyKmKH^7W!h#ws(X6CfExIh~Ye zz6AC0x^37>O3cY*u-(gPFn<)+&!+4Rl$kYSDO)A%|U|KJ3K)V$!?!DU0JrKn$iy!t&yQ%hvd$kLGpUBPb4r0!A zE+#1@zE%P5g!~D4E0}nmoO^@6Rsm;@4yU01R)GhZ=MOi~1=IA&)^mIua3JJC$dl&+ zSJE?)zg7W%lVd8NAN$8mX6pL8(H_(5$>i(&wF}c zqN87rR=K!ypno@!NbUsdAIr?m+)i?XEM6x=ZQ#b|sWXUp@|E&-Rjl z<3Dk3V~hhT>GsbYa=jpakW*n^h51zU{7lX;?1|4Zb2;pvb?r8@vb2I8()xnDb%(E2 zAcxzzynQ$C(fINsLs*=lpA?RvUimH|wVtQb{qC*jzBzS?c>k6+=j+^P5Q~`7Z9;zU z3Oa4I3KN$#k8qQS;#SWe%9QRw$mhFDh|rnUbhp}BT90E&Sf;P$t_dB?Y+G@f`dV|I z+~Qk84^(`@o%Zf1ImP}U^W^9TI`6|>YJ6xh8ULt&e$v&z9UFL?C@{Fr41ZKg=c-H6 z+jJVqPi32Fjc5t4c@ipQopKwKMOV`6sR+8d`31Q$vz?w?9q2V+wL7__|82$^si47} zu<6n#Ki-^1M>K15_pb^hviv49zKk1Pq)K$uwOnk8%qRtRdv)OlEeBcB2O$ z-bc0EtS9^AhtZgUl9$rLVPuff6z1_xclvGhB{Du1A)o55rK5-Ha=ku(A;OzdxbGvT zGM!TbY4e-U$wMt?>35+Mxr>&$k*Q}ZnZhfnG?+hXcR!QrwwLHH#tXfy_j-|4Ik%b9 zt1EcC!|PRmei0*{R=E>vc*lA_gvUUh`YB|<^I@i*Y4#XEgDloa^lCT!xu(~UzHfTj1MS%T{;Cez`J^82700wN z1237>ugv-G{Y0?@U#kFjLjHujImmn?y3g>n2yk}tsvaD3?d%840>>CJO`vrIhZE%+R8dXpUj z3Y(|up_X_N>vKS?-xd_${kK_qN%ocG)=gSZIi2nCe{h@?v@cI~F$=#Pp$}N5Q0h{A z9K?>`1jLgNF|OK#X8OkIKq4ZBkAs{F^D4|IQ)xuX$@XX!9eV`!H;q!~&dGdE=gJz9 zq78hl0y&)c>^8*X?mKYjbjmU3J0B4zxQB?UU<+CzEP=_lJB#;wKZ)UdbfrNou_K>h zARhOZPNZgG9x8s($SZrN2)E>PGde{_9_Hdp^BB@!|13&N+I!8IBg;MZz=9kjAxf6; z*8GE}D&}>~CPI2!JA*{*r>{`1qzQeGs7$t?ue;fA#Yp3`7XoZI*j z_h>4i?p6iRk~Z1c2HyNA;?0jU9Df|Y(3comv5{KsxPd-xbQ$wI#KTUz($z#CGO*wk zF~i^+^|kf_Gd903@i8Tsv8UYW4;n^9L)lE?!7O>&5Pv{FzgUg$l$7_%mu+JvM-`JQ zPc+G-$^GfutF4&aBQk`jUjsAaW*!aZPv<=ol6p6q?y5z(ncbSijY4(q$!pJPwpN|} z=0=W_-U)L7_!T~c$3ULeZkfuv)+ZtY_J9dLX_cnyT4KLB*=tRXx-yCJy4_n_K>wfH zbqzeu?Uu^ur3Ikf<`xr}Yf%5)o){r5#@8yq ziI5i|Z^eG22){bM76I(S*?Q=I*n`pB$$GX-cg-sNyWpM%z`@Tv_*rhj9in>H`mt0G z$mjl&X=GiECoyB}D&kWef2{&;mhbxt?T^+T%BZxP(Oqpx#0>AAwH$UU3Tj!<$~I>8 zmP)!td;<|L-lJ9W4hazV7<7%X#Y<`Lm13B&K&zT>%5y+rb0`(AfSIziDx}F0;uUL# zP$%Y?kyo46P&4X!{NMEbBJ|H+Z#Y+Fr3JI%P8{`8jE{rZZ3jJv_)B?&J5E-eN!{`e zixAwiFxkEv0fjw&s96dTBHj~Ucwj8_f8?m!%u=rkT76^^F{PfbRW6&dkd%$C-TfosHzJK>Dex27dzE%oC* zS#QaNbzjHF-$jUVFKVb84P|tM@evF!9K@aS%!Dbp^btQea~v`Az%D8y;}&iC`UN(W zaOF}{m6%Vw3us@zB@k&2(zLP5NTy?;9d2fOmC--4iw1LIz}0A6C4B(pMioovev$)CVGvf?WQ{6 zQKRoMO(pwiFn==AQ;FrypQz166Pb>qZupYm>&$vxaGkQ}ylR%e7Cw861EoR6WIRU?QuLA{M7(;|a(f|P>m%l>Q$gX+ ziH6hOgj$~-t;%%l=Jy}>ojTaRk<@i6Li-A|s{6FRCn#)wofcH08Z524HY^NXl;*HVYzP5e$2L4_^bgZG2~MW^;MLQgV-IW42Jj)jbntK>Q-E2L@qX5 z5dVRHmeW^-fySQHV5-Q$LHYd9$dzy4i;mz z5g6WS^acMKpF)+SbLoIUf9$Kx0mAv^dfevSD7?8rmEd~EP$@%~(BiJK*aZD7a^u-B z;!4m*{N`Ys$iIJ?YUFm&V_qM}JW?mn8x}cHU{18{w#Us&rKxB$4{BTdcGUg}jhc

QkarH%qg5b>V+S?DT(I*gJO=Xg*tRd! zDzFDk{u8ZQa@qB7Y1Q9t$M)mDQJ?wp;otJ_zcJR|vC%j_4wn;X72xD&UdDXChF1vo zcmVtM@DTKW-6C&~`BwVwV~2mnRtdC<_E$hbVUH`9rG}@owCa(jF2n~tSN2rB-{CHs zE>5TlvmQW)}vM1 zzeGa3uIVLu@!D}jw{j7xB+x3Gq*f15*!~S%9fc~hv}(K92Z+C=ZP7Xr8RUFO3{`!y zN2~N5PeT7BH*fL`@n7IE@?$i$LZDS3cBbkE#Gh`M&3RdbdCuxH2g3yM@A+8E0fjyO z>%J<4w^&bnZjNEl|Dj$wsK({@95XdVf+Nr>kG62mySzAv9~hR8O}r6>uKhq@>Z)sT zjWGkLdkZV5My?ho5=0x=@{yEUsnhsLw;Y4)q2r19DnCtH2KiHy*98DPW3n_JK za?l!dPrw-@X+;j_uBJF%M@kZs`^Heq=Y`T13-)3K`ts;J$2N|Gcsr*~aWTw=$J+Nq zq_qb6Cf6C8preS|&0B^``zaIpc3G71neCKkDSlP7N2~hX(1Unqu#D%DOSjzj zKEY9WfmSUF4D$em?H{bOwCZQfpeC9MWNB6I(%I0zJJ01^(^s@RKN%lQ*$cD^#9nkV z2I9lCZ@AtlD|6|V=3owj_`S-faX?{@u`u;9-Xz)+U+6LZx(qB%^>Dg5&E0HyJ6?B^ ze_mxcUX3$@Hi7XeRKi!5Cn-bpxvarQnr z0{NB^v&K}Y;hdz52_CKCPVQ_Uey)QU2kDpLd!7VS;la(6#}zGT&NU^PG3Pd_EbE*`=4^cXe&3LpgV;N{K$$l{KwyN;$h- z70Qh9f=Bo8m*{$GK4%=QSmA-)Enek0=++0uJSV%aIOPwhBvHw!q_B8s&a_ zc?>6wr{f?t(Q zH|zhFR{h;}Y(M@R_2{qoFY*01?Ea@Vj#)Y4djwhqIQf~E0gIAxU4d2s%qvSB`ky!P zlzrsGY&-Lke9TXvRb6W)AfT{)c}E<>f>>HL^ymVJe-G|-ZtSdfstMkM2MM$aa3isN z2(+&`BhR&;VU&xy+)+H7rBz4vor3tgpp&k)mD^lWEl=XnEUn7cz6kO00e9Tm`|fr1 zJg1Gy3bZP5ub~GhZ2w^WH(J%7j;4%QS~W0R2im{%T-mYneuw?H?kMV`2>-kb#GW_?oXFTMJ6!mT=MlG9>j|NmaISibjU=POeJeL9a zR_gx|9jz>cT?a9KJeh(Yu;@o!MzSdX(W|h`r{azqO0L@57aC%s zyF>9fX)UT`X&e<{x*J{8e#po`bM^`W>#@yKnblv>e zisPrC3;V4f96@{`7ReQ zf36=rj(4R8^RCd_(cbUXF{SdWZa2(ITz}T8q>sN|ZxS8@d6I4%0<{Y40XO`KRi}2ta3dEpD^XNhpjB_K+;j(p z?H{cFMyn=vMp181^y~pOrd@~eH@C&vM!Nai*zAm=t_ZXW#O^!sHN@=)_}Y!RVr;u{ z_E*$MpjBmu8$Cf`k3S$Z5BFke)zX|K=)b$MysL?On{&+OY#bA4Rlg_Ep5gm8K|Gk7 z=$hRe=+ev?jr#d%z;mq=dsL~%bK_8#iG?-+nsL@(95bsZ$oFEcZER|h^&ug=%RMeJG2e0^II=h$IMws-o!9DMxo$7x0^CD4dTi|f&;lL#H zg5e;$>#I#{-@W|<^>$l?yN-QJzPc4ftq(3jBfj>x|MJz)ZiC|{>_KW4zI5$K>dM$} zl;QnH=wh9*Oan>|3~EDO53O=c-I?YvP09oDkSu^PbN0+7+gC}T zJ9cezy|i?h%jol(*zSv)@z+OWsWa-$)TPx1*g>@+E~k{toHK5AcrIEZ2K$|R%Mf-x zTahV}D?AQtALyQ0m54pB$i;2;Q`8|uoN7vWj#?yscOJ2z(Fx3-*W2^)pzmJP-YiLa zu-z+EN0RH>m!s?QvsV4tS{5Dyd3t;v{C_?FbHKF!gIcw&y}p+g{5((or{5=?7r~$G1TH(Wi}_vgU|7zUxfDHCS3@=Qs$?zoV}a zC+Avq$6+UvaXpq+^&dD2;*yWwIZGU>cWVE92aytJ)rpPS?x3*!gZ1BNRYD<8i&$C} zK5{$sFQmxJ>TR5!WzgIx>YzZYKc9HlqD6qZ7!M|rfxkj^|__0XB8T}#19Xi zdX=2jy&^iuwU1bXSeNiAEam^|GLT2=>)mH@IJCH?Thm}#RjMTmiN$Qm!t93 z&hJQXmtqRJ-UoAX+2NF&678sMROERrlK(lU`(+)%^=uS!%FxCA(rQJwD~ASS8kUK; zqp1~D?9@VTTyPqF_0`wuM=nI_~tdR{hyp z79In6x|1yWV-J}Ae^9GBr8mR5{PXiPoc-z?=}<~4=pW&v~* zl`GIH5PM(oH4uM&K*u6x-3PPJ=y6m_5dYA1fu5kS$G7;t6n`($6W=ZuJ?Q_1tFcad z){b(d>%e&}|9X|aw4!HnojJriFMe`bbK{KT*_bY*Mg0*xHWH~LxhuJ-xK*qfvUnu# zU(Q5C)>?95K54};w0ied&vG+STaCO6@SKb72fH55i?Imh;DhXCDOcKrY7SAr+@3P# zb;i18!F`sY_FDXV4$A{&shO>VP+3=fi`%iE&928Lpo|6wPyBwEoa$&t&0Z9O4s+4A z9HjrwJkH%5tLi@=H&QjD(%%(R?{o^#7PYrd%3gJjU`}*u@&CqX*}NyJKJP>vt>x_= zF>N+_Pfu~gE-!=e$%p8Y?xX6Fg=o0b35NlWNrk)6()j7P%j0v84^Ik2P>a z^S;^_IE8ysiTv||!osik)eVyo=b@@@`!$}rgmH8+qq|G+ex`k?fg%;8Q_e0_U+R>D zZ%(#7m_Jgxm*LEGE$SWbKH%-2cca_P_B##GU+?&{R{hyp79In63TbYk0k2>Wxat3( zR+>3TDd9-;%@DWZ0d$^t&bE9!W#uz6?9`ZYV*fRXy5&qo4w6eWjm=G z$#ES{1SK4~VzT%y*1@9_4V&r4*46Xw{_#cXv?O z{=xchwCag(6t$D3RZCB!(7x5xbQ8*GzVQ(KC@NK;RUr10iNhgY7d6o|%gx!O>Dn&d zJrn#rWSXI!Cn)Uk2e}5|w^&-WFXRpEzu?<7OXGxNc?Y zZaC(-mpk|ohmeuhjqup+eg)*0O*Ux!z2nBYt22ziIM-bG_r7LH4M&;qBcAoi)mF1Z z(ka6kc`$H|m+lIJ$Kpw()@ zrs=ocOwLXngC2Zjiocv$NcJyNrdGcmfodlPm__6}nyPLV!J4p9xcMiRC?CWE1X|@>m4tx8Hl&!Y zifD#83OO0P9|w<}Y>W3L5fBft%UhH*e)_n-HK!JJrHRd_cL;>{1ctZu{{ zwd7=b;)euUwd@{-R*UnmmrY;!-1gbmqc%}?-grGrt2|a6hvV;Ykh0tBd)=nByFY%G zrB$Nq&Op3eHP7MLtw{Sl^SAxbs)dD0?x3*!gZ1BN)h+8NY7by9#UW!y=E(_CWya0K-v=&_V}xVeDG5&t=cPH3;WOX zp6&2_7G{6QhQ?0_v}*K?GLIqin;>3ruhl{S@oxLE7t#>%J5_KkKcAIEuFTTpiK&~R zqlmHr7-!FX9hgt=BTi`e^J34#!wW5f72?dR zE*Ut=X`|hG-{E-kYEF|#q5x`PePo5ZRaqL3 zTEr=@@qyT*j61}UPZ`J@-;?$)^S0SNEEtJCn}*;Wd-jp4tplj`O}o*98T0JY)Ja=| zFfGsXsacS#fU)~=_p{DO_y;+ciLdWD9lv%0wO{3pml(YwKbWp0-}pzPUD0ZGW#U~n zVE)(^_rp29@5z*sNXknl7F`#U?NBVSg7;a)-`6UTr}MiqXuvJ_9B}JDTJ`fZ`?udy z^LJt+1zHtXJrx0kZG{wcL)Y}c>ABu>#}Bf!D*R&}#N{Vf*e}#cw%dC(5fK-hS4}SMa07+yAFTgItClH7 zQEORRwP(XC82{0%T>YslBlXOlL{duyS_NVkFO!7hN3Wk};I2UGN8kyliXeWM;M*Rc zu*ZMvJEMKJ2)d?C_9A2MxQdw}2|v1>@|!DhK^7_0d8b+O~QoJghX6ewqsRhipICbr9oO zM@gtv<{jrq=EzahAKR*LnzmT)!1#}dad8J6Z~Eumq=KI!I>>p6LH>9z{V`p#X!KnP z*l$Tu3z;?B5`FMsj^VI_xIv6jA?mr<9=|-Lo6P&_N1@DQw6FhT`@7zU?7*C;JFX3F z(r>pA1&wo%jbBzwIQq7`2qY%RaZ*+krX=Mj4;8FKX?A`@?!VTAaO(sx!m{MpeZHw&$ZaKRsY z-XhH$1ISzLGtr?gH*Bt-EVBml=kaVeJn_ywa@)y3D(>nWH2i3(y@PWC?;79V*D8>w za`8AC@Cx>T8UJV%dy4%>b)|V*u)zYYx_t*nKw%qtTJi=97ibZ{<~E#wxLTy4&Is4< zT4LU^n5h6a^poX)!tURcXM|a?v}zB*|IGecjJZYFEOGO#&$aLjfmXd8`4#DZKpM`k zjPEJdR*h4w992y59W1T-Iw=z3-kSN=9;H!Mo3t$OLY7v^Z;gX^8@%OC&W%pj9CDGNro^e=^5H z&&X6l*DZbpsw{{CgebYTY z%~}WXJ~DFlmzUhN{d~|Hk-M`MuJf|zqsYad-y_Lc={f_?%+m(rv~s?~e5joiN5hsT zdcLfCXkyW)%9!oL&vg*vn#6a|rudNx(#p96^iCP#OP zpnFnm^cfI232)RWrkG6+>T zD{WtXZuKt)s}KnL24%GILE%h#bd*C^V&mwjk;H}{;!{N+)Qt2?=Caoe{lNOKkGq_9!T z(SeRa(?^)Xehv32vRiWmntrXq+B+=S3d|o%6>~iGNF#ar1DE%m5u?z6Lrr!oHWt|a zJ6Z+ubp7T`8gL8tfZP7js-LIXzx{q+L?YHtpj8H11_&r@BhRL^VG9LX1TeJ`c@X!g z7uPbKcU6O0^bIu<;6@@Ho}jS%ryNql%voBsxc>);M~6Hz6HBQwJ@QH(PZnquH{>dE z-|i8#?_aaZO7+|z%h5I(_-2+?Szhvo^KYmAdaJ9Ordys_sfBN4Y1Mc2K!`u06K${y zf!1GE%tnM-HN<+m8z^l5VEs2*)$kyaTEx<-9fgI^ze__f?OWISY7MWCq=E%n1!DKk zu7>#92QoTj&TXx}4l{Z8&GWBU5$`v9fWjWXF;@fM!qTc3dMxyxqmyTKrajW~R)i{^ zCeSKXlJ@vCaVo^my`(vJInvPKgb;?Rr1=AVW!Kqc1HKp1mk>J@f2#6 z(&ZuF0N)HWsNseR!5{ zOkE~8Xn@8-+XpJN4VV)&!@mxv)^9e!UE_`9^top?c-B5ssXG!JwFnJp^S0?mB0O$7 zGU3W7n+mRtH8Jr8l8Gxqo5fO6)CY-h^w8?BRvQ}|;Ptq^4xKO;uHKI^qw$fP+P7^E zVjn8)0mrG+`1g4X+GI^$*P&37lWpac9AOFOk8djfe%#l|w$#V-G#VNlZIh5aocDRe z-#@Pcc`E4Qe@86X17`iBRXPGa;6#LryiA=dKx5}$ZQkCV+2~Ih3!XT z$J9dm6AYGG`f7Pw7|O}vi7c(kz2gqo>dmDImJ`=av{18Az>`^8W!i;7ygJs+%6_)8 zrPAK9NFTu-@YvuuH&EFA!TN8ss_k+lHH)QH=Vg+ie>LOWG|%-{)~GrVNd*YB3dEk= zCmZ6aapGE!q_1kMukc3|1o6MTu*w4z_V}jDc=55cs#D1m`meWXo8=#)mRX$KD22xh zw2I?m;xXQefw;7Ys&!Gu6U&nW zcoDf&IM?%gYl7iZu$|`CRvC>A7uO@JviC#G z>*icib;Na~-djcs&AX+sxH=DsPj80U+&5{YTfkRD>uDctOXVw?mTPGA`epvUvf`Z* zh6M(mRMJkI28 z{q6Uc8`=SmjTkxDkE^Rm-dA!&$I5Q8v@8j+0P`nvxIDzPTeYZ#8=cVVT6-%8F*%<8 z{e7(hd9qCNpaHL7519RrR z_$vN+%gLQ9EZhuy&5b6C;!!NEnv$jq=k%?Z6bn1asphuB`rt7vt@>JK1aa@}UoHAP zYA~m zh}Q(FXryiFRF{wLkIDW^@=*yZ(t^DTQ3T+4nb8f5jI<48bgta@7aNHs7{755y> z=liZ|B>HGTuiUWxVAny6Pmb{K6@OnBMqc<3iDn)k`mU(gR3Eo#8PZ>@7>+k3Y81Kk zDyBYF5^yDm(g$rJA8NO@BbF(wui6={jBX zOv-4>oa;6gU`_-Ly9?uPZ$$~)XO_qU)kKqrF@eTYLr-{K>Bm3kYVw~=7^Qk6$D2l5 ziOAVn#=k2?3dZpFs!zsUC(kb$fsQ@>heg@;v+%P6`Dp%r{lT0nOjBb5$K!RqZCXmU z4dA#qMHK4M<18t1!B}H7WJ#vQu*1vE!Tc#-%fDakxS0$kbI=4mQ}Mw;bp`qgCuF_8--Q4ot#00ASe6&Vn>T^(T@?=sLRciaPPkwB}qrNtqo zU8&H%+VTYRXGs&y)*X3;g|W11;1K?`>yHvy<`z4`%%Y+|sw$G2z|yLfPQ#&p1;$I&p6Gb1wr59D!vtCdV#hbl zfcVN(SM_f@mDHYjpg**#-2?Lgg+0E_;@4OhORIdWrJ(;Kn#;@;6%x&+B|gJK1zOdC zyl}6W!@EYqdu;L?V6oiJ$UJ^`6DRFJFw_8D5oz-Cuzkpgl?AFxx5TJ`cHOl#FrO>d z6(M?W2lvVi!2j$4Vw}-^0NU)@IGH?bIUiNfpQA=-;;L4eqmk>cvmn;Log$G#)rjUn zR6Q)PuUfGGd}NCqzu&tz^GH{P8_4(XE`42HmDD%BRX}}wp2B_`T%M3My2@xtjJJh} zhrT(O6MffTgmF2ow<0b~)I%zQmKrAy_BOK4+v(Zr`wC*B{YDbq_dF2wf;pC6F}OwW zf=$S{G5mYW4ky=yVN8YH> zzz?to%>75J02TjFk2MV=Fn57gjh$G^0flYw-K<^MXn__1j4E@4`FH%fP&uiAR&w0U zyVp^G7psMPg2L_}YkU{gV`){+$T)~U@|$bqr$`t!`nOTMx4tvebjOrn=jC+xpywOH`jcC z_`|wj^O#xgW)@%faS{vo_fcNa{6L6?6e2U32g=Wz4k&?f6mIW<`K-IW1ySvydgX@g z2fGepyqi-3*Q?5d!Q_kY6VQG}`6_cNqm-*JAjmh{jSwp$9LPKU%8;QOBUM)(8lqB- z4@07E?SfdK#8`5}$Oh!iz)-dKQa-A0y51wNi*7;eZOs+3-b@nh*A-~K-QUFw%!!ah z{=FK}@3e?=PgO*_X}Xbm0msn%SgPj;#rsgJ?rfrn6Sr-UIOla13!($e%PpgkV-DA# zO{rshNpm%KwDXyPIo0nQ{G9VuL^b(i>T zEr*_eTW^-XKHC(`ALsr2??U<3-9}mrmPaKOHO*xzUh!=HeXRm{ni42U175)%Fz+9& zVo$OEsJ?xAKg>m-Rfp`ha6n-jQkkEF4Hsw;z#ho*@7M9rtWx~AJWau1@P1TNfTzd> zc!I+259=(g`WeG@YI%E1{vL33$Y2B%cAKxhm$6wat?Jx$8sb@l3r#g-<4k7mxrWVU zX_eHiOAx=0Qf8f>wM-xTn*Y$M)BV-mKw;a5_1|b!d0r&t$I>c|bNtU97D|aI@4w!t z*e@}X>L<`D5PMpb6C5AYs!{$@x=HE0k{R!Rr}@{`y?hltKw*zR#^nk&o26B*vHb68 zshxk()cy5Nlle!RdDjd1dqAsi``tGWyanx#Se0y6H6_?o;Yk)p&u{=-cds+riT3xI zNalMxrTc-hieQ|_zB^$)pH5Cd#s#?jnj5w=f;xy%CnXo!+#rLIv%U>MO{TtA>eM@> zc%#__XJZEO9?&DbD;76DB1y%fy<+i#i*Yn;ZI~}Fmmmd^XAJh4vRo#dFa0i7wet(MhojNS7a=O7k z7dSt~(WG^lys>l232c%;tE3j>xVzi%zf4YtCy>5uJCG+ZObYJ>lMXM51 z1?6lda43+GHYb42ff#1`E@aqq_= z(NGMz|A_y6YFI`xIeLQ@%5+4SG{p^s_hhU-whiW@BO@H^vbW(ldQG%Q5_2{O9QS*F z3i`6o_5hLN@DeFevo?9jl`;nNr*irkh;6s1B7?MEAsnekCZ9OqK99eDUIp?b?$ZLb z3hV(3{?V!*dHYW*ABQ>f{*jgcy`W*T^EjZeZ3Tv;VEqMJ1h5_RUqO7(-FP{luj6HF zKjxz90=#~VttTk#{;>WVt(u%shK&(um3OKU0t&l-gmw)U$kHm3%b$OKQqe~H<%Sy; zpE-&Jv9yZmn*;mr+q}>?WjSrs7O3_^t3rG_TtQ*mhxOlR)y(yg)Bu)N5&J&E_@&<; zlfP2GRc_LXNQx9_6^Q+inJgTC0-2~_*AOW09;k}S2;w)S?z)4*9=|=07avQjbUiX* z|8p66Mpyes8GbV7{a=AVt6YXAyI=I(0r4pj$|jd=JB*qxFXe;`jC9~^o?@M z?|$Rdk6i|_)3_u#^Li>Wf5!%e6%w=L<-aH(t9PeBZ09fwvhU_1?MMD+A_K|k_>?~LIO9t1=?v)Jr!8>lPhS?s zTOg@wd*FEaVbP?-IwiC$w$tcXpTmZrgL_~aJ-p9aG^_)j|)8LVoej}>Mk(5lpeksMH1ut=WuTFhIZMF8`Sy9V);E8?;h zaaX1D_woLRM1ZHaXnBId?hos~(W*C%1=t9IR+WS*A)v7PPmAA+jbmw5TE#kupVSsL z%uKzY-yqMs_RrF)_&5A}3Vdwe8_s@NZ$KWD`=M2H#4osl!nP0VztJkOb&-@0ORKiN zyaVH3X^hA<=%~wnTN+7W0<8kE-(LA1;`a9>3!HZ zmR5GK|ExkDOtwL#uer$0 zQ&p0+p7MaRsAM*Oo$GugTE*j$985v}`^R=!seWHMBmCDw%yO4D*$}uL(YzOk;q0KK<^O?<`dfWT6c0Ob8GM;UFd)f2hje9AG?ltcBn+up} z_o-g(tZr@~&(?hS5f>O+Dt$ed8aadayMH}#bnx1VYMb|JS9~UgTeN6uuXc&Nc86A7 z=+*9;&w8$T>vU#`!cj6`lYd?XxD)ax~iD=>H!bCCsMkczR7j z9J#kyfCH`sdtQ(Xr1-+t&IGsr%T z9<`>Kh!yN zqhqs)&I>$A?J*MIyx1S?DTMg0q?62x)@}4i7hiHm5+4US73Ni#Pl*s;D$AuOzG_## z|1E0|GTxGDbbUBU<-8Q!v%t$?%vxg@-@T&K%)YMew8srmI$h!@xlHv0J=8_Y>*HN7 za%IVUrr`xigIJ1r@ntPqN{_oH$xNmLhzE@+Tyfs-PVMDk15e};S1;_N7u`HWORUf! zmba#G$0tl>mhpHULLd|qewBVbeX&>K1m15Oi|l5uHOAAXbJx(_$Hl0s5m)KBON5uq zgrVf__`{3|x`__nQAnG;G^V=7*U>d?tG%Md6Ul`;PBNBHw$aIIy3G64B(*YAg<1am zs@H+EGP14lAQMTX(O^!D;>{z~@+|sFO#ydO;R~Ym%6#UY5=Bo?h@v+0=Fq;~(RB5S zQ{08BO5|Rd1X0bSPBHskHIJ40yRZ@*&DTY4s$2@S9?FV zBiDXorse!!M6YqX{5WLX-DSPBr6RwA`(|h|oTtKAK)ZZmXRqJV z@SIo9O-o4prQO7BLwnil@T%Y%_DNI&C&dcAV$qfCC{9LZ0G}jv-eh_pJ3V+I&6-HqK$@@4)G6oiE5cclc`+ za5M8V-~OHOxr~-0L6^qKP?Lju*7AmgbjVdjdnsc%G=X+;P@o2z_Gnf9#4LzU2spzG zT#-Y+?YD(E9J|6Ba0a*%@+IWy5x~8BejhMWz~{te<=l$2L?+9#hRV9r+1n@A&_xkS7t*+lBIg|EoW0lDYwe>l3|%y;>8|L)q#4~tD!GK*lA@8MP)f+C zh>+BDzf7sf(3B==QjwaFrvKji9Mfj{e~;(+dcN;G&w6b8U3=~I{@!>0&f5E|y?!s@ zupl4$tEB+5zBDoZ`Q=T(UTZUP;-8vugI})fa%>ds&Dib-E{E1c98Pug#2tmj;Pv`k zSzJOC?VGHO-`{DX@|G1~FucFRou8gitL=(u%Qri*%}sUudRh@hwC7;Iq(+%{Vkfk! zxkg9iW?(NfbKG42h|2WYhpFkt4w=_1sboe^Xal1swEVk0`0!jcG;De^K4}*0P-6ZK z)js`>OE_Ux3$LW%e3+Q-IeGI^ZKJN6#%a1X9x4t9Ioc+112}mxT5UDJ0uvpo;;WC>vXW{HXLoy6OVcHJczn%3s(;^)T$Sm( zi?d(-N_+4$)boP!MMmJiAI)72lRZ2_@uk=mj>{CwPq|wz(tbJtxJZ>BU z>36F5sfju5a%B}riSed4W{{Zm#q-=wE{{AlGvT6AKsdxUg5}!98vB&L*87 z1RahIBkI1*gFoxVfEi8=Fn3-Rd^LD9{=qVa?qjwDCT|@kv#xE!*Iu+h$LYneeDxrF z;q7XA?4B@S{HuX1zTp*~@2O1{KB$3pHfp$STQdFi_AD>}naK`%snFFE_fl6IwWyPK)|WZ zD8Fz!vX=Og9v+3r^>{p0aUzyl9>(B04B{hWhtO}@-+&faOUI7S!)*T869cH27cy+; zKU6lUqZ81l?$Sz^j$l4lu{pFLV+|Jzem-^WW8)N>FLhvT>^)%CC%9_ktZ%zsh@@tjF#%lUtjte52=hD9AwpeXVB0SF12TTl4;OvVJKcj=g zOYpJ%yWt(4KH!+Ry_|i3c_V%AWD$Hf)`9HGOO=gs5L7$_-&`1?I7qPxCSfIh3|6mi8ZD}^3cM0B; zfr{sKB1a{HE&TI0mhFONJbl0`PCs$SXWeR~O=ZQHa7ln!3VlF89F$h;$;Z+D;f-{M zS~1>Uy$l!}(}7W^^U%mIL-B&@GBV~zZ#rWKiCJG7T(mg6m;F)+#tuo74z4bjteLc1 zd}|Obd6o4C*Ox@oR1oYQDpi>lCsl1+B5^Ey4m1XOK_9(4$n}pp?0)kE&@lK$rj~6< z2TyAQ9#1F2fo^A!`~5O(YIlPyZJHwcd&U>EZFWCsJ76g+nxBVmt#8K3Py2vEWm49@ zTc6(Rn*o)zcfypSe)zA>rL@5i2r5mV(z4)U%;v=XKO(^D-=`vj{>;L{724!Oqx~r8 z;b!Xkti|M(^J!pX1BvRVK1J@1Hl+E`6s$8Z0KH!{9DW}b37=&hLH=_@bo#HMVCk%d zbd$dTD+!E<8dX&;R^u&xq0-xnh$D+@$&x8~gqOiCm~<-737*lw zvIMjF<7%4(b6zWBH^bF*!M#Z!!nKj!S5<_2a#i#&xt1e8o?ZLcIIYl%m$5eXcfjmV zaMkOd3VX#Zy{*@BvUge&y{-2+d096u<@@>nvE7hb1Xd|Dw^&Si;-YtT2%MuBH?cNy zihP`@wagZ3j_L>AXfWCmif8LA20d{|UpE`yi)V(R+$$YRlCNbvLlpA3+H7o3sl z(aGZHC}Ie^)}8zw(7|Y~m@Ryq7l)i640v4SK5a8+H(t^#%9~a$EVvnpmhyDl2J24|jFnbOa_DQs=(UgI}qwO75hG1 zO!kSHj*_7zHEV=7Sg_CtZa5x}w)h5Pm4s;Ww)QaGG^mXVOX~}>Hcy1v!h2}Q@%MO7 z7dhA2_;QkgASWs zCN547hyB840at5AYfO)Xt$tfji`8ZPq+}2X`ZFIZn{Go_^)%4C%7sgo9-1?^|l_rAD?48 zRayQ{)aPjHbF^Wo8VU3iT*YG26PL+*eg~%&<0jU2&{jV8#4WyDe5n6!d%YrNy>k#?hSJ&Q&b2$QUHv)YKPRD7cEnCUb@+7yp7aT2iyEb>gr6 z_2CTJ$9p8t-$FV2z;_gR@1h}bLe&U*^7H{iN~1XY0VQWJe76Z1c5(GT`hdU0*4VR! z-@jG+f(gDp;EH4KIQ!RAGXyJFh1ltA4?&lBIyM&s@^^EqRQ04$x0}S&;1Cq4EFV{~ zz7JNsN8*0%sq~1}DT&T?UlJ(%SE;@vVGIBKv$hR^v-^DX&-GOp*Z-?SHLMu`RkWYu1OI{pR6SKY) zs>;_{ou#e<)^6@E6&36en3k?*bPkl1+|n*{ym{a>Ns1E|NZu*E7IX^E+Oa+!c=-@G zs)wOB4A<0Izb9QEt4IShE5&0DRgp6!XMx9tCCI8k6%!i`$-6ep@A|FBB?Wt0$^QBE zz^~jL^=Ti2e=n7iI#mw|t?c7c=C>Sh!DA5AkIhHEwrN=RXb8D?Oe+WstsvQ)*c2y! z2f1Z(Gc|?uB>dW~B(WDX#hd1d2;DaMx-W5YMrfdWBynlZIf59HBK2(?0O%ZTnAkcO zeS0bx|IExa`0?T#az~{NF-q^LWVDKWZbeC4Cw$moNc`TX!``LOMUk{`7^uIX57Q6q zL%G$N$g3lq%qd<>wv4kU*!)qyYyiC$WuP$W5nK`(Nw#eig7coNW=VIh>Q^<7iv>TP zUHjNLc?MkNo++{SfVrRGs%!TeyIm{%cKF-9t;av+b8NrXm#;PSIokRhZ8RFq1~rN~ z3oIr*acP{F2Rsy9#o7vVS90G4eN4>+%Om_ES{_$T zS|wlqs^7#_qKp)o@N|9+IHAynV6pja-T|)tz7Nvv_Zx+X2CFxLYMwU4h&}SPzTe8M z#PK3c`;Iv+;1N$70tT9M`BVDMJMpf8m+ale6!Siwd|c&WywRR5{Qhy>I0QTqlcZeA^J-3@6aQrS9*(S6GqR$EHb&GalJ^3@>2T|@cf}FkXq4ipMq_56=VRZUBGtDcMe* zT$>L*OdE%EbsN#ia~fplJJQ~E@emQtwFa`v2JqqW4)p5V#W>(Aid+}6RT8B(SIp)_ zg}r=E`@{1Gsi1IcLetVqr2dUqxN_5Nss2>?yL*$oZc~P#%w zb>O5MME?6Fc%<1D@;CPY@djNd`-QS_iEIt#VllHM2Sz-4BJ~F*qOtFlgyfr1KzBnE zAeiykxDp@K7C(Wscu9+k#|*P)^T#ygB^VVPf%Z=L9-nA)B%e7w5x>+ZV)CXtSFNDs z>qhXA?Api1snuf}$5re-VBROV>Wf`J^@^){TaVw5&#|rFE`J~5bF}q2+K8Lau1hI@ zw$5VG6PGJ>alll;Rjf^aoiP`GIM3MT;<%2n`tzA}MHO6SzSUF87Jk37e3^9`cw7}3 z_9JI+FjNoTj2HX3<%|M)z@nBwMtqmeim*M z#R?n*=Rq`&tHR#Y$oto3DZ0|GA(Upb^Ty>|)%vrEJzMzwj zwKDX;Jd@aRIm%{b)&h=?q}lShvtcVbh!i6$Ns(@}^&I1c%(`*^{loegDUJt{+fJzM z&;4l7`Vi8p#LYG$(ZXiz7Z#-L!42ToEyK_s?vdz>FpmgN8aG}&SKHQOG$gea#sl?- zx+pjx8|AuI61$DZ+37oLkMCIjkW2_F1#kSUyCSSYZ z`@*%mGim*j! zLxl}mpJYG|udfgqG$sq!{GrRvfrFV#(GFDs?$8=aD(9Gpgfo?y_Z_-()rgCETrBuV gcI{*1G-tm%$5qBmg}<1RrNX>Npr}0itFt5i1HUFL=l}o! literal 0 HcmV?d00001 diff --git a/tests/outfile/test_output.cpp b/tests/outfile/test_output.cpp new file mode 100644 index 000000000..f4c22dbb3 --- /dev/null +++ b/tests/outfile/test_output.cpp @@ -0,0 +1,359 @@ +/* + * test_output.cpp + * + * Created: 11/2/2017 + * Author: Michael E. Tryby + * US EPA - ORD/NRMRL + * + * Unit testing for SWMM outputapi using Boost Test. + */ + +// NOTE: Travis installs libboost test version 1.5.4 +//#define BOOST_TEST_DYN_LINK + +#define BOOST_TEST_MODULE "output" + + +#include +#include +#include +#include + +#include + +#include "swmm_output.h" + + +// NOTE: Reference data for the unit tests is currently tied to SWMM 5.1.7 +#define DATA_PATH "./Example1.out" + +using namespace std; + +// Custom test to check the minimum number of correct decimal digits between +// the test and the ref vectors. +boost::test_tools::predicate_result check_cdd(std::vector& test, + std::vector& ref, long cdd_tol) +{ + float tmp, min_cdd = 10.0f; + + // TODO: What is the vectors aren't the same length? + + std::vector::iterator test_it; + std::vector::iterator ref_it; + + for (test_it = test.begin(); test_it < test.end(); ++test_it) { + for (ref_it = ref.begin(); ref_it < ref.end(); ++ref_it) { + + if (*test_it != *ref_it) { + // Compute log absolute error + tmp = abs(*test_it - *ref_it); + if (tmp < 1.0e-7) + tmp = 1.0e-7f; + + else if (tmp > 2.0) + tmp = 1.0f; + + tmp = - log10f(tmp); + if (tmp < 0.0) + tmp = 0.0f; + + if (tmp < min_cdd) + min_cdd = tmp; + } + } + } + + return floor(min_cdd) <= cdd_tol; +} + +boost::test_tools::predicate_result check_string(std::string test, std::string ref) +{ + if (ref.compare(test) == 0) + return true; + else + return false; +} + +BOOST_AUTO_TEST_SUITE (test_output_auto) + +BOOST_AUTO_TEST_CASE(InitTest) { + SMO_Handle p_handle = NULL; + + int error = SMO_init(&p_handle); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(p_handle != NULL); + + SMO_close(&p_handle); +} + +BOOST_AUTO_TEST_CASE(CloseTest) { + SMO_Handle p_handle = NULL; + SMO_init(&p_handle); + + int error = SMO_close(&p_handle); + BOOST_REQUIRE(error == 0); + BOOST_CHECK(p_handle == NULL); +} + +BOOST_AUTO_TEST_CASE(InitOpenCloseTest) { + std::string path = std::string(DATA_PATH); + SMO_Handle p_handle = NULL; + SMO_init(&p_handle); + + int error = SMO_open(p_handle, path.c_str()); + BOOST_REQUIRE(error == 0); + + SMO_close(&p_handle); +} + +BOOST_AUTO_TEST_SUITE_END() + +struct Fixture{ + Fixture() { + std::string path = std::string(DATA_PATH); + + error = SMO_init(&p_handle); + SMO_clearError(p_handle); + error = SMO_open(p_handle, path.c_str()); + + array = NULL; + array_dim = 0; + } + ~Fixture() { + SMO_free((void**)&array); + error = SMO_close(&p_handle); + } + + std::string path; + int error; + SMO_Handle p_handle; + + float* array; + int array_dim; +}; + +BOOST_AUTO_TEST_SUITE(test_output_fixture) + +BOOST_FIXTURE_TEST_CASE(test_getVersion, Fixture) { + int version; + + error = SMO_getVersion(p_handle, &version); + BOOST_REQUIRE(error == 0); + + BOOST_CHECK_EQUAL(51000, version); +} + +BOOST_FIXTURE_TEST_CASE(test_getProjectSize, Fixture) { + int* i_array = NULL; + + error = SMO_getProjectSize(p_handle, &i_array, &array_dim); + BOOST_REQUIRE(error == 0); + + std::vector test; + test.assign(i_array, i_array + array_dim); + + // subcatchs, nodes, links, pollutants + const int ref_dim = 5; + int ref_array[ref_dim] = {8,14,13,1,2}; + + std::vector ref; + ref.assign(ref_array, ref_array + ref_dim); + + BOOST_CHECK_EQUAL_COLLECTIONS(ref.begin(), ref.end(), test.begin(), test.end()); + + SMO_free((void**)&i_array); +} + +BOOST_FIXTURE_TEST_CASE(test_getFlowUnits, Fixture) { + int units = -1; + + error = SMO_getFlowUnits(p_handle, &units); + BOOST_REQUIRE(error == 0); + BOOST_CHECK_EQUAL(0, units); +} + +BOOST_FIXTURE_TEST_CASE(test_getPollutantUnits, Fixture) { + int* i_array = NULL; + + error = SMO_getPollutantUnits(p_handle, &i_array, &array_dim); + BOOST_REQUIRE(error == 0); + + std::vector test; + test.assign(i_array, i_array + array_dim); + + const int ref_dim = 2; + int ref_array[ref_dim] = {0, 1}; + + std::vector ref; + ref.assign(ref_array, ref_array + ref_dim); + + BOOST_CHECK_EQUAL_COLLECTIONS(ref.begin(), ref.end(), test.begin(), test.end()); + + SMO_free((void**)&i_array); + BOOST_CHECK(i_array == NULL); +} + +BOOST_FIXTURE_TEST_CASE(test_getStartDate, Fixture) { + double date = -1; + + error = SMO_getStartDate(p_handle, &date); + BOOST_REQUIRE(error == 0); + + BOOST_CHECK_EQUAL(35796., date); +} + +BOOST_FIXTURE_TEST_CASE(test_getTimes, Fixture) { + int time = -1; + + error = SMO_getTimes(p_handle, SMO_reportStep, &time); + BOOST_REQUIRE(error == 0); + + BOOST_CHECK_EQUAL(3600, time); + + error = SMO_getTimes(p_handle, SMO_numPeriods, &time); + BOOST_REQUIRE(error == 0); + + BOOST_CHECK_EQUAL(36, time); +} + +BOOST_FIXTURE_TEST_CASE(test_getElementName, Fixture) { + char* c_array = NULL; + int index = 1; + + error = SMO_getElementName(p_handle, SMO_node, index, &c_array, &array_dim); + BOOST_REQUIRE(error == 0); + + std::string test(c_array); + std::string ref("10"); + BOOST_CHECK(check_string(test, ref)); + + SMO_free((void**)&c_array); +} + +BOOST_FIXTURE_TEST_CASE(test_getSubcatchSeries, Fixture) { + + error = SMO_getSubcatchSeries(p_handle, 1, SMO_runoff_rate, 0, 10, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 10; + float ref_array[ref_dim] = {0.0f, + 1.2438242f, + 2.5639679f, + 4.524055f, + 2.5115132f, + 0.69808137f, + 0.040894926f, + 0.011605669f, + 0.00509294f, + 0.0027438672f}; + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + 10); + + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); +} + +BOOST_FIXTURE_TEST_CASE(test_getSubcatchResult, Fixture) { + + error = SMO_getSubcatchResult(p_handle, 1, 1, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 10; + float ref_array[ref_dim] = {0.5f, + 0.0f, + 0.0f, + 0.125f, + 1.2438242f, + 0.0f, + 0.0f, + 0.0f, + 33.481991f, + 6.6963983f}; + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); +} + +BOOST_FIXTURE_TEST_CASE(test_getNodeResult, Fixture) { + + error = SMO_getNodeResult(p_handle, 2, 2, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 8; + float ref_array[ref_dim] = {0.296234f, + 995.296204f, + 0.0f, + 1.302650f, + 1.302650f, + 0.0f, + 15.361463f, + 3.072293f}; + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); +} + +BOOST_FIXTURE_TEST_CASE(test_getLinkResult, Fixture) { + + error = SMO_getLinkResult(p_handle, 3, 3, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + + const int ref_dim = 7; + float ref_array[ref_dim] = {4.631762f, + 1.0f, + 5.8973422f, + 314.15927f, + 1.0f, + 19.070757f, + 3.8141515f}; + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); +} + +BOOST_FIXTURE_TEST_CASE(test_getSystemResult, Fixture) { + + error = SMO_getSystemResult(p_handle, 4, 4, &array, &array_dim); + BOOST_REQUIRE(error == 0); + + const int ref_dim = 14; + float ref_array[ref_dim] = {70.0f, + 0.1f, + 0.0f, + 0.19042271f, + 14.172027f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 14.172027f, + 0.55517411f, + 13.622702f, + 2913.0793f, + 0.0f}; + std::vector ref_vec; + ref_vec.assign(ref_array, ref_array + ref_dim); + + std::vector test_vec; + test_vec.assign(array, array + array_dim); + + BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); +} + +BOOST_AUTO_TEST_SUITE_END() From 9a103bb9a75ff462227fbb594325afd39cb73f52 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 20 Nov 2019 17:02:01 -0500 Subject: [PATCH 019/266] Configuring for swmm-output build --- CMakeLists.txt | 10 +++ outfile/include/swmm_output_enums.h | 106 ++++++++++++++++------------ 2 files changed, 71 insertions(+), 45 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f11b88d9d..bfb0bd0db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,16 @@ project(swmm-solver ) +# Append local dir to module search path +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) + +# Sets the position independent code property for all targets +SET(CMAKE_POSITION_INDEPENDENT_CODE ON) + + +# Searches for and create install rules for vcruntime.dll, msvcp.dll, etc. +include(InstallRequiredSystemLibraries) + # Sets default install prefix when cmakecache is initialized for first time if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "..." FORCE) diff --git a/outfile/include/swmm_output_enums.h b/outfile/include/swmm_output_enums.h index 35d440404..e4034c86a 100644 --- a/outfile/include/swmm_output_enums.h +++ b/outfile/include/swmm_output_enums.h @@ -13,69 +13,85 @@ typedef enum { - SMO_flow_rate, - SMO_concentration -} SMO_unit; + SMO_US, + SMO_SI +} SMO_unitSystem; typedef enum { - SMO_subcatch, - SMO_node, - SMO_link, - SMO_sys, + SMO_CFS, + SMO_GPM, + SMO_MGD, + SMO_CMS, + SMO_LPS, + SMO_MLD +} SMO_flowUnits; + +typedef enum { + SMO_MG, + SMO_UG, + SMO_COUNT, + SMO_NONE +} SMO_concUnits; + +typedef enum { + SMO_subcatch, + SMO_node, + SMO_link, + SMO_sys, SMO_pollut } SMO_elementType; typedef enum { - SMO_reportStep, - SMO_numPeriods + SMO_reportStep, + SMO_numPeriods } SMO_time; typedef enum { - SMO_rainfall_subcatch, // (in/hr or mm/hr), - SMO_snow_depth_subcatch, // (in or mm), - SMO_evap_loss, // (in/hr or mm/hr), - SMO_infil_loss, // (in/hr or mm/hr), - SMO_runoff_rate, // (flow units), - SMO_gwoutflow_rate, // (flow units), - SMO_gwtable_elev, // (ft or m), - SMO_soil_moisture, // unsaturated zone moisture content (-), - SMO_pollutant_conc_subcatch // first pollutant + SMO_rainfall_subcatch, // (in/hr or mm/hr), + SMO_snow_depth_subcatch, // (in or mm), + SMO_evap_loss, // (in/hr or mm/hr), + SMO_infil_loss, // (in/hr or mm/hr), + SMO_runoff_rate, // (flow units), + SMO_gwoutflow_rate, // (flow units), + SMO_gwtable_elev, // (ft or m), + SMO_soil_moisture, // unsaturated zone moisture content (-), + SMO_pollutant_conc_subcatch // first pollutant } SMO_subcatchAttribute; typedef enum { - SMO_invert_depth, // (ft or m), - SMO_hydraulic_head, // (ft or m), - SMO_stored_ponded_volume, // (ft3 or m3), - SMO_lateral_inflow, // (flow units), - SMO_total_inflow, // lateral + upstream (flow units), - SMO_flooding_losses, // (flow units), - SMO_pollutant_conc_node // first pollutant, + SMO_invert_depth, // (ft or m), + SMO_hydraulic_head, // (ft or m), + SMO_stored_ponded_volume, // (ft3 or m3), + SMO_lateral_inflow, // (flow units), + SMO_total_inflow, // lateral + upstream (flow units), + SMO_flooding_losses, // (flow units), + SMO_pollutant_conc_node // first pollutant, } SMO_nodeAttribute; typedef enum { - SMO_flow_rate_link, // (flow units), - SMO_flow_depth, // (ft or m), - SMO_flow_velocity, // (ft/s or m/s), - SMO_flow_volume, // (ft3 or m3), - SMO_capacity, // (fraction of conduit filled), - SMO_pollutant_conc_link // first pollutant, + SMO_flow_rate_link, // (flow units), + SMO_flow_depth, // (ft or m), + SMO_flow_velocity, // (ft/s or m/s), + SMO_flow_volume, // (ft3 or m3), + SMO_capacity, // (fraction of conduit filled), + SMO_pollutant_conc_link // first pollutant, } SMO_linkAttribute; typedef enum { - SMO_air_temp, // (deg. F or deg. C), - SMO_rainfall_system, // (in/hr or mm/hr), - SMO_snow_depth_system, // (in or mm), - SMO_evap_infil_loss, // (in/hr or mm/hr), - SMO_runoff_flow, // (flow units), - SMO_dry_weather_inflow, // (flow units), - SMO_groundwater_inflow, // (flow units), - SMO_RDII_inflow, // (flow units), - SMO_direct_inflow, // user defined (flow units), - SMO_total_lateral_inflow, // (sum of variables 4 to 8) //(flow units), - SMO_flood_losses, // (flow units), - SMO_outfall_flows, // (flow units), - SMO_volume_stored, // (ft3 or m3), - SMO_evap_rate // (in/day or mm/day), + SMO_air_temp, // (deg. F or deg. C), + SMO_rainfall_system, // (in/hr or mm/hr), + SMO_snow_depth_system, // (in or mm), + SMO_evap_infil_loss, // (in/hr or mm/hr), + SMO_runoff_flow, // (flow units), + SMO_dry_weather_inflow, // (flow units), + SMO_groundwater_inflow, // (flow units), + SMO_RDII_inflow, // (flow units), + SMO_direct_inflow, // user defined (flow units), + SMO_total_lateral_inflow, // (sum of variables 4 to 8) //(flow units), + SMO_flood_losses, // (flow units), + SMO_outfall_flows, // (flow units), + SMO_volume_stored, // (ft3 or m3), + SMO_evap_rate // (in/day or mm/day), //p_evap_rate // (in/day or mm/day) } SMO_systemAttribute; From 0252278e39c3fde03291ccc5e62f1472d64c79b3 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 20 Nov 2019 17:17:35 -0500 Subject: [PATCH 020/266] Update swmm_output.c Adding SMO_getUnits() --- outfile/swmm_output.c | 50 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/outfile/swmm_output.c b/outfile/swmm_output.c index a464269e6..ae0a75f2c 100644 --- a/outfile/swmm_output.c +++ b/outfile/swmm_output.c @@ -17,7 +17,6 @@ #include "errormanager.h" #include "messages.h" -#include "swmm_output_enums.h" #include "swmm_output.h" @@ -284,6 +283,55 @@ int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, i return set_error(p_data->error_handle, errorcode); } +int DLLEXPORT SMO_getUnits(SMO_Handle p_handle, int **unitFlag, int *length) +// +// Purpose: Returns unit flags for unit_system, flow, and pollutants. +// +{ + int errorcode = 0; + int* temp; + F_OFF offset; + data_t* p_data; + + p_data = (data_t*)p_handle; + + *unitFlag = NULL; + if (p_data->Npolluts > 0) + *length = 2 + p_data->Npolluts; + else + *length = 3; + + p_data = (data_t*)p_handle; + + if (p_data == NULL) + errorcode = -1; + else if (MEMCHECK(temp = newIntArray(*length))) + errorcode = 414; + else { + // Set flow units flag + fseek(p_data->file, 2*RECORDSIZE, SEEK_SET); + fread(&temp[1], RECORDSIZE, 1, p_data->file); + + // Set unit system based on flow flag + if (temp[1] < SMO_CMS) + temp[0] = SMO_US; + else + temp[0] = SMO_SI; + + // Set conc units flag + if (p_data->Npolluts == 0) + temp[2] = SMO_NONE; + else { + offset = p_data->ObjPropPos - (p_data->Npolluts * RECORDSIZE); + _fseek(p_data->file, offset, SEEK_SET); + fread(&temp[2], RECORDSIZE, p_data->Npolluts, p_data->file); + } + *unitFlag = temp; + } + + return set_error(p_data->error_handle, errorcode); +} + int EXPORT_OUT_API SMO_getFlowUnits(SMO_Handle p_handle, int* unitFlag) // // Purpose: Returns unit flag for flow. From ef51c771fb1482e5d1b24dc7890b943eb888d86f Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 20 Nov 2019 17:24:32 -0500 Subject: [PATCH 021/266] Update swmm_output.c --- outfile/swmm_output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/outfile/swmm_output.c b/outfile/swmm_output.c index ae0a75f2c..7bc714cf6 100644 --- a/outfile/swmm_output.c +++ b/outfile/swmm_output.c @@ -283,7 +283,7 @@ int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, i return set_error(p_data->error_handle, errorcode); } -int DLLEXPORT SMO_getUnits(SMO_Handle p_handle, int **unitFlag, int *length) +int EXPORT_OUT_API SMO_getUnits(SMO_Handle p_handle, int **unitFlag, int *length) // // Purpose: Returns unit flags for unit_system, flow, and pollutants. // From 3dfa746e975588bd1a8778b59842926663b78188 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 21 Nov 2019 10:32:48 -0500 Subject: [PATCH 022/266] Updating swmm_output Copying swmm_output from feature-wrapper branch --- outfile/include/swmm_output.h | 51 +- outfile/swmm_output.c | 868 ++++++++++++++++++---------------- tests/outfile/test_output.cpp | 221 ++++----- 3 files changed, 590 insertions(+), 550 deletions(-) diff --git a/outfile/include/swmm_output.h b/outfile/include/swmm_output.h index 7aac0bbad..7fc7f6e9e 100644 --- a/outfile/include/swmm_output.h +++ b/outfile/include/swmm_output.h @@ -12,8 +12,8 @@ #ifndef SWMM_OUTPUT_H_ #define SWMM_OUTPUT_H_ -#define MAXFILENAME 259 // Max characters in file path -#define MAXELENAME 31 // Max characters in element name +#define MAXFILENAME 259 // Max characters in file path +#define MAXELENAME 31 // Max characters in element name // This is an opaque pointer to struct. Do not access variables. typedef void* SMO_Handle; @@ -22,59 +22,44 @@ typedef void* SMO_Handle; #include "swmm_output_enums.h" #include "swmm_output_export.h" - #ifdef __cplusplus - extern "C" { +extern "C" { #endif - int EXPORT_OUT_API SMO_init(SMO_Handle* p_handle); int EXPORT_OUT_API SMO_close(SMO_Handle* p_handle); int EXPORT_OUT_API SMO_open(SMO_Handle p_handle, const char* path); int EXPORT_OUT_API SMO_getVersion(SMO_Handle p_handle, int* version); int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, int* length); +int EXPORT_OUT_API SMO_getUnits(SMO_Handle p_handle, int** unitFlag, int* length); int EXPORT_OUT_API SMO_getFlowUnits(SMO_Handle p_handle, int* unitFlag); int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, int* length); int EXPORT_OUT_API SMO_getStartDate(SMO_Handle p_handle, double* date); int EXPORT_OUT_API SMO_getTimes(SMO_Handle p_handle, SMO_time code, int* time); -int EXPORT_OUT_API SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, - int elementIndex, char** elementName, int* size); +int EXPORT_OUT_API SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, int elementIndex, char** elementName, int* size); -int EXPORT_OUT_API SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, - SMO_subcatchAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim); -int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, SMO_nodeAttribute attr, - int startPeriod, int endPeriod, float** outValueSeries, int* dim); -int EXPORT_OUT_API SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, SMO_linkAttribute attr, - int startPeriod, int endPeriod, float** outValueSeries, int* dim); -int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, - int startPeriod, int endPeriod, float** outValueSeries, int* dim); +int EXPORT_OUT_API SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, SMO_subcatchAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim); +int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, SMO_nodeAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim); +int EXPORT_OUT_API SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, SMO_linkAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim); +int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim); -int EXPORT_OUT_API SMO_getSubcatchAttribute(SMO_Handle p_handle, int timeIndex, - SMO_subcatchAttribute attr, float** outValueArray, int* length); -int EXPORT_OUT_API SMO_getNodeAttribute(SMO_Handle p_handle, int timeIndex, - SMO_nodeAttribute attr, float** outValueArray, int* length); -int EXPORT_OUT_API SMO_getLinkAttribute(SMO_Handle p_handle, int timeIndex, - SMO_linkAttribute attr, float** outValueArray, int* length); -int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int timeIndex, - SMO_systemAttribute attr, float** outValueArray, int* length); +int EXPORT_OUT_API SMO_getSubcatchAttribute(SMO_Handle p_handle, int timeIndex, SMO_subcatchAttribute attr, float** outValueArray, int* length); +int EXPORT_OUT_API SMO_getNodeAttribute(SMO_Handle p_handle, int timeIndex, SMO_nodeAttribute attr, float** outValueArray, int* length); +int EXPORT_OUT_API SMO_getLinkAttribute(SMO_Handle p_handle, int timeIndex, SMO_linkAttribute attr, float** outValueArray, int* length); +int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int timeIndex, SMO_systemAttribute attr, float** outValueArray, int* length); -int EXPORT_OUT_API SMO_getSubcatchResult(SMO_Handle p_handle, int timeIndex, - int subcatchIndex, float** outValueArray, int* arrayLength); -int EXPORT_OUT_API SMO_getNodeResult(SMO_Handle p_handle, int timeIndex, - int nodeIndex, float** outValueArray, int* arrayLength); -int EXPORT_OUT_API SMO_getLinkResult(SMO_Handle p_handle, int timeIndex, - int linkIndex, float** outValueArray, int* arrayLength); -int EXPORT_OUT_API SMO_getSystemResult(SMO_Handle p_handle, int timeIndex, - int dummyIndex, float** outValueArray, int* arrayLength); +int EXPORT_OUT_API SMO_getSubcatchResult(SMO_Handle p_handle, int timeIndex, int subcatchIndex, float** outValueArray, int* arrayLength); +int EXPORT_OUT_API SMO_getNodeResult(SMO_Handle p_handle, int timeIndex, int nodeIndex, float** outValueArray, int* arrayLength); +int EXPORT_OUT_API SMO_getLinkResult(SMO_Handle p_handle, int timeIndex, int linkIndex, float** outValueArray, int* arrayLength); +int EXPORT_OUT_API SMO_getSystemResult(SMO_Handle p_handle, int timeIndex, int dummyIndex, float** outValueArray, int* arrayLength); void EXPORT_OUT_API SMO_free(void** array); void EXPORT_OUT_API SMO_clearError(SMO_Handle p_handle_in); int EXPORT_OUT_API SMO_checkError(SMO_Handle p_handle_in, char** msg_buffer); - #ifdef __cplusplus - } +} #endif #endif /* SWMM_OUTPUT_H_ */ diff --git a/outfile/swmm_output.c b/outfile/swmm_output.c index 7bc714cf6..993b2aeff 100644 --- a/outfile/swmm_output.c +++ b/outfile/swmm_output.c @@ -10,8 +10,9 @@ */ -#include + #include +#include #include #include "errormanager.h" @@ -22,24 +23,25 @@ // NOTE: These depend on machine data model and may change when porting // F_OFF Must be a 8 byte / 64 bit integer for large file support -#ifdef _WIN32 // Windows (32-bit and 64-bit) +#ifdef _WIN32 // Windows (32-bit and 64-bit) #define F_OFF __int64 -#else // Other platforms +#else // Other platforms #define F_OFF off_t #endif -#define INT4 int // Must be a 4 byte / 32 bit integer type -#define REAL4 float // Must be a 4 byte / 32 bit real type +#define INT4 int // Must be a 4 byte / 32 bit integer type +#define REAL4 float // Must be a 4 byte / 32 bit real type -#define RECORDSIZE 4 // Memory alignment 4 byte word size for both int and real -#define DATESIZE 8 // Dates are stored as 8 byte word size +#define RECORDSIZE \ + 4 // Memory alignment 4 byte word size for both int and real +#define DATESIZE 8 // Dates are stored as 8 byte word size -#define NELEMENTTYPES 5 // Number of element types +#define NELEMENTTYPES 4 // Number of element types -#define MEMCHECK(x) (((x) == NULL) ? 414 : 0 ) +#define MEMCHECK(x) (((x) == NULL) ? 414 : 0) struct IDentry { char* IDname; - int length; + int length; }; typedef struct IDentry idEntry; @@ -48,58 +50,58 @@ typedef struct IDentry idEntry; //----------------------------------------------------------------------------- typedef struct { - char name[MAXFILENAME + 1]; // file path/name - FILE* file; // FILE structure pointer + char name[MAXFILENAME + 1]; // file path/name + FILE* file; // FILE structure pointer - struct IDentry *elementNames; // array of pointers to element names + struct IDentry* elementNames; // array of pointers to element names - long Nperiods; // number of reporting periods - int FlowUnits; // flow units code + long Nperiods; // number of reporting periods + int FlowUnits; // flow units code - int Nsubcatch; // number of subcatchments - int Nnodes; // number of drainage system nodes - int Nlinks; // number of drainage system links - int Npolluts; // number of pollutants tracked + int Nsubcatch; // number of subcatchments + int Nnodes; // number of drainage system nodes + int Nlinks; // number of drainage system links + int Npolluts; // number of pollutants tracked - int SubcatchVars; // number of subcatch reporting variables - int NodeVars; // number of node reporting variables - int LinkVars; // number of link reporting variables - int SysVars; // number of system reporting variables + int SubcatchVars; // number of subcatch reporting variables + int NodeVars; // number of node reporting variables + int LinkVars; // number of link reporting variables + int SysVars; // number of system reporting variables - double StartDate; // start date of simulation - int ReportStep; // reporting time step (seconds) + double StartDate; // start date of simulation + int ReportStep; // reporting time step (seconds) - F_OFF IDPos; // file position where object ID names start - F_OFF ObjPropPos; // file position where object properties start - F_OFF ResultsPos; // file position where results start - F_OFF BytesPerPeriod; // bytes used for results in each period + F_OFF IDPos; // file position where object ID names start + F_OFF ObjPropPos; // file position where object properties start + F_OFF ResultsPos; // file position where results start + F_OFF BytesPerPeriod; // bytes used for results in each period error_handle_t* error_handle; } data_t; - //----------------------------------------------------------------------------- // Local functions //----------------------------------------------------------------------------- -static void errorLookup(int errcode, char* errmsg, int length); -static int validateFile(data_t* p_data); -static void initElementNames(data_t* p_data); -static void clearElementNames(data_t* p_data); - -static double getTimeValue(data_t* p_data, int timeIndex); -static float getSubcatchValue(data_t* p_data, int timeIndex, int subcatchIndex, SMO_subcatchAttribute attr); -static float getNodeValue(data_t* p_data, int timeIndex, int nodeIndex, SMO_nodeAttribute attr); -static float getLinkValue(data_t* p_data, int timeIndex, int linkIndex, SMO_linkAttribute attr); -static float getSystemValue(data_t* p_data, int timeIndex, SMO_systemAttribute attr); - -static int _fopen(FILE **f, const char *name, const char *mode); -static int _fseek(FILE* stream, F_OFF offset, int whence); -static F_OFF _ftell(FILE* stream); - -static float* newFloatArray(int n); -static int* newIntArray(int n); -static char* newCharArray(int n); - +void errorLookup(int errcode, char* errmsg, int length); +int validateFile(data_t* p_data); +void initElementNames(data_t* p_data); + +double getTimeValue(data_t* p_data, int timeIndex); +float getSubcatchValue(data_t* p_data, int timeIndex, int subcatchIndex, + SMO_subcatchAttribute attr); +float getNodeValue(data_t* p_data, int timeIndex, int nodeIndex, + SMO_nodeAttribute attr); +float getLinkValue(data_t* p_data, int timeIndex, int linkIndex, + SMO_linkAttribute attr); +float getSystemValue(data_t* p_data, int timeIndex, SMO_systemAttribute attr); + +int _fopen(FILE** f, const char* name, const char* mode); +int _fseek(FILE* stream, F_OFF offset, int whence); +F_OFF _ftell(FILE* stream); + +float* newFloatArray(int n); +int* newIntArray(int n); +char* newCharArray(int n); int EXPORT_OUT_API SMO_init(SMO_Handle* p_handle) // Purpose: Initialized pointer for the opaque SMO_Handle. @@ -110,17 +112,16 @@ int EXPORT_OUT_API SMO_init(SMO_Handle* p_handle) // Don't change it. // { - int errorcode = 0; + int errorcode = 0; data_t* priv_data; // Allocate memory for private data priv_data = (data_t*)calloc(1, sizeof(data_t)); - if (priv_data != NULL){ + if (priv_data != NULL) { priv_data->error_handle = new_errormanager(&errorLookup); - *p_handle = priv_data; - } - else + *p_handle = priv_data; + } else errorcode = -1; // TODO: Need to handle errors during initialization better. @@ -133,16 +134,23 @@ int EXPORT_OUT_API SMO_close(SMO_Handle* p_handle) // { data_t* p_data; - int errorcode = 0; + int i, n, errorcode = 0; p_data = (data_t*)*p_handle; if (p_data == NULL) errorcode = -1; - else - { - clearElementNames(p_data); + else { + if (p_data->elementNames != NULL) { + n = p_data->Nsubcatch + p_data->Nnodes + p_data->Nlinks + + p_data->Npolluts; + + for (i = 0; i < n; i++) + free(p_data->elementNames[i].IDname); + + free(p_data->elementNames); + } dst_errormanager(p_data->error_handle); @@ -162,51 +170,60 @@ int EXPORT_OUT_API SMO_open(SMO_Handle p_handle, const char* path) // Purpose: Open the output binary file and read the header. // { - int err, errorcode = 0; + int err, errorcode = 0; F_OFF offset; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) return -1; - else - { + if (p_data == NULL) + return -1; + else { strncpy(p_data->name, path, MAXFILENAME); // Attempt to open binary output file for reading only - if ((_fopen(&(p_data->file), path, "rb")) != 0) errorcode = 434; + if ((_fopen(&(p_data->file), path, "rb")) != 0) + errorcode = 434; // --- validate the output file - else if ((err = validateFile(p_data)) != 0) errorcode = err; + else if ((err = validateFile(p_data)) != 0) + errorcode = err; // If a warning is encountered read file header - if (errorcode < 400 ) { + if (errorcode < 400) { // --- otherwise read additional parameters from start of file - fseek(p_data->file, 3*RECORDSIZE, SEEK_SET); + fseek(p_data->file, 3 * RECORDSIZE, SEEK_SET); fread(&(p_data->Nsubcatch), RECORDSIZE, 1, p_data->file); fread(&(p_data->Nnodes), RECORDSIZE, 1, p_data->file); fread(&(p_data->Nlinks), RECORDSIZE, 1, p_data->file); fread(&(p_data->Npolluts), RECORDSIZE, 1, p_data->file); // Compute offset for saved subcatch/node/link input values - offset = (p_data->Nsubcatch + 2) * RECORDSIZE // Subcatchment area - + (3 * p_data->Nnodes + 4) * RECORDSIZE // Node type, invert & max depth - + (5 * p_data->Nlinks + 6) * RECORDSIZE; // Link type, z1, z2, max depth & length + offset = + (p_data->Nsubcatch + 2) * RECORDSIZE // Subcatchment area + + (3 * p_data->Nnodes + 4) * + RECORDSIZE // Node type, invert & max depth + + (5 * p_data->Nlinks + 6) * + RECORDSIZE; // Link type, z1, z2, max depth & length offset += p_data->ObjPropPos; // Read number & codes of computed variables _fseek(p_data->file, offset, SEEK_SET); - fread(&(p_data->SubcatchVars), RECORDSIZE, 1, p_data->file); // # Subcatch variables + fread(&(p_data->SubcatchVars), RECORDSIZE, 1, + p_data->file); // # Subcatch variables - _fseek(p_data->file, p_data->SubcatchVars*RECORDSIZE, SEEK_CUR); - fread(&(p_data->NodeVars), RECORDSIZE, 1, p_data->file); // # Node variables + _fseek(p_data->file, p_data->SubcatchVars * RECORDSIZE, SEEK_CUR); + fread(&(p_data->NodeVars), RECORDSIZE, 1, + p_data->file); // # Node variables - _fseek(p_data->file, p_data->NodeVars*RECORDSIZE, SEEK_CUR); - fread(&(p_data->LinkVars), RECORDSIZE, 1, p_data->file); // # Link variables + _fseek(p_data->file, p_data->NodeVars * RECORDSIZE, SEEK_CUR); + fread(&(p_data->LinkVars), RECORDSIZE, 1, + p_data->file); // # Link variables - _fseek(p_data->file, p_data->LinkVars*RECORDSIZE, SEEK_CUR); - fread(&(p_data->SysVars), RECORDSIZE, 1, p_data->file); // # System variables + _fseek(p_data->file, p_data->LinkVars * RECORDSIZE, SEEK_CUR); + fread(&(p_data->SysVars), RECORDSIZE, 1, + p_data->file); // # System variables // --- read data just before start of output results offset = p_data->ResultsPos - 3 * RECORDSIZE; @@ -214,12 +231,14 @@ int EXPORT_OUT_API SMO_open(SMO_Handle p_handle, const char* path) fread(&(p_data->StartDate), DATESIZE, 1, p_data->file); fread(&(p_data->ReportStep), RECORDSIZE, 1, p_data->file); - // --- compute number of bytes of results values used per time period - p_data->BytesPerPeriod = DATESIZE + - (p_data->Nsubcatch*p_data->SubcatchVars + - p_data->Nnodes*p_data->NodeVars + - p_data->Nlinks*p_data->LinkVars + - p_data->SysVars)*RECORDSIZE; + // --- compute number of bytes of results values used per time + // period + p_data->BytesPerPeriod = + DATESIZE + + (p_data->Nsubcatch * p_data->SubcatchVars + + p_data->Nnodes * p_data->NodeVars + + p_data->Nlinks * p_data->LinkVars + p_data->SysVars) * + RECORDSIZE; } } // If error close the binary file @@ -240,15 +259,15 @@ int EXPORT_OUT_API SMO_getVersion(SMO_Handle p_handle, int* version) // Purpose: Returns SWMM version that wrote binary file // { - int errorcode = 0; + int errorcode = 0; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) return -1; - else - { - fseek(p_data->file, 1*RECORDSIZE, SEEK_SET); + if (p_data == NULL) + return -1; + else { + fseek(p_data->file, 1 * RECORDSIZE, SEEK_SET); if (fread(version, RECORDSIZE, 1, p_data->file) != 1) errorcode = 436; } @@ -256,33 +275,38 @@ int EXPORT_OUT_API SMO_getVersion(SMO_Handle p_handle, int* version) return set_error(p_data->error_handle, errorcode); } -int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, int* length) +int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, + int* length) // // Purpose: Returns project size. // { - int errorcode = 0; - int* temp = newIntArray(NELEMENTTYPES); - data_t* p_data; + int errorcode = 0; + int *temp; + data_t *p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; - else - { + *elementCount = NULL; + *length = NELEMENTTYPES; + + if (p_data == NULL) + errorcode = -1; + else if (MEMCHECK(temp = newIntArray(*length))) + errorcode = 414; + else { temp[0] = p_data->Nsubcatch; temp[1] = p_data->Nnodes; temp[2] = p_data->Nlinks; - temp[3] = 1; //Nsystems - temp[4] = p_data->Npolluts; + temp[3] = p_data->Npolluts; *elementCount = temp; - *length = NELEMENTTYPES; } return set_error(p_data->error_handle, errorcode); } + int EXPORT_OUT_API SMO_getUnits(SMO_Handle p_handle, int **unitFlag, int *length) // // Purpose: Returns unit flags for unit_system, flow, and pollutants. @@ -345,24 +369,25 @@ int EXPORT_OUT_API SMO_getFlowUnits(SMO_Handle p_handle, int* unitFlag) // 5: MLD (million liters per day) // { - int errorcode = 0; + int errorcode = 0; data_t* p_data; *unitFlag = -1; p_data = (data_t*)p_handle; - if (p_data == NULL) return -1; - else - { - fseek(p_data->file, 2*RECORDSIZE, SEEK_SET); + if (p_data == NULL) + return -1; + else { + fseek(p_data->file, 2 * RECORDSIZE, SEEK_SET); fread(unitFlag, RECORDSIZE, 1, p_data->file); } return set_error(p_data->error_handle, errorcode); } -int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, int* length) +int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, + int* length) // // Purpose: // Return integer flag representing the units that the given pollutant is @@ -376,23 +401,24 @@ int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, in // Args: // pollutantIndex: valid values are 0 to Npolluts-1 { - int errorcode = 0; - int* temp; - F_OFF offset; + int errorcode = 0; + int* temp; + F_OFF offset; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; - else if (MEMCHECK(temp = newIntArray(p_data->Npolluts))) errorcode = 414; - else - { + if (p_data == NULL) + errorcode = -1; + else if (MEMCHECK(temp = newIntArray(p_data->Npolluts))) + errorcode = 414; + else { offset = p_data->ObjPropPos - (p_data->Npolluts * RECORDSIZE); _fseek(p_data->file, offset, SEEK_SET); fread(temp, RECORDSIZE, p_data->Npolluts, p_data->file); *unitFlag = temp; - *length = p_data->Npolluts; + *length = p_data->Npolluts; } return set_error(p_data->error_handle, errorcode); @@ -403,496 +429,547 @@ int EXPORT_OUT_API SMO_getStartDate(SMO_Handle p_handle, double* date) // Purpose: Returns start date. // { - int errorcode = 0; + int errorcode = 0; data_t* p_data; *date = -1.0; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; + if (p_data == NULL) + errorcode = -1; else *date = p_data->StartDate; return set_error(p_data->error_handle, errorcode); } - int EXPORT_OUT_API SMO_getTimes(SMO_Handle p_handle, SMO_time code, int* time) // // Purpose: Returns step size and number of periods. // { - int errorcode = 0; + int errorcode = 0; data_t* p_data; *time = -1; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; - else - { - switch (code) - { - case SMO_reportStep: *time = p_data->ReportStep; - break; - case SMO_numPeriods: *time = p_data->Nperiods; - break; - default: errorcode = 421; + if (p_data == NULL) + errorcode = -1; + else { + switch (code) { + case SMO_reportStep: + *time = p_data->ReportStep; + break; + case SMO_numPeriods: + *time = p_data->Nperiods; + break; + default: + errorcode = 421; } } - return errorcode; + return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, - int index, char** name, int* length) + int index, char** name, int* length) // // Purpose: Given an element index returns the element name. // { - int idx = -1, - errorcode = 0; + int idx = -1, errorcode = 0; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = 410; - else if (p_data->file == NULL) errorcode = 411; - else - { - // Lazy initialize the name array on demand - if (p_data->elementNames == NULL) initElementNames(p_data); - - switch (type) - { - case SMO_subcatch: - if (index < 0 || index >= p_data->Nsubcatch) - errorcode = 423; - else - idx = index; - break; - - case SMO_node: - if (index < 0 || index >= p_data->Nnodes) - errorcode = 423; - else - idx = p_data->Nsubcatch + index; - break; - - case SMO_link: - if (index < 0 || index >= p_data->Nlinks) - errorcode = 423; - else - idx = p_data->Nsubcatch + p_data->Nnodes + index; - break; - - case SMO_sys: - if (index < 0 || index >= p_data->Npolluts) - errorcode = 423; - else - idx = p_data->Nsubcatch + p_data->Nnodes + p_data->Nlinks + index; - break; - - default: - errorcode = 421; + if (p_data == NULL) + errorcode = 410; + else if (p_data->file == NULL) + errorcode = 411; + else { + // Initialize the name array if necessary + if (p_data->elementNames == NULL) + initElementNames(p_data); + + switch (type) { + case SMO_subcatch: + if (index < 0 || index >= p_data->Nsubcatch) + errorcode = 423; + else + idx = index; + break; + + case SMO_node: + if (index < 0 || index >= p_data->Nnodes) + errorcode = 423; + else + idx = p_data->Nsubcatch + index; + break; + + case SMO_link: + if (index < 0 || index >= p_data->Nlinks) + errorcode = 423; + else + idx = p_data->Nsubcatch + p_data->Nnodes + index; + break; + + case SMO_pollut: + if (index < 0 || index >= p_data->Npolluts) + errorcode = 423; + else + idx = p_data->Nsubcatch + p_data->Nnodes + p_data->Nlinks + + index; + break; + + default: + errorcode = 421; } if (!errorcode) { *length = p_data->elementNames[idx].length; - *name = newCharArray(*length + 1); + *name = newCharArray(*length + 1); // Writes IDname and an additional null character to name - strncpy(*name, p_data->elementNames[idx].IDname, (*length + 1)*sizeof(char)); + strncpy(*name, p_data->elementNames[idx].IDname, + (*length + 1) * sizeof(char)); } } - return errorcode; + return set_error(p_data->error_handle, errorcode); } - int EXPORT_OUT_API SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, - SMO_subcatchAttribute attr, int startPeriod, int endPeriod, - float** outValueSeries, int* dim) + SMO_subcatchAttribute attr, int startPeriod, + int endPeriod, float** outValueSeries, + int* dim) // // Purpose: Get time series results for particular attribute. Specify series // start and length using timeIndex and length respectively. // { - int k, length, errorcode = 0; - float* temp; + int k, length, errorcode = 0; + float* temp; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; - else if (subcatchIndex < 0 || subcatchIndex > p_data->Nsubcatch) errorcode = 420; + if (p_data == NULL) + errorcode = -1; + else if (subcatchIndex < 0 || subcatchIndex > p_data->Nsubcatch) + errorcode = 420; else if (startPeriod < 0 || startPeriod >= p_data->Nperiods || - endPeriod <= startPeriod) errorcode = 422; + endPeriod <= startPeriod) + errorcode = 422; // Check memory for outValues - else if MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) errorcode = 411; - else - { + else if + MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) + errorcode = 411; + else { // loop over and build time series for (k = 0; k < length; k++) - temp[k] = getSubcatchValue(p_data, startPeriod + k, - subcatchIndex, attr); + temp[k] = + getSubcatchValue(p_data, startPeriod + k, subcatchIndex, attr); *outValueSeries = temp; - *dim = length; + *dim = length; } return set_error(p_data->error_handle, errorcode); } - -int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, SMO_nodeAttribute attr, - int startPeriod, int endPeriod, float** outValueSeries, int* dim) +int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, + SMO_nodeAttribute attr, int startPeriod, + int endPeriod, float** outValueSeries, int* dim) // // Purpose: Get time series results for particular attribute. Specify series // start and length using timeIndex and length respectively. // { - int k, length, errorcode = 0; - float* temp; + int k, length, errorcode = 0; + float* temp; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; - else if (nodeIndex < 0 || nodeIndex > p_data->Nnodes) errorcode = 420; + if (p_data == NULL) + errorcode = -1; + else if (nodeIndex < 0 || nodeIndex > p_data->Nnodes) + errorcode = 420; else if (startPeriod < 0 || startPeriod >= p_data->Nperiods || - endPeriod <= startPeriod) errorcode = 422; + endPeriod <= startPeriod) + errorcode = 422; // Check memory for outValues - else if MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) errorcode = 411; - else - { + else if + MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) + errorcode = 411; + else { // loop over and build time series for (k = 0; k < length; k++) - temp[k] = getNodeValue(p_data, startPeriod + k, - nodeIndex, attr); + temp[k] = getNodeValue(p_data, startPeriod + k, nodeIndex, attr); *outValueSeries = temp; - *dim = length; + *dim = length; } return set_error(p_data->error_handle, errorcode); } - -int EXPORT_OUT_API SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, SMO_linkAttribute attr, - int startPeriod, int endPeriod, float** outValueSeries, int* dim) +int EXPORT_OUT_API SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, + SMO_linkAttribute attr, int startPeriod, + int endPeriod, float** outValueSeries, int* dim) // // Purpose: Get time series results for particular attribute. Specify series // start and length using timeIndex and length respectively. // { - int k, length, errorcode = 0; - float* temp; + int k, length, errorcode = 0; + float* temp; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; - else if (linkIndex < 0 || linkIndex > p_data->Nlinks) errorcode = 420; + if (p_data == NULL) + errorcode = -1; + else if (linkIndex < 0 || linkIndex > p_data->Nlinks) + errorcode = 420; else if (startPeriod < 0 || startPeriod >= p_data->Nperiods || - endPeriod <= startPeriod) errorcode = 422; + endPeriod <= startPeriod) + errorcode = 422; // Check memory for outValues - else if MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) errorcode = 411; - else - { + else if + MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) + errorcode = 411; + else { // loop over and build time series for (k = 0; k < length; k++) temp[k] = getLinkValue(p_data, startPeriod + k, linkIndex, attr); *outValueSeries = temp; - *dim = length; + *dim = length; } return set_error(p_data->error_handle, errorcode); } - - int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, - int startPeriod, int endPeriod, float** outValueSeries, int* dim) + int startPeriod, int endPeriod, + float** outValueSeries, int* dim) // // Purpose: Get time series results for particular attribute. Specify series // start and length using timeIndex and length respectively. // { - int k, length, errorcode = 0; - float* temp; + int k, length, errorcode = 0; + float* temp; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; + if (p_data == NULL) + errorcode = -1; else if (startPeriod < 0 || startPeriod >= p_data->Nperiods || - endPeriod <= startPeriod) errorcode = 422; + endPeriod <= startPeriod) + errorcode = 422; // Check memory for outValues - else if MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) errorcode = 411; - else - { + else if + MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) + errorcode = 411; + else { // loop over and build time series for (k = 0; k < length; k++) temp[k] = getSystemValue(p_data, startPeriod + k, attr); *outValueSeries = temp; - *dim = length; + *dim = length; } return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API SMO_getSubcatchAttribute(SMO_Handle p_handle, int periodIndex, - SMO_subcatchAttribute attr, float** outValueArray, int* length) + SMO_subcatchAttribute attr, + float** outValueArray, int* length) // // Purpose: For all subcatchments at given time, get a particular attribute. // { - int k, errorcode = 0; - float* temp; + int k, errorcode = 0; + float* temp; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; - else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; // Check memory for outValues - else if MEMCHECK(temp = newFloatArray(p_data->Nsubcatch)) errorcode = 411; - else - { + else if + MEMCHECK(temp = newFloatArray(p_data->Nsubcatch)) errorcode = 411; + else { // loop over and pull result for (k = 0; k < p_data->Nsubcatch; k++) temp[k] = getSubcatchValue(p_data, periodIndex, k, attr); *outValueArray = temp; - *length = p_data->Nsubcatch; + *length = p_data->Nsubcatch; } return set_error(p_data->error_handle, errorcode); } - - int EXPORT_OUT_API SMO_getNodeAttribute(SMO_Handle p_handle, int periodIndex, - SMO_nodeAttribute attr, float** outValueArray, int* length) + SMO_nodeAttribute attr, + float** outValueArray, int* length) // // Purpose: For all nodes at given time, get a particular attribute. // { - int k, errorcode = 0; - float* temp; + int k, errorcode = 0; + float* temp; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; - else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; // Check memory for outValues - else if MEMCHECK(temp = newFloatArray(p_data->Nnodes)) errorcode = 411; - else - { + else if + MEMCHECK(temp = newFloatArray(p_data->Nnodes)) errorcode = 411; + else { // loop over and pull result for (k = 0; k < p_data->Nnodes; k++) temp[k] = getNodeValue(p_data, periodIndex, k, attr); *outValueArray = temp; - *length = p_data->Nnodes; + *length = p_data->Nnodes; } return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API SMO_getLinkAttribute(SMO_Handle p_handle, int periodIndex, - SMO_linkAttribute attr, float** outValueArray, int* length) + SMO_linkAttribute attr, + float** outValueArray, int* length) // // Purpose: For all links at given time, get a particular attribute. // { - int k, errorcode = 0; - float* temp; + int k, errorcode = 0; + float* temp; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; - else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; // Check memory for outValues - else if MEMCHECK(temp = newFloatArray(p_data->Nlinks)) errorcode = 411; - else - { + else if + MEMCHECK(temp = newFloatArray(p_data->Nlinks)) errorcode = 411; + else { // loop over and pull result for (k = 0; k < p_data->Nlinks; k++) temp[k] = getLinkValue(p_data, periodIndex, k, attr); *outValueArray = temp; - *length = p_data->Nlinks; + *length = p_data->Nlinks; } return set_error(p_data->error_handle, errorcode); } - int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, - SMO_systemAttribute attr, float** outValueArray, int* length) + SMO_systemAttribute attr, + float** outValueArray, int* length) // // Purpose: For the system at given time, get a particular attribute. // { - int errorcode = 0; - float temp; + int errorcode = 0; + float temp; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; - else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; - else - { + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; + else { // don't need to loop since there's only one system temp = getSystemValue(p_data, periodIndex, attr); *outValueArray = &temp; - *length = 1; + *length = 1; } return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API SMO_getSubcatchResult(SMO_Handle p_handle, int periodIndex, - int subcatchIndex, float** outValueArray, int* arrayLength) + int subcatchIndex, float** outValueArray, + int* arrayLength) // // Purpose: For a subcatchment at given time, get all attributes. // { - int errorcode = 0; - float* temp; - F_OFF offset; + int errorcode = 0; + float* temp; + F_OFF offset; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; - else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; - else if (subcatchIndex < 0 || subcatchIndex > p_data->Nsubcatch) errorcode = 423; - else if MEMCHECK(temp = newFloatArray(p_data->SubcatchVars)) errorcode = 411; - else - { + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; + else if (subcatchIndex < 0 || subcatchIndex > p_data->Nsubcatch) + errorcode = 423; + else if + MEMCHECK(temp = newFloatArray(p_data->SubcatchVars)) errorcode = 411; + else { // --- compute offset into output file - offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + 2 * RECORDSIZE; + offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + + 2 * RECORDSIZE; // add offset for subcatchment - offset += (subcatchIndex*p_data->SubcatchVars)*RECORDSIZE; + offset += (subcatchIndex * p_data->SubcatchVars) * RECORDSIZE; _fseek(p_data->file, offset, SEEK_SET); fread(temp, RECORDSIZE, p_data->SubcatchVars, p_data->file); *outValueArray = temp; - *arrayLength = p_data->SubcatchVars; + *arrayLength = p_data->SubcatchVars; } return set_error(p_data->error_handle, errorcode); } - int EXPORT_OUT_API SMO_getNodeResult(SMO_Handle p_handle, int periodIndex, - int nodeIndex, float** outValueArray, int* arrayLength) + int nodeIndex, float** outValueArray, + int* arrayLength) // // Purpose: For a node at given time, get all attributes. // { - int errorcode = 0; - float* temp; - F_OFF offset; + int errorcode = 0; + float* temp; + F_OFF offset; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; - else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; - else if (nodeIndex < 0 || nodeIndex > p_data->Nnodes) errorcode = 423; - else if MEMCHECK(temp = newFloatArray(p_data->NodeVars)) errorcode = 411; - else - { + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; + else if (nodeIndex < 0 || nodeIndex > p_data->Nnodes) + errorcode = 423; + else if + MEMCHECK(temp = newFloatArray(p_data->NodeVars)) errorcode = 411; + else { // calculate byte offset to start time for series - offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + 2 * RECORDSIZE; + offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + + 2 * RECORDSIZE; // add offset for subcatchment and node - offset += (p_data->Nsubcatch*p_data->SubcatchVars + nodeIndex*p_data->NodeVars)*RECORDSIZE; + offset += (p_data->Nsubcatch * p_data->SubcatchVars + + nodeIndex * p_data->NodeVars) * + RECORDSIZE; _fseek(p_data->file, offset, SEEK_SET); fread(temp, RECORDSIZE, p_data->NodeVars, p_data->file); *outValueArray = temp; - *arrayLength = p_data->NodeVars; + *arrayLength = p_data->NodeVars; } return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API SMO_getLinkResult(SMO_Handle p_handle, int periodIndex, - int linkIndex, float** outValueArray, int* arrayLength) + int linkIndex, float** outValueArray, + int* arrayLength) // // Purpose: For a link at given time, get all attributes. // { - int errorcode = 0; - float* temp; - F_OFF offset; + int errorcode = 0; + float* temp; + F_OFF offset; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; - else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; - else if (linkIndex < 0 || linkIndex > p_data->Nlinks) errorcode = 423; - else if MEMCHECK(temp = newFloatArray(p_data->LinkVars)) errorcode = 411; - else - { + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; + else if (linkIndex < 0 || linkIndex > p_data->Nlinks) + errorcode = 423; + else if + MEMCHECK(temp = newFloatArray(p_data->LinkVars)) errorcode = 411; + else { // calculate byte offset to start time for series - offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + 2 * RECORDSIZE; + offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + + 2 * RECORDSIZE; // add offset for subcatchment and node and link - offset += (p_data->Nsubcatch*p_data->SubcatchVars - + p_data->Nnodes*p_data->NodeVars + linkIndex*p_data->LinkVars)*RECORDSIZE; + offset += + (p_data->Nsubcatch * p_data->SubcatchVars + + p_data->Nnodes * p_data->NodeVars + linkIndex * p_data->LinkVars) * + RECORDSIZE; _fseek(p_data->file, offset, SEEK_SET); fread(temp, RECORDSIZE, p_data->LinkVars, p_data->file); *outValueArray = temp; - *arrayLength = p_data->LinkVars; + *arrayLength = p_data->LinkVars; } return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API SMO_getSystemResult(SMO_Handle p_handle, int periodIndex, - int dummyIndex, float** outValueArray, int* arrayLength) + int dummyIndex, float** outValueArray, + int* arrayLength) // // Purpose: For the system at given time, get all attributes. // { - int errorcode = 0; - float* temp; - F_OFF offset; + int errorcode = 0; + float* temp; + F_OFF offset; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) errorcode = -1; - else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) errorcode = 422; - else if MEMCHECK(temp = newFloatArray(p_data->SysVars)) errorcode = 411; + if (p_data == NULL) + errorcode = -1; + else if (periodIndex < 0 || periodIndex >= p_data->Nperiods) + errorcode = 422; + else if + MEMCHECK(temp = newFloatArray(p_data->SysVars)) errorcode = 411; { // calculate byte offset to start time for series - offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + 2 * RECORDSIZE; - // add offset for subcatchment and node and link (system starts after the last link) - offset += (p_data->Nsubcatch*p_data->SubcatchVars + p_data->Nnodes*p_data->NodeVars - + p_data->Nlinks*p_data->LinkVars)*RECORDSIZE; + offset = p_data->ResultsPos + (periodIndex)*p_data->BytesPerPeriod + + 2 * RECORDSIZE; + // add offset for subcatchment and node and link (system starts after + // the last link) + offset += (p_data->Nsubcatch * p_data->SubcatchVars + + p_data->Nnodes * p_data->NodeVars + + p_data->Nlinks * p_data->LinkVars) * + RECORDSIZE; _fseek(p_data->file, offset, SEEK_SET); fread(temp, RECORDSIZE, p_data->SysVars, p_data->file); *outValueArray = temp; - *arrayLength = p_data->SysVars; + *arrayLength = p_data->SysVars; } return set_error(p_data->error_handle, errorcode); @@ -909,25 +986,23 @@ void EXPORT_OUT_API SMO_free(void** array) } } -void EXPORT_OUT_API SMO_clearError(SMO_Handle p_handle) -{ +void EXPORT_OUT_API SMO_clearError(SMO_Handle p_handle) { data_t* p_data; p_data = (data_t*)p_handle; clear_error(p_data->error_handle); } -int EXPORT_OUT_API SMO_checkError(SMO_Handle p_handle, char** msg_buffer) -{ - int errorcode = 0; - char *temp = NULL; +int EXPORT_OUT_API SMO_checkError(SMO_Handle p_handle, char** msg_buffer) { + int errorcode = 0; + char* temp = NULL; data_t* p_data; p_data = (data_t*)p_handle; - if (p_data == NULL) return -1; - else - { + if (p_data == NULL) + return -1; + else { errorcode = p_data->error_handle->error_status; if (errorcode) temp = check_error(p_data->error_handle); @@ -945,38 +1020,45 @@ void errorLookup(int errcode, char* dest_msg, int dest_len) { const char* msg; - switch (errcode) - { - case 10: msg = WARN10; - break; - case 411: msg = ERR411; - break; - case 421: msg = ERR421; - break; - case 422: msg = ERR422; - break; - case 423: msg = ERR423; - break; - case 424: msg = ERR424; - break; - case 434: msg = ERR434; - break; - case 435: msg = ERR435; - break; - case 436: msg = ERR436; - break; - default: msg = ERR440; + switch (errcode) { + case 10: + msg = WARN10; + break; + case 411: + msg = ERR411; + break; + case 421: + msg = ERR421; + break; + case 422: + msg = ERR422; + break; + case 423: + msg = ERR423; + break; + case 424: + msg = ERR424; + break; + case 434: + msg = ERR434; + break; + case 435: + msg = ERR435; + break; + case 436: + msg = ERR436; + break; + default: + msg = ERR440; } strncpy(dest_msg, msg, MAXMSG); } - // Local functions: -int validateFile(data_t* p_data) -{ +int validateFile(data_t* p_data) { INT4 magic1, magic2, errcode; - int errorcode = 0; + int errorcode = 0; // --- fast forward to end and read epilogue _fseek(p_data->file, -6 * RECORDSIZE, SEEK_END); @@ -992,20 +1074,23 @@ int validateFile(data_t* p_data) fread(&magic1, RECORDSIZE, 1, p_data->file); // Is this a valid SWMM binary output file? - if (magic1 != magic2) errorcode = 435; + if (magic1 != magic2) + errorcode = 435; // Does the binary file contain results? - else if (p_data->Nperiods <= 0) errorcode = 436; + else if (p_data->Nperiods <= 0) + errorcode = 436; // Were there problems with the model run? - else if (errcode != 0) errorcode = 10; + else if (errcode != 0) + errorcode = 10; return errorcode; } -void initElementNames(data_t* p_data) -{ +void initElementNames(data_t* p_data) { int j, numNames; - numNames = p_data->Nsubcatch + p_data->Nnodes + p_data->Nlinks + p_data->Npolluts; + numNames = + p_data->Nsubcatch + p_data->Nnodes + p_data->Nlinks + p_data->Npolluts; // allocate memory for array of idEntries p_data->elementNames = (idEntry*)calloc(numNames, sizeof(idEntry)); @@ -1013,40 +1098,23 @@ void initElementNames(data_t* p_data) // Position the file to the start of the ID entries _fseek(p_data->file, p_data->IDPos, SEEK_SET); - for(j=0;jelementNames[j].length), RECORDSIZE, 1, p_data->file); p_data->elementNames[j].IDname = - (char*)calloc(p_data->elementNames[j].length + 1, sizeof(char)); + (char*)calloc(p_data->elementNames[j].length + 1, sizeof(char)); fread(p_data->elementNames[j].IDname, sizeof(char), - p_data->elementNames[j].length, p_data->file); - } -} - -void clearElementNames(data_t* p_data) -{ - int i, n; - - if (p_data->elementNames != NULL) - { - n = p_data->Nsubcatch + p_data->Nnodes + p_data->Nlinks + p_data->Npolluts; - - for(i = 0; i < n; i++) - free(p_data->elementNames[i].IDname); - - free(p_data->elementNames); + p_data->elementNames[j].length, p_data->file); } } -double getTimeValue(data_t* p_data, int timeIndex) -{ - F_OFF offset; +double getTimeValue(data_t* p_data, int timeIndex) { + F_OFF offset; double value; // --- compute offset into output file - offset = p_data->ResultsPos + timeIndex*p_data->BytesPerPeriod; + offset = p_data->ResultsPos + timeIndex * p_data->BytesPerPeriod; // --- re-position the file and read the result _fseek(p_data->file, offset, SEEK_SET); @@ -1056,15 +1124,15 @@ double getTimeValue(data_t* p_data, int timeIndex) } float getSubcatchValue(data_t* p_data, int timeIndex, int subcatchIndex, - SMO_subcatchAttribute attr) -{ + SMO_subcatchAttribute attr) { F_OFF offset; float value; // --- compute offset into output file - offset = p_data->ResultsPos + timeIndex*p_data->BytesPerPeriod + 2*RECORDSIZE; + offset = p_data->ResultsPos + timeIndex * p_data->BytesPerPeriod + + 2 * RECORDSIZE; // offset for subcatch - offset += RECORDSIZE*(subcatchIndex*p_data->SubcatchVars + attr); + offset += RECORDSIZE * (subcatchIndex * p_data->SubcatchVars + attr); // --- re-position the file and read the result _fseek(p_data->file, offset, SEEK_SET); @@ -1074,15 +1142,16 @@ float getSubcatchValue(data_t* p_data, int timeIndex, int subcatchIndex, } float getNodeValue(data_t* p_data, int timeIndex, int nodeIndex, - SMO_nodeAttribute attr) -{ + SMO_nodeAttribute attr) { F_OFF offset; float value; // --- compute offset into output file - offset = p_data->ResultsPos + timeIndex*p_data->BytesPerPeriod + 2*RECORDSIZE; + offset = p_data->ResultsPos + timeIndex * p_data->BytesPerPeriod + + 2 * RECORDSIZE; // offset for node - offset += RECORDSIZE*(p_data->Nsubcatch*p_data->SubcatchVars + nodeIndex*p_data->NodeVars + attr); + offset += RECORDSIZE * (p_data->Nsubcatch * p_data->SubcatchVars + + nodeIndex * p_data->NodeVars + attr); // --- re-position the file and read the result _fseek(p_data->file, offset, SEEK_SET); @@ -1092,16 +1161,17 @@ float getNodeValue(data_t* p_data, int timeIndex, int nodeIndex, } float getLinkValue(data_t* p_data, int timeIndex, int linkIndex, - SMO_linkAttribute attr) -{ + SMO_linkAttribute attr) { F_OFF offset; float value; // --- compute offset into output file - offset = p_data->ResultsPos + timeIndex*p_data->BytesPerPeriod + 2*RECORDSIZE; + offset = p_data->ResultsPos + timeIndex * p_data->BytesPerPeriod + + 2 * RECORDSIZE; // offset for link - offset += RECORDSIZE*(p_data->Nsubcatch*p_data->SubcatchVars + p_data->Nnodes*p_data->NodeVars + - linkIndex*p_data->LinkVars + attr); + offset += RECORDSIZE * (p_data->Nsubcatch * p_data->SubcatchVars + + p_data->Nnodes * p_data->NodeVars + + linkIndex * p_data->LinkVars + attr); // --- re-position the file and read the result _fseek(p_data->file, offset, SEEK_SET); @@ -1110,17 +1180,17 @@ float getLinkValue(data_t* p_data, int timeIndex, int linkIndex, return value; } -float getSystemValue(data_t* p_data, int timeIndex, - SMO_systemAttribute attr) -{ +float getSystemValue(data_t* p_data, int timeIndex, SMO_systemAttribute attr) { F_OFF offset; float value; // --- compute offset into output file - offset = p_data->ResultsPos + timeIndex*p_data->BytesPerPeriod + 2*RECORDSIZE; + offset = p_data->ResultsPos + timeIndex * p_data->BytesPerPeriod + + 2 * RECORDSIZE; // offset for system - offset += RECORDSIZE*(p_data->Nsubcatch*p_data->SubcatchVars + p_data->Nnodes*p_data->NodeVars + - p_data->Nlinks*p_data->LinkVars + attr); + offset += RECORDSIZE * (p_data->Nsubcatch * p_data->SubcatchVars + + p_data->Nnodes * p_data->NodeVars + + p_data->Nlinks * p_data->LinkVars + attr); // --- re-position the file and read the result _fseek(p_data->file, offset, SEEK_SET); @@ -1129,7 +1199,7 @@ float getSystemValue(data_t* p_data, int timeIndex, return value; } -int _fopen(FILE **f, const char *name, const char *mode) { +int _fopen(FILE** f, const char* name, const char* mode) { // // Purpose: Substitute for fopen_s on platforms where it doesn't exist // Note: fopen_s is part of C++11 standard @@ -1177,7 +1247,7 @@ float* newFloatArray(int n) // Warning: Caller must free memory allocated by this function. // { - return (float*) malloc((n)*sizeof(float)); + return (float*)malloc((n) * sizeof(float)); } int* newIntArray(int n) @@ -1185,7 +1255,7 @@ int* newIntArray(int n) // Warning: Caller must free memory allocated by this function. // { - return (int*) malloc((n)*sizeof(int)); + return (int*)malloc((n) * sizeof(int)); } char* newCharArray(int n) @@ -1193,5 +1263,5 @@ char* newCharArray(int n) // Warning: Caller must free memory allocated by this function. // { - return (char*) malloc((n)*sizeof(char)); + return (char*)malloc((n) * sizeof(char)); } diff --git a/tests/outfile/test_output.cpp b/tests/outfile/test_output.cpp index f4c22dbb3..da15e24d8 100644 --- a/tests/outfile/test_output.cpp +++ b/tests/outfile/test_output.cpp @@ -12,69 +12,65 @@ //#define BOOST_TEST_DYN_LINK #define BOOST_TEST_MODULE "output" +#include - -#include -#include -#include #include - -#include +#include +#include +#include #include "swmm_output.h" - // NOTE: Reference data for the unit tests is currently tied to SWMM 5.1.7 #define DATA_PATH "./Example1.out" using namespace std; -// Custom test to check the minimum number of correct decimal digits between -// the test and the ref vectors. -boost::test_tools::predicate_result check_cdd(std::vector& test, - std::vector& ref, long cdd_tol) -{ - float tmp, min_cdd = 10.0f; +// Checks for minimum number of correct decimal digits +boost::test_tools::predicate_result check_cdd_float(std::vector& test, + std::vector& ref, long cdd_tol){ + + float tmp, min_cdd = 10.0; - // TODO: What is the vectors aren't the same length? + // TODO: What if the vectors aren't the same length? std::vector::iterator test_it; std::vector::iterator ref_it; - for (test_it = test.begin(); test_it < test.end(); ++test_it) { - for (ref_it = ref.begin(); ref_it < ref.end(); ++ref_it) { - - if (*test_it != *ref_it) { - // Compute log absolute error - tmp = abs(*test_it - *ref_it); - if (tmp < 1.0e-7) - tmp = 1.0e-7f; - - else if (tmp > 2.0) - tmp = 1.0f; - - tmp = - log10f(tmp); - if (tmp < 0.0) - tmp = 0.0f; - - if (tmp < min_cdd) - min_cdd = tmp; - } + for (test_it = test.begin(), ref_it = ref.begin(); + (test_it < test.end()) && (ref_it < ref.end()); + ++test_it, ++ref_it) + { + if (*test_it != *ref_it) { + // Compute log absolute error + tmp = abs(*test_it - *ref_it); + if (tmp < 1.0e-7f) + tmp = 1.0e-7f; + + else if (tmp > 2.0f) + tmp = 1.0f; + + tmp = -log10(tmp); + if (tmp < 0.0f) + tmp = 0.0f; + + if (tmp < min_cdd) + min_cdd = tmp; } } - - return floor(min_cdd) <= cdd_tol; + return floor(min_cdd) >= cdd_tol; } -boost::test_tools::predicate_result check_string(std::string test, std::string ref) -{ +boost::test_tools::predicate_result check_string(std::string test, + std::string ref) { + if (ref.compare(test) == 0) return true; else return false; } -BOOST_AUTO_TEST_SUITE (test_output_auto) +BOOST_AUTO_TEST_SUITE(test_output_auto) BOOST_AUTO_TEST_CASE(InitTest) { SMO_Handle p_handle = NULL; @@ -96,8 +92,8 @@ BOOST_AUTO_TEST_CASE(CloseTest) { } BOOST_AUTO_TEST_CASE(InitOpenCloseTest) { - std::string path = std::string(DATA_PATH); - SMO_Handle p_handle = NULL; + std::string path = std::string(DATA_PATH); + SMO_Handle p_handle = NULL; SMO_init(&p_handle); int error = SMO_open(p_handle, path.c_str()); @@ -108,7 +104,7 @@ BOOST_AUTO_TEST_CASE(InitOpenCloseTest) { BOOST_AUTO_TEST_SUITE_END() -struct Fixture{ +struct Fixture { Fixture() { std::string path = std::string(DATA_PATH); @@ -116,7 +112,7 @@ struct Fixture{ SMO_clearError(p_handle); error = SMO_open(p_handle, path.c_str()); - array = NULL; + array = NULL; array_dim = 0; } ~Fixture() { @@ -125,11 +121,11 @@ struct Fixture{ } std::string path; - int error; - SMO_Handle p_handle; + int error; + SMO_Handle p_handle; float* array; - int array_dim; + int array_dim; }; BOOST_AUTO_TEST_SUITE(test_output_fixture) @@ -153,13 +149,36 @@ BOOST_FIXTURE_TEST_CASE(test_getProjectSize, Fixture) { test.assign(i_array, i_array + array_dim); // subcatchs, nodes, links, pollutants - const int ref_dim = 5; - int ref_array[ref_dim] = {8,14,13,1,2}; + const int ref_dim = 4; + int ref_array[ref_dim] = {8, 14, 13, 2}; std::vector ref; ref.assign(ref_array, ref_array + ref_dim); - BOOST_CHECK_EQUAL_COLLECTIONS(ref.begin(), ref.end(), test.begin(), test.end()); + BOOST_CHECK_EQUAL_COLLECTIONS(ref.begin(), ref.end(), test.begin(), + test.end()); + + SMO_free((void**)&i_array); +} + +BOOST_FIXTURE_TEST_CASE(test_getUnits, Fixture) { + int* i_array = NULL; + + error = SMO_getUnits(p_handle, &i_array, &array_dim); + BOOST_REQUIRE(error == 0); + + std::vector test; + test.assign(i_array, i_array + array_dim); + + // unit system, flow units, pollut units + const int ref_dim = 4; + const int ref_array[ref_dim] = {SMO_US, SMO_CFS, SMO_MG, SMO_UG}; + + std::vector ref; + ref.assign(ref_array, ref_array + ref_dim); + + BOOST_CHECK_EQUAL_COLLECTIONS(ref.begin(), ref.end(), test.begin(), + test.end()); SMO_free((void**)&i_array); } @@ -181,13 +200,14 @@ BOOST_FIXTURE_TEST_CASE(test_getPollutantUnits, Fixture) { std::vector test; test.assign(i_array, i_array + array_dim); - const int ref_dim = 2; - int ref_array[ref_dim] = {0, 1}; + const int ref_dim = 2; + int ref_array[ref_dim] = {0, 1}; std::vector ref; ref.assign(ref_array, ref_array + ref_dim); - BOOST_CHECK_EQUAL_COLLECTIONS(ref.begin(), ref.end(), test.begin(), test.end()); + BOOST_CHECK_EQUAL_COLLECTIONS(ref.begin(), ref.end(), test.begin(), + test.end()); SMO_free((void**)&i_array); BOOST_CHECK(i_array == NULL); @@ -218,7 +238,7 @@ BOOST_FIXTURE_TEST_CASE(test_getTimes, Fixture) { BOOST_FIXTURE_TEST_CASE(test_getElementName, Fixture) { char* c_array = NULL; - int index = 1; + int index = 1; error = SMO_getElementName(p_handle, SMO_node, index, &c_array, &array_dim); BOOST_REQUIRE(error == 0); @@ -231,129 +251,94 @@ BOOST_FIXTURE_TEST_CASE(test_getElementName, Fixture) { } BOOST_FIXTURE_TEST_CASE(test_getSubcatchSeries, Fixture) { - - error = SMO_getSubcatchSeries(p_handle, 1, SMO_runoff_rate, 0, 10, &array, &array_dim); + error = SMO_getSubcatchSeries(p_handle, 1, SMO_runoff_rate, 0, 10, &array, + &array_dim); BOOST_REQUIRE(error == 0); - const int ref_dim = 10; - float ref_array[ref_dim] = {0.0f, - 1.2438242f, - 2.5639679f, - 4.524055f, - 2.5115132f, - 0.69808137f, - 0.040894926f, - 0.011605669f, - 0.00509294f, - 0.0027438672f}; + const int ref_dim = 10; + float ref_array[ref_dim] = { + 0.0f, 1.2438242f, 2.5639679f, 4.524055f, 2.5115132f, 0.69808137f, + 0.040894926f, 0.011605669f, 0.00509294f, 0.0027438672f}; + std::vector ref_vec; ref_vec.assign(ref_array, ref_array + 10); - std::vector test_vec; test_vec.assign(array, array + array_dim); - BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); + BOOST_CHECK(check_cdd_float(test_vec, ref_vec, 3)); } BOOST_FIXTURE_TEST_CASE(test_getSubcatchResult, Fixture) { - error = SMO_getSubcatchResult(p_handle, 1, 1, &array, &array_dim); BOOST_REQUIRE(error == 0); - const int ref_dim = 10; - float ref_array[ref_dim] = {0.5f, - 0.0f, - 0.0f, - 0.125f, - 1.2438242f, - 0.0f, - 0.0f, - 0.0f, - 33.481991f, - 6.6963983f}; + const int ref_dim = 10; + float ref_array[ref_dim] = { + 0.5f, 0.0f, 0.0f, 0.125f, 1.2438242f, + 0.0f, 0.0f, 0.0f, 33.481991f, 6.6963983f}; + std::vector ref_vec; ref_vec.assign(ref_array, ref_array + ref_dim); std::vector test_vec; test_vec.assign(array, array + array_dim); - BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); + BOOST_CHECK(check_cdd_float(test_vec, ref_vec, 3)); } BOOST_FIXTURE_TEST_CASE(test_getNodeResult, Fixture) { - error = SMO_getNodeResult(p_handle, 2, 2, &array, &array_dim); BOOST_REQUIRE(error == 0); - const int ref_dim = 8; - float ref_array[ref_dim] = {0.296234f, - 995.296204f, - 0.0f, - 1.302650f, - 1.302650f, - 0.0f, - 15.361463f, - 3.072293f}; + const int ref_dim = 8; + float ref_array[ref_dim] = { + 0.296234f, 995.296204f, 0.0f, 1.302650f, 1.302650f, 0.0f, + 15.361463f, 3.072293f}; + std::vector ref_vec; ref_vec.assign(ref_array, ref_array + ref_dim); std::vector test_vec; test_vec.assign(array, array + array_dim); - BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); + BOOST_CHECK(check_cdd_float(test_vec, ref_vec, 3)); } BOOST_FIXTURE_TEST_CASE(test_getLinkResult, Fixture) { - error = SMO_getLinkResult(p_handle, 3, 3, &array, &array_dim); BOOST_REQUIRE(error == 0); + const int ref_dim = 7; + float ref_array[ref_dim] = { + 4.631762f, 1.0f, 5.8973422f, 314.15927f, 1.0f, 19.070757f, + 3.8141515f}; - const int ref_dim = 7; - float ref_array[ref_dim] = {4.631762f, - 1.0f, - 5.8973422f, - 314.15927f, - 1.0f, - 19.070757f, - 3.8141515f}; std::vector ref_vec; ref_vec.assign(ref_array, ref_array + ref_dim); std::vector test_vec; test_vec.assign(array, array + array_dim); - BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); + BOOST_CHECK(check_cdd_float(test_vec, ref_vec, 3)); } BOOST_FIXTURE_TEST_CASE(test_getSystemResult, Fixture) { - error = SMO_getSystemResult(p_handle, 4, 4, &array, &array_dim); BOOST_REQUIRE(error == 0); - const int ref_dim = 14; - float ref_array[ref_dim] = {70.0f, - 0.1f, - 0.0f, - 0.19042271f, - 14.172027f, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 14.172027f, - 0.55517411f, - 13.622702f, - 2913.0793f, - 0.0f}; + const int ref_dim = 14; + float ref_array[ref_dim] = { + 70.0f, 0.1f, 0.0f, 0.19042271f, 14.172027f, 0.0f, 0.0f, 0.0f, + 0.0f, 14.172027f, 0.55517411f, 13.622702f, 2913.0793f, 0.0f}; + std::vector ref_vec; ref_vec.assign(ref_array, ref_array + ref_dim); std::vector test_vec; test_vec.assign(array, array + array_dim); - BOOST_CHECK(check_cdd(test_vec, ref_vec, 3)); + BOOST_CHECK(check_cdd_float(test_vec, ref_vec, 3)); } BOOST_AUTO_TEST_SUITE_END() From 85a34c460947918af47684f5d65a2e308402f560 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 22 Nov 2019 09:02:37 -0500 Subject: [PATCH 023/266] Work-in-progress --- outfile/swmm_output.c | 69 +++++++++++++++++++------------------------ tools/make.cmd | 3 +- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/outfile/swmm_output.c b/outfile/swmm_output.c index 993b2aeff..3b1f48255 100644 --- a/outfile/swmm_output.c +++ b/outfile/swmm_output.c @@ -10,14 +10,13 @@ */ - #include #include #include #include "errormanager.h" -#include "messages.h" +#include "messages.h" #include "swmm_output.h" @@ -28,11 +27,10 @@ #else // Other platforms #define F_OFF off_t #endif -#define INT4 int // Must be a 4 byte / 32 bit integer type -#define REAL4 float // Must be a 4 byte / 32 bit real type +#define INT4 int // Must be a 4 byte / 32 bit integer type +#define REAL4 float // Must be a 4 byte / 32 bit real type -#define RECORDSIZE \ - 4 // Memory alignment 4 byte word size for both int and real +#define RECORDSIZE 4 // Memory alignment 4 byte word size for both int and real #define DATESIZE 8 // Dates are stored as 8 byte word size #define NELEMENTTYPES 4 // Number of element types @@ -149,7 +147,7 @@ int EXPORT_OUT_API SMO_close(SMO_Handle* p_handle) for (i = 0; i < n; i++) free(p_data->elementNames[i].IDname); - free(p_data->elementNames); + free(p_data->elementNames); } dst_errormanager(p_data->error_handle); @@ -276,7 +274,7 @@ int EXPORT_OUT_API SMO_getVersion(SMO_Handle p_handle, int* version) } int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, - int* length) + int* length) // // Purpose: Returns project size. // @@ -387,7 +385,7 @@ int EXPORT_OUT_API SMO_getFlowUnits(SMO_Handle p_handle, int* unitFlag) } int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, - int* length) + int* length) // // Purpose: // Return integer flag representing the units that the given pollutant is @@ -475,7 +473,7 @@ int EXPORT_OUT_API SMO_getTimes(SMO_Handle p_handle, SMO_time code, int* time) } int EXPORT_OUT_API SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, - int index, char** name, int* length) + int index, char** name, int* length) // // Purpose: Given an element index returns the element name. // @@ -541,9 +539,8 @@ int EXPORT_OUT_API SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, } int EXPORT_OUT_API SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, - SMO_subcatchAttribute attr, int startPeriod, - int endPeriod, float** outValueSeries, - int* dim) + SMO_subcatchAttribute attr, int startPeriod, int endPeriod, + float** outValueSeries, int* dim) // // Purpose: Get time series results for particular attribute. Specify series // start and length using timeIndex and length respectively. @@ -580,8 +577,8 @@ int EXPORT_OUT_API SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, } int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, - SMO_nodeAttribute attr, int startPeriod, - int endPeriod, float** outValueSeries, int* dim) + SMO_nodeAttribute attr, int startPeriod, int endPeriod, + float** outValueSeries, int* dim) // // Purpose: Get time series results for particular attribute. Specify series // start and length using timeIndex and length respectively. @@ -617,8 +614,8 @@ int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, } int EXPORT_OUT_API SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, - SMO_linkAttribute attr, int startPeriod, - int endPeriod, float** outValueSeries, int* dim) + SMO_linkAttribute attr, int startPeriod, int endPeriod, + float** outValueSeries, int* dim) // // Purpose: Get time series results for particular attribute. Specify series // start and length using timeIndex and length respectively. @@ -654,8 +651,7 @@ int EXPORT_OUT_API SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, } int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, - int startPeriod, int endPeriod, - float** outValueSeries, int* dim) + int startPeriod, int endPeriod, float** outValueSeries, int* dim) // // Purpose: Get time series results for particular attribute. Specify series // start and length using timeIndex and length respectively. @@ -689,8 +685,7 @@ int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute } int EXPORT_OUT_API SMO_getSubcatchAttribute(SMO_Handle p_handle, int periodIndex, - SMO_subcatchAttribute attr, - float** outValueArray, int* length) + SMO_subcatchAttribute attr, float** outValueArray, int* length) // // Purpose: For all subcatchments at given time, get a particular attribute. // @@ -721,8 +716,7 @@ int EXPORT_OUT_API SMO_getSubcatchAttribute(SMO_Handle p_handle, int periodIndex } int EXPORT_OUT_API SMO_getNodeAttribute(SMO_Handle p_handle, int periodIndex, - SMO_nodeAttribute attr, - float** outValueArray, int* length) + SMO_nodeAttribute attr, float** outValueArray, int* length) // // Purpose: For all nodes at given time, get a particular attribute. // @@ -753,8 +747,7 @@ int EXPORT_OUT_API SMO_getNodeAttribute(SMO_Handle p_handle, int periodIndex, } int EXPORT_OUT_API SMO_getLinkAttribute(SMO_Handle p_handle, int periodIndex, - SMO_linkAttribute attr, - float** outValueArray, int* length) + SMO_linkAttribute attr, float** outValueArray, int* length) // // Purpose: For all links at given time, get a particular attribute. // @@ -785,8 +778,7 @@ int EXPORT_OUT_API SMO_getLinkAttribute(SMO_Handle p_handle, int periodIndex, } int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, - SMO_systemAttribute attr, - float** outValueArray, int* length) + SMO_systemAttribute attr, float** outValueArray, int* length) // // Purpose: For the system at given time, get a particular attribute. // @@ -813,8 +805,7 @@ int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, } int EXPORT_OUT_API SMO_getSubcatchResult(SMO_Handle p_handle, int periodIndex, - int subcatchIndex, float** outValueArray, - int* arrayLength) + int subcatchIndex, float** outValueArray, int* arrayLength) // // Purpose: For a subcatchment at given time, get all attributes. // @@ -852,8 +843,7 @@ int EXPORT_OUT_API SMO_getSubcatchResult(SMO_Handle p_handle, int periodIndex, } int EXPORT_OUT_API SMO_getNodeResult(SMO_Handle p_handle, int periodIndex, - int nodeIndex, float** outValueArray, - int* arrayLength) + int nodeIndex, float** outValueArray, int* arrayLength) // // Purpose: For a node at given time, get all attributes. // @@ -893,8 +883,7 @@ int EXPORT_OUT_API SMO_getNodeResult(SMO_Handle p_handle, int periodIndex, } int EXPORT_OUT_API SMO_getLinkResult(SMO_Handle p_handle, int periodIndex, - int linkIndex, float** outValueArray, - int* arrayLength) + int linkIndex, float** outValueArray, int* arrayLength) // // Purpose: For a link at given time, get all attributes. // @@ -935,8 +924,7 @@ int EXPORT_OUT_API SMO_getLinkResult(SMO_Handle p_handle, int periodIndex, } int EXPORT_OUT_API SMO_getSystemResult(SMO_Handle p_handle, int periodIndex, - int dummyIndex, float** outValueArray, - int* arrayLength) + int dummyIndex, float** outValueArray, int* arrayLength) // // Purpose: For the system at given time, get all attributes. // @@ -1110,6 +1098,7 @@ void initElementNames(data_t* p_data) { } double getTimeValue(data_t* p_data, int timeIndex) { + F_OFF offset; double value; @@ -1124,7 +1113,8 @@ double getTimeValue(data_t* p_data, int timeIndex) { } float getSubcatchValue(data_t* p_data, int timeIndex, int subcatchIndex, - SMO_subcatchAttribute attr) { + SMO_subcatchAttribute attr) { + F_OFF offset; float value; @@ -1142,7 +1132,8 @@ float getSubcatchValue(data_t* p_data, int timeIndex, int subcatchIndex, } float getNodeValue(data_t* p_data, int timeIndex, int nodeIndex, - SMO_nodeAttribute attr) { + SMO_nodeAttribute attr) { + F_OFF offset; float value; @@ -1161,7 +1152,8 @@ float getNodeValue(data_t* p_data, int timeIndex, int nodeIndex, } float getLinkValue(data_t* p_data, int timeIndex, int linkIndex, - SMO_linkAttribute attr) { + SMO_linkAttribute attr) { + F_OFF offset; float value; @@ -1181,6 +1173,7 @@ float getLinkValue(data_t* p_data, int timeIndex, int linkIndex, } float getSystemValue(data_t* p_data, int timeIndex, SMO_systemAttribute attr) { + F_OFF offset; float value; diff --git a/tools/make.cmd b/tools/make.cmd index f0c7ac93c..ff05393fc 100644 --- a/tools/make.cmd +++ b/tools/make.cmd @@ -86,8 +86,7 @@ if %TESTING% EQU 1 ( & echo. && ctest -C Debug --output-on-failure ) else ( cmake -G"%GENERATOR%" -DBUILD_TESTS=OFF ..^ - && cmake --build . --config Release --target package^ - && move %PROJECT%-solver*.zip %PROJECT_PATH% + && cmake --build . --config Release --target install ) From 299feec7035b58a7ef704685848e2cab3bab6c9e Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 22 Nov 2019 11:14:54 -0500 Subject: [PATCH 024/266] Adding system to array returned by project size --- outfile/swmm_output.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/outfile/swmm_output.c b/outfile/swmm_output.c index 3b1f48255..29a7b4a65 100644 --- a/outfile/swmm_output.c +++ b/outfile/swmm_output.c @@ -33,7 +33,7 @@ #define RECORDSIZE 4 // Memory alignment 4 byte word size for both int and real #define DATESIZE 8 // Dates are stored as 8 byte word size -#define NELEMENTTYPES 4 // Number of element types +#define NELEMENTTYPES 5 // Number of element types #define MEMCHECK(x) (((x) == NULL) ? 414 : 0) @@ -296,7 +296,8 @@ int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, temp[0] = p_data->Nsubcatch; temp[1] = p_data->Nnodes; temp[2] = p_data->Nlinks; - temp[3] = p_data->Npolluts; + temp[3] = 1; // NSystems + temp[4] = p_data->Npolluts; *elementCount = temp; } From bd548ea1ffb68e6bb7a1d22c25d607b12a2f3521 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 22 Nov 2019 11:56:06 -0500 Subject: [PATCH 025/266] Update swmm_output.h --- outfile/include/swmm_output.h | 52 +++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/outfile/include/swmm_output.h b/outfile/include/swmm_output.h index 7fc7f6e9e..e9e028069 100644 --- a/outfile/include/swmm_output.h +++ b/outfile/include/swmm_output.h @@ -16,7 +16,7 @@ #define MAXELENAME 31 // Max characters in element name // This is an opaque pointer to struct. Do not access variables. -typedef void* SMO_Handle; +typedef void *SMO_Handle; #include "swmm_output_enums.h" @@ -26,37 +26,37 @@ typedef void* SMO_Handle; extern "C" { #endif -int EXPORT_OUT_API SMO_init(SMO_Handle* p_handle); -int EXPORT_OUT_API SMO_close(SMO_Handle* p_handle); -int EXPORT_OUT_API SMO_open(SMO_Handle p_handle, const char* path); -int EXPORT_OUT_API SMO_getVersion(SMO_Handle p_handle, int* version); -int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, int* length); +int EXPORT_OUT_API SMO_init(SMO_Handle *p_handle); +int EXPORT_OUT_API SMO_close(SMO_Handle *p_handle); +int EXPORT_OUT_API SMO_open(SMO_Handle p_handle, const char *path); +int EXPORT_OUT_API SMO_getVersion(SMO_Handle p_handle, int *version); +int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int **elementCount, int *length); -int EXPORT_OUT_API SMO_getUnits(SMO_Handle p_handle, int** unitFlag, int* length); -int EXPORT_OUT_API SMO_getFlowUnits(SMO_Handle p_handle, int* unitFlag); -int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, int* length); -int EXPORT_OUT_API SMO_getStartDate(SMO_Handle p_handle, double* date); -int EXPORT_OUT_API SMO_getTimes(SMO_Handle p_handle, SMO_time code, int* time); -int EXPORT_OUT_API SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, int elementIndex, char** elementName, int* size); +int EXPORT_OUT_API SMO_getUnits(SMO_Handle p_handle, int **unitFlag, int *length); +int EXPORT_OUT_API SMO_getFlowUnits(SMO_Handle p_handle, int *unitFlag); +int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int **unitFlag, int *length); +int EXPORT_OUT_API SMO_getStartDate(SMO_Handle p_handle, double *date); +int EXPORT_OUT_API SMO_getTimes(SMO_Handle p_handle, SMO_time code, int *time); +int EXPORT_OUT_API SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, int elementIndex, char **elementName, int *size); -int EXPORT_OUT_API SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, SMO_subcatchAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim); -int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, SMO_nodeAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim); -int EXPORT_OUT_API SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, SMO_linkAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim); -int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, int startPeriod, int endPeriod, float** outValueSeries, int* dim); +int EXPORT_OUT_API SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, SMO_subcatchAttribute attr, int startPeriod, int endPeriod, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, SMO_nodeAttribute attr, int startPeriod, int endPeriod, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, SMO_linkAttribute attr, int startPeriod, int endPeriod, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, int startPeriod, int endPeriod, float **outValueArray, int *length); -int EXPORT_OUT_API SMO_getSubcatchAttribute(SMO_Handle p_handle, int timeIndex, SMO_subcatchAttribute attr, float** outValueArray, int* length); -int EXPORT_OUT_API SMO_getNodeAttribute(SMO_Handle p_handle, int timeIndex, SMO_nodeAttribute attr, float** outValueArray, int* length); -int EXPORT_OUT_API SMO_getLinkAttribute(SMO_Handle p_handle, int timeIndex, SMO_linkAttribute attr, float** outValueArray, int* length); -int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int timeIndex, SMO_systemAttribute attr, float** outValueArray, int* length); +int EXPORT_OUT_API SMO_getSubcatchAttribute(SMO_Handle p_handle, int timeIndex, SMO_subcatchAttribute attr, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getNodeAttribute(SMO_Handle p_handle, int timeIndex, SMO_nodeAttribute attr, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getLinkAttribute(SMO_Handle p_handle, int timeIndex, SMO_linkAttribute attr, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int timeIndex, SMO_systemAttribute attr, float **outValueArray, int *length); -int EXPORT_OUT_API SMO_getSubcatchResult(SMO_Handle p_handle, int timeIndex, int subcatchIndex, float** outValueArray, int* arrayLength); -int EXPORT_OUT_API SMO_getNodeResult(SMO_Handle p_handle, int timeIndex, int nodeIndex, float** outValueArray, int* arrayLength); -int EXPORT_OUT_API SMO_getLinkResult(SMO_Handle p_handle, int timeIndex, int linkIndex, float** outValueArray, int* arrayLength); -int EXPORT_OUT_API SMO_getSystemResult(SMO_Handle p_handle, int timeIndex, int dummyIndex, float** outValueArray, int* arrayLength); +int EXPORT_OUT_API SMO_getSubcatchResult(SMO_Handle p_handle, int timeIndex, int subcatchIndex, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getNodeResult(SMO_Handle p_handle, int timeIndex, int nodeIndex, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getLinkResult(SMO_Handle p_handle, int timeIndex, int linkIndex, float **outValueArray, int *length); +int EXPORT_OUT_API SMO_getSystemResult(SMO_Handle p_handle, int timeIndex, int dummyIndex, float **outValueArray, int *length); -void EXPORT_OUT_API SMO_free(void** array); +void EXPORT_OUT_API SMO_free(void **array); void EXPORT_OUT_API SMO_clearError(SMO_Handle p_handle_in); -int EXPORT_OUT_API SMO_checkError(SMO_Handle p_handle_in, char** msg_buffer); +int EXPORT_OUT_API SMO_checkError(SMO_Handle p_handle_in, char **msg_buffer); #ifdef __cplusplus } From 2498bc0aac893b830cd7fb3f3c52cda6cf2d5045 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 22 Nov 2019 13:11:14 -0500 Subject: [PATCH 026/266] Updating --- outfile/swmm_output.c | 311 +++++++++++++++++++++--------------------- 1 file changed, 153 insertions(+), 158 deletions(-) diff --git a/outfile/swmm_output.c b/outfile/swmm_output.c index 29a7b4a65..dae5dc8fb 100644 --- a/outfile/swmm_output.c +++ b/outfile/swmm_output.c @@ -80,28 +80,25 @@ typedef struct { //----------------------------------------------------------------------------- // Local functions //----------------------------------------------------------------------------- -void errorLookup(int errcode, char* errmsg, int length); -int validateFile(data_t* p_data); -void initElementNames(data_t* p_data); - -double getTimeValue(data_t* p_data, int timeIndex); -float getSubcatchValue(data_t* p_data, int timeIndex, int subcatchIndex, - SMO_subcatchAttribute attr); -float getNodeValue(data_t* p_data, int timeIndex, int nodeIndex, - SMO_nodeAttribute attr); -float getLinkValue(data_t* p_data, int timeIndex, int linkIndex, - SMO_linkAttribute attr); -float getSystemValue(data_t* p_data, int timeIndex, SMO_systemAttribute attr); - -int _fopen(FILE** f, const char* name, const char* mode); -int _fseek(FILE* stream, F_OFF offset, int whence); -F_OFF _ftell(FILE* stream); - -float* newFloatArray(int n); -int* newIntArray(int n); -char* newCharArray(int n); - -int EXPORT_OUT_API SMO_init(SMO_Handle* p_handle) +void errorLookup(int errcode, char *errmsg, int length); +int validateFile(data_t *p_data); +void initElementNames(data_t *p_data); + +double getTimeValue(data_t *p_data, int timeIndex); +float getSubcatchValue(data_t *p_data, int timeIndex, int subcatchIndex, SMO_subcatchAttribute attr); +float getNodeValue(data_t *p_data, int timeIndex, int nodeIndex, SMO_nodeAttribute attr); +float getLinkValue(data_t *p_data, int timeIndex, int linkIndex, SMO_linkAttribute attr); +float getSystemValue(data_t *p_data, int timeIndex, SMO_systemAttribute attr); + +int _fopen(FILE **f, const char *name, const char *mode); +int _fseek(FILE *stream, F_OFF offset, int whence); +F_OFF _ftell(FILE *stream); + +float *newFloatArray(int n); +int *newIntArray(int n); +char *newCharArray(int n); + +int EXPORT_OUT_API SMO_init(SMO_Handle *p_handle) // Purpose: Initialized pointer for the opaque SMO_Handle. // // Returns: Error code 0 on success, -1 on failure @@ -111,14 +108,14 @@ int EXPORT_OUT_API SMO_init(SMO_Handle* p_handle) // { int errorcode = 0; - data_t* priv_data; + data_t *priv_data; // Allocate memory for private data - priv_data = (data_t*)calloc(1, sizeof(data_t)); + priv_data = (data_t *)calloc(1, sizeof(data_t)); if (priv_data != NULL) { priv_data->error_handle = new_errormanager(&errorLookup); - *p_handle = priv_data; + *p_handle = priv_data; } else errorcode = -1; @@ -131,10 +128,10 @@ int EXPORT_OUT_API SMO_close(SMO_Handle* p_handle) // Purpose: Clean up after and close Output API // { - data_t* p_data; - int i, n, errorcode = 0; + data_t *p_data; + int i, n, errorcode = 0; - p_data = (data_t*)*p_handle; + p_data = (data_t *)*p_handle; if (p_data == NULL) errorcode = -1; @@ -163,7 +160,7 @@ int EXPORT_OUT_API SMO_close(SMO_Handle* p_handle) return errorcode; } -int EXPORT_OUT_API SMO_open(SMO_Handle p_handle, const char* path) +int EXPORT_OUT_API SMO_open(SMO_Handle p_handle, const char *path) // // Purpose: Open the output binary file and read the header. // @@ -171,9 +168,9 @@ int EXPORT_OUT_API SMO_open(SMO_Handle p_handle, const char* path) int err, errorcode = 0; F_OFF offset; - data_t* p_data; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) return -1; @@ -248,7 +245,7 @@ int EXPORT_OUT_API SMO_open(SMO_Handle p_handle, const char* path) return errorcode; } -int EXPORT_OUT_API SMO_getVersion(SMO_Handle p_handle, int* version) +int EXPORT_OUT_API SMO_getVersion(SMO_Handle p_handle, int *version) // // Input: p_handle = pointer to SMO_Handle struct // Output: version SWMM version @@ -258,9 +255,9 @@ int EXPORT_OUT_API SMO_getVersion(SMO_Handle p_handle, int* version) // { int errorcode = 0; - data_t* p_data; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) return -1; @@ -273,8 +270,7 @@ int EXPORT_OUT_API SMO_getVersion(SMO_Handle p_handle, int* version) return set_error(p_data->error_handle, errorcode); } -int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int** elementCount, - int* length) +int EXPORT_OUT_API SMO_getProjectSize(SMO_Handle p_handle, int **elementCount, int *length) // // Purpose: Returns project size. // @@ -355,7 +351,7 @@ int EXPORT_OUT_API SMO_getUnits(SMO_Handle p_handle, int **unitFlag, int *length return set_error(p_data->error_handle, errorcode); } -int EXPORT_OUT_API SMO_getFlowUnits(SMO_Handle p_handle, int* unitFlag) +int EXPORT_OUT_API SMO_getFlowUnits(SMO_Handle p_handle, int *unitFlag) // // Purpose: Returns unit flag for flow. // @@ -385,8 +381,7 @@ int EXPORT_OUT_API SMO_getFlowUnits(SMO_Handle p_handle, int* unitFlag) return set_error(p_data->error_handle, errorcode); } -int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, - int* length) +int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int **unitFlag, int *length) // // Purpose: // Return integer flag representing the units that the given pollutant is @@ -400,12 +395,12 @@ int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, // Args: // pollutantIndex: valid values are 0 to Npolluts-1 { - int errorcode = 0; - int* temp; - F_OFF offset; - data_t* p_data; + int errorcode = 0; + int *temp; + F_OFF offset; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -423,17 +418,17 @@ int EXPORT_OUT_API SMO_getPollutantUnits(SMO_Handle p_handle, int** unitFlag, return set_error(p_data->error_handle, errorcode); } -int EXPORT_OUT_API SMO_getStartDate(SMO_Handle p_handle, double* date) +int EXPORT_OUT_API SMO_getStartDate(SMO_Handle p_handle, double *date) // // Purpose: Returns start date. // { int errorcode = 0; - data_t* p_data; + data_t *p_data; *date = -1.0; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -443,17 +438,17 @@ int EXPORT_OUT_API SMO_getStartDate(SMO_Handle p_handle, double* date) return set_error(p_data->error_handle, errorcode); } -int EXPORT_OUT_API SMO_getTimes(SMO_Handle p_handle, SMO_time code, int* time) +int EXPORT_OUT_API SMO_getTimes(SMO_Handle p_handle, SMO_time code, int *time) // // Purpose: Returns step size and number of periods. // { - int errorcode = 0; - data_t* p_data; + int errorcode = 0; + data_t *p_data; *time = -1; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -474,15 +469,15 @@ int EXPORT_OUT_API SMO_getTimes(SMO_Handle p_handle, SMO_time code, int* time) } int EXPORT_OUT_API SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, - int index, char** name, int* length) + int index, char **name, int *length) // // Purpose: Given an element index returns the element name. // { - int idx = -1, errorcode = 0; - data_t* p_data; + int idx = -1, errorcode = 0; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = 410; @@ -541,17 +536,17 @@ int EXPORT_OUT_API SMO_getElementName(SMO_Handle p_handle, SMO_elementType type, int EXPORT_OUT_API SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, SMO_subcatchAttribute attr, int startPeriod, int endPeriod, - float** outValueSeries, int* dim) + float **outValueArray, int *length) // // Purpose: Get time series results for particular attribute. Specify series // start and length using timeIndex and length respectively. // { - int k, length, errorcode = 0; - float* temp; - data_t* p_data; + int k, len, errorcode = 0; + float *temp; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -562,16 +557,16 @@ int EXPORT_OUT_API SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, errorcode = 422; // Check memory for outValues else if - MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) + MEMCHECK(temp = newFloatArray(len = endPeriod - startPeriod)) errorcode = 411; else { // loop over and build time series - for (k = 0; k < length; k++) + for (k = 0; k < len; k++) temp[k] = getSubcatchValue(p_data, startPeriod + k, subcatchIndex, attr); - *outValueSeries = temp; - *dim = length; + *outValueArray = temp; + *length = len; } return set_error(p_data->error_handle, errorcode); @@ -579,17 +574,17 @@ int EXPORT_OUT_API SMO_getSubcatchSeries(SMO_Handle p_handle, int subcatchIndex, int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, SMO_nodeAttribute attr, int startPeriod, int endPeriod, - float** outValueSeries, int* dim) + float **outValueArray, int *length) // // Purpose: Get time series results for particular attribute. Specify series // start and length using timeIndex and length respectively. // { - int k, length, errorcode = 0; - float* temp; - data_t* p_data; + int k, len, errorcode = 0; + float *temp; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -600,15 +595,15 @@ int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, errorcode = 422; // Check memory for outValues else if - MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) + MEMCHECK(temp = newFloatArray(len = endPeriod - startPeriod)) errorcode = 411; else { // loop over and build time series - for (k = 0; k < length; k++) + for (k = 0; k < len; k++) temp[k] = getNodeValue(p_data, startPeriod + k, nodeIndex, attr); - *outValueSeries = temp; - *dim = length; + *outValueArray = temp; + *length = len; } return set_error(p_data->error_handle, errorcode); @@ -616,17 +611,17 @@ int EXPORT_OUT_API SMO_getNodeSeries(SMO_Handle p_handle, int nodeIndex, int EXPORT_OUT_API SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, SMO_linkAttribute attr, int startPeriod, int endPeriod, - float** outValueSeries, int* dim) + float **outValueArray, int *length) // // Purpose: Get time series results for particular attribute. Specify series // start and length using timeIndex and length respectively. // { - int k, length, errorcode = 0; - float* temp; - data_t* p_data; + int k, len, errorcode = 0; + float *temp; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -637,32 +632,32 @@ int EXPORT_OUT_API SMO_getLinkSeries(SMO_Handle p_handle, int linkIndex, errorcode = 422; // Check memory for outValues else if - MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) + MEMCHECK(temp = newFloatArray(len = endPeriod - startPeriod)) errorcode = 411; else { // loop over and build time series - for (k = 0; k < length; k++) + for (k = 0; k < len; k++) temp[k] = getLinkValue(p_data, startPeriod + k, linkIndex, attr); - *outValueSeries = temp; - *dim = length; + *outValueArray = temp; + *length = len; } return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute attr, - int startPeriod, int endPeriod, float** outValueSeries, int* dim) + int startPeriod, int endPeriod, float **outValueArray, int *length) // // Purpose: Get time series results for particular attribute. Specify series // start and length using timeIndex and length respectively. // { - int k, length, errorcode = 0; - float* temp; - data_t* p_data; + int k, len, errorcode = 0; + float *temp; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -671,31 +666,31 @@ int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute errorcode = 422; // Check memory for outValues else if - MEMCHECK(temp = newFloatArray(length = endPeriod - startPeriod)) + MEMCHECK(temp = newFloatArray(len = endPeriod - startPeriod)) errorcode = 411; else { // loop over and build time series for (k = 0; k < length; k++) temp[k] = getSystemValue(p_data, startPeriod + k, attr); - *outValueSeries = temp; - *dim = length; + *outValueArray = temp; + *length = len; } return set_error(p_data->error_handle, errorcode); } int EXPORT_OUT_API SMO_getSubcatchAttribute(SMO_Handle p_handle, int periodIndex, - SMO_subcatchAttribute attr, float** outValueArray, int* length) + SMO_subcatchAttribute attr, float **outValueArray, int *length) // // Purpose: For all subcatchments at given time, get a particular attribute. // { - int k, errorcode = 0; - float* temp; - data_t* p_data; + int k, errorcode = 0; + float *temp; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -717,16 +712,16 @@ int EXPORT_OUT_API SMO_getSubcatchAttribute(SMO_Handle p_handle, int periodIndex } int EXPORT_OUT_API SMO_getNodeAttribute(SMO_Handle p_handle, int periodIndex, - SMO_nodeAttribute attr, float** outValueArray, int* length) + SMO_nodeAttribute attr, float **outValueArray, int *length) // // Purpose: For all nodes at given time, get a particular attribute. // { - int k, errorcode = 0; - float* temp; - data_t* p_data; + int k, errorcode = 0; + float *temp; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -748,16 +743,16 @@ int EXPORT_OUT_API SMO_getNodeAttribute(SMO_Handle p_handle, int periodIndex, } int EXPORT_OUT_API SMO_getLinkAttribute(SMO_Handle p_handle, int periodIndex, - SMO_linkAttribute attr, float** outValueArray, int* length) + SMO_linkAttribute attr, float **outValueArray, int *length) // // Purpose: For all links at given time, get a particular attribute. // { - int k, errorcode = 0; - float* temp; - data_t* p_data; + int k, errorcode = 0; + float *temp; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -779,16 +774,16 @@ int EXPORT_OUT_API SMO_getLinkAttribute(SMO_Handle p_handle, int periodIndex, } int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, - SMO_systemAttribute attr, float** outValueArray, int* length) + SMO_systemAttribute attr, float **outValueArray, int *length) // // Purpose: For the system at given time, get a particular attribute. // { int errorcode = 0; float temp; - data_t* p_data; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -806,17 +801,17 @@ int EXPORT_OUT_API SMO_getSystemAttribute(SMO_Handle p_handle, int periodIndex, } int EXPORT_OUT_API SMO_getSubcatchResult(SMO_Handle p_handle, int periodIndex, - int subcatchIndex, float** outValueArray, int* arrayLength) + int subcatchIndex, float **outValueArray, int *arrayLength) // // Purpose: For a subcatchment at given time, get all attributes. // { - int errorcode = 0; - float* temp; - F_OFF offset; - data_t* p_data; + int errorcode = 0; + float *temp; + F_OFF offset; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -844,17 +839,17 @@ int EXPORT_OUT_API SMO_getSubcatchResult(SMO_Handle p_handle, int periodIndex, } int EXPORT_OUT_API SMO_getNodeResult(SMO_Handle p_handle, int periodIndex, - int nodeIndex, float** outValueArray, int* arrayLength) + int nodeIndex, float **outValueArray, int *arrayLength) // // Purpose: For a node at given time, get all attributes. // { - int errorcode = 0; - float* temp; - F_OFF offset; - data_t* p_data; + int errorcode = 0; + float *temp; + F_OFF offset; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -884,17 +879,17 @@ int EXPORT_OUT_API SMO_getNodeResult(SMO_Handle p_handle, int periodIndex, } int EXPORT_OUT_API SMO_getLinkResult(SMO_Handle p_handle, int periodIndex, - int linkIndex, float** outValueArray, int* arrayLength) + int linkIndex, float **outValueArray, int *arrayLength) // // Purpose: For a link at given time, get all attributes. // { - int errorcode = 0; - float* temp; - F_OFF offset; - data_t* p_data; + int errorcode = 0; + float *temp; + F_OFF offset; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -925,17 +920,17 @@ int EXPORT_OUT_API SMO_getLinkResult(SMO_Handle p_handle, int periodIndex, } int EXPORT_OUT_API SMO_getSystemResult(SMO_Handle p_handle, int periodIndex, - int dummyIndex, float** outValueArray, int* arrayLength) + int dummyIndex, float **outValueArray, int *arrayLength) // // Purpose: For the system at given time, get all attributes. // { - int errorcode = 0; - float* temp; - F_OFF offset; - data_t* p_data; + int errorcode = 0; + float *temp; + F_OFF offset; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) errorcode = -1; @@ -964,7 +959,7 @@ int EXPORT_OUT_API SMO_getSystemResult(SMO_Handle p_handle, int periodIndex, return set_error(p_data->error_handle, errorcode); } -void EXPORT_OUT_API SMO_free(void** array) +void EXPORT_OUT_API SMO_free(void **array) // // Purpose: Frees memory allocated by API calls // @@ -976,18 +971,18 @@ void EXPORT_OUT_API SMO_free(void** array) } void EXPORT_OUT_API SMO_clearError(SMO_Handle p_handle) { - data_t* p_data; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; clear_error(p_data->error_handle); } -int EXPORT_OUT_API SMO_checkError(SMO_Handle p_handle, char** msg_buffer) { - int errorcode = 0; - char* temp = NULL; - data_t* p_data; +int EXPORT_OUT_API SMO_checkError(SMO_Handle p_handle, char **msg_buffer) { + int errorcode = 0; + char *temp = NULL; + data_t *p_data; - p_data = (data_t*)p_handle; + p_data = (data_t *)p_handle; if (p_data == NULL) return -1; @@ -1002,12 +997,12 @@ int EXPORT_OUT_API SMO_checkError(SMO_Handle p_handle, char** msg_buffer) { return errorcode; } -void errorLookup(int errcode, char* dest_msg, int dest_len) +void errorLookup(int errcode, char *dest_msg, int dest_len) // // Purpose: takes error code returns error message // { - const char* msg; + const char *msg; switch (errcode) { case 10: @@ -1045,7 +1040,7 @@ void errorLookup(int errcode, char* dest_msg, int dest_len) } // Local functions: -int validateFile(data_t* p_data) { +int validateFile(data_t *p_data) { INT4 magic1, magic2, errcode; int errorcode = 0; @@ -1075,7 +1070,7 @@ int validateFile(data_t* p_data) { return errorcode; } -void initElementNames(data_t* p_data) { +void initElementNames(data_t *p_data) { int j, numNames; numNames = @@ -1098,7 +1093,7 @@ void initElementNames(data_t* p_data) { } } -double getTimeValue(data_t* p_data, int timeIndex) { +double getTimeValue(data_t *p_data, int timeIndex) { F_OFF offset; double value; @@ -1113,7 +1108,7 @@ double getTimeValue(data_t* p_data, int timeIndex) { return value; } -float getSubcatchValue(data_t* p_data, int timeIndex, int subcatchIndex, +float getSubcatchValue(data_t *p_data, int timeIndex, int subcatchIndex, SMO_subcatchAttribute attr) { F_OFF offset; @@ -1132,7 +1127,7 @@ float getSubcatchValue(data_t* p_data, int timeIndex, int subcatchIndex, return value; } -float getNodeValue(data_t* p_data, int timeIndex, int nodeIndex, +float getNodeValue(data_t *p_data, int timeIndex, int nodeIndex, SMO_nodeAttribute attr) { F_OFF offset; @@ -1152,7 +1147,7 @@ float getNodeValue(data_t* p_data, int timeIndex, int nodeIndex, return value; } -float getLinkValue(data_t* p_data, int timeIndex, int linkIndex, +float getLinkValue(data_t *p_data, int timeIndex, int linkIndex, SMO_linkAttribute attr) { F_OFF offset; @@ -1173,7 +1168,7 @@ float getLinkValue(data_t* p_data, int timeIndex, int linkIndex, return value; } -float getSystemValue(data_t* p_data, int timeIndex, SMO_systemAttribute attr) { +float getSystemValue(data_t *p_data, int timeIndex, SMO_systemAttribute attr) { F_OFF offset; float value; @@ -1193,7 +1188,7 @@ float getSystemValue(data_t* p_data, int timeIndex, SMO_systemAttribute attr) { return value; } -int _fopen(FILE** f, const char* name, const char* mode) { +int _fopen(FILE **f, const char *name, const char *mode) { // // Purpose: Substitute for fopen_s on platforms where it doesn't exist // Note: fopen_s is part of C++11 standard @@ -1209,7 +1204,7 @@ int _fopen(FILE** f, const char* name, const char* mode) { return ret; } -int _fseek(FILE* stream, F_OFF offset, int whence) +int _fseek(FILE *stream, F_OFF offset, int whence) // // Purpose: Selects platform fseek() for large file support // @@ -1223,7 +1218,7 @@ int _fseek(FILE* stream, F_OFF offset, int whence) return FSEEK64(stream, offset, whence); } -F_OFF _ftell(FILE* stream) +F_OFF _ftell(FILE *stream) // // Purpose: Selects platform ftell() for large file support // @@ -1236,26 +1231,26 @@ F_OFF _ftell(FILE* stream) return FTELL64(stream); } -float* newFloatArray(int n) +float *newFloatArray(int n) // // Warning: Caller must free memory allocated by this function. // { - return (float*)malloc((n) * sizeof(float)); + return (float *)malloc((n) * sizeof(float)); } -int* newIntArray(int n) +int *newIntArray(int n) // // Warning: Caller must free memory allocated by this function. // { - return (int*)malloc((n) * sizeof(int)); + return (int *)malloc((n) * sizeof(int)); } -char* newCharArray(int n) +char *newCharArray(int n) // // Warning: Caller must free memory allocated by this function. // { - return (char*)malloc((n) * sizeof(char)); + return (char *)malloc((n) * sizeof(char)); } From 1ef367fb6f615779fbdffd199aaacbe85e7ab9d0 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 22 Nov 2019 15:01:05 -0500 Subject: [PATCH 027/266] Update app-config.cmd Using git commit hash for version --- tools/app-config.cmd | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/app-config.cmd b/tools/app-config.cmd index f1e48302d..a53c56dc8 100644 --- a/tools/app-config.cmd +++ b/tools/app-config.cmd @@ -14,7 +14,6 @@ :: 1 - absolute path to test executable (valid path seperator for nrtest is "/") :: 2 - (platform) :: 3 - (build identifier for SUT) -:: 4 - (commit hash string) :: @echo off @@ -36,9 +35,9 @@ if [%2]==[] ( set "PLATFORM=unknown" if [%3]==[] ( set "BUILD_ID=unknown" ) else ( set "BUILD_ID=%~3" ) -if "%4" == "unknown" ( - for /F "tokens=1" %%v in ( '%ABS_BUILD_PATH%/%TEST_CMD% --version' ) do ( set "VERSION=%%v" ) -) else ( set "VERSION=%~4" ) +:: determine version +for /F "tokens=1" %%v in ( 'git rev-parse --short HEAD' ) do ( set "VERSION=%%v" ) +if not defined VERSION ( echo "ERROR: VERSION could not be determined" & exit /B 1 ) echo { From 41b2cd66c0b7cf86d22a65106f20ffc277fb3a6a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 22 Nov 2019 17:23:23 -0500 Subject: [PATCH 028/266] Reorganizing repository Moving outfile library into source folder --- {outfile => src/outfile}/CMakeLists.txt | 0 {outfile => src/outfile}/errormanager.c | 0 {outfile => src/outfile}/errormanager.h | 0 .../outfile}/include/swmm_output.h | 0 .../outfile}/include/swmm_output_enums.h | 0 {outfile => src/outfile}/messages.h | 0 {outfile => src/outfile}/swmm_output.c | 0 src/solver/CMakeLists.txt | 21 +++++++++---------- {include => src/solver/include}/swmm5.h | 0 9 files changed, 10 insertions(+), 11 deletions(-) rename {outfile => src/outfile}/CMakeLists.txt (100%) rename {outfile => src/outfile}/errormanager.c (100%) rename {outfile => src/outfile}/errormanager.h (100%) rename {outfile => src/outfile}/include/swmm_output.h (100%) rename {outfile => src/outfile}/include/swmm_output_enums.h (100%) rename {outfile => src/outfile}/messages.h (100%) rename {outfile => src/outfile}/swmm_output.c (100%) rename {include => src/solver/include}/swmm5.h (100%) diff --git a/outfile/CMakeLists.txt b/src/outfile/CMakeLists.txt similarity index 100% rename from outfile/CMakeLists.txt rename to src/outfile/CMakeLists.txt diff --git a/outfile/errormanager.c b/src/outfile/errormanager.c similarity index 100% rename from outfile/errormanager.c rename to src/outfile/errormanager.c diff --git a/outfile/errormanager.h b/src/outfile/errormanager.h similarity index 100% rename from outfile/errormanager.h rename to src/outfile/errormanager.h diff --git a/outfile/include/swmm_output.h b/src/outfile/include/swmm_output.h similarity index 100% rename from outfile/include/swmm_output.h rename to src/outfile/include/swmm_output.h diff --git a/outfile/include/swmm_output_enums.h b/src/outfile/include/swmm_output_enums.h similarity index 100% rename from outfile/include/swmm_output_enums.h rename to src/outfile/include/swmm_output_enums.h diff --git a/outfile/messages.h b/src/outfile/messages.h similarity index 100% rename from outfile/messages.h rename to src/outfile/messages.h diff --git a/outfile/swmm_output.c b/src/outfile/swmm_output.c similarity index 100% rename from outfile/swmm_output.c rename to src/outfile/swmm_output.c diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 8ccf900f2..acb48cd47 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -2,22 +2,15 @@ # CMakeLists.txt - CMake configuration file for swmm-solver/library # # Created: July 11, 2019 +# Modified: Nov 22, 2019 # # Modified by: Michael E. Tryby -# US EPA ORD/NRMRL +# US EPA ORD/CESER # -# Loads settings for OpenMP and append any OpenMP compiler flags. -find_package(OpenMP) -if(OPENMP_FOUND) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") -endif() - # configure file groups set(SWMM_PUBLIC_HEADERS - ${PROJECT_SOURCE_DIR}/include/swmm5.h + include/swmm5.h ) file(GLOB SWMM_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c *.h) @@ -26,9 +19,15 @@ add_library(swmm5 SHARED ${SWMM_SOURCES} ) + +find_package(OpenMP) +if(OpenMP_C_FOUND) + target_link_libraries(swmm5 PUBLIC OpenMP::OpenMP_C) +endif() + target_include_directories(swmm5 PUBLIC - $ + $ $ ) diff --git a/include/swmm5.h b/src/solver/include/swmm5.h similarity index 100% rename from include/swmm5.h rename to src/solver/include/swmm5.h From f87cfbb7f75b9c04452812ec871ffac0f0197432 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 22 Nov 2019 17:24:12 -0500 Subject: [PATCH 029/266] Update CMakeLists.txt --- CMakeLists.txt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bfb0bd0db..9af78d7e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ # US EPA ORD/NRMRL # -cmake_minimum_required (VERSION 3.0) +cmake_minimum_required (VERSION 3.13) if("${CMAKE_BINARY_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") message(FATAL_ERROR "In-source builds are disabled.") @@ -20,15 +20,14 @@ project(swmm-solver ) +# Searches for and create install rules for vcruntime.dll, msvcp.dll, etc. +include(InstallRequiredSystemLibraries) + # Append local dir to module search path list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) # Sets the position independent code property for all targets -SET(CMAKE_POSITION_INDEPENDENT_CODE ON) - - -# Searches for and create install rules for vcruntime.dll, msvcp.dll, etc. -include(InstallRequiredSystemLibraries) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) # Sets default install prefix when cmakecache is initialized for first time if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) @@ -47,7 +46,7 @@ option(BUILD_TESTS "Build component tests (requires Boost)" OFF) # Add project subdirectories -add_subdirectory(outfile) +add_subdirectory(src/outfile) add_subdirectory(src/solver) add_subdirectory(src/run) From 9b0394b696331b76aad7f3ddd6bbb17f01a92210 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 25 Nov 2019 10:42:11 -0500 Subject: [PATCH 030/266] Configuring cmake for OpenMP Simplifies OpenMP settings Installs vcomp.dll Updates headers --- CMakeLists.txt | 15 +++++++++------ src/outfile/CMakeLists.txt | 6 +++++- src/solver/CMakeLists.txt | 16 +++++++++++----- tests/outfile/CMakeLists.txt | 6 +++++- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9af78d7e1..fdd8bd99a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,13 @@ # CMakeLists.txt - CMake configuration file for swmm-solver # # Created: July 11, 2019 +# Modified: Nov 25, 2019 # -# Modified by: Michael E. Tryby -# US EPA ORD/NRMRL +# Author: Michael E. Tryby +# US EPA ORD/CESER # + cmake_minimum_required (VERSION 3.13) if("${CMAKE_BINARY_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") @@ -19,10 +21,6 @@ project(swmm-solver LANGUAGES C CXX ) - -# Searches for and create install rules for vcruntime.dll, msvcp.dll, etc. -include(InstallRequiredSystemLibraries) - # Append local dir to module search path list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) @@ -67,6 +65,11 @@ install( ) +# Create install rules for vcruntime.dll, msvcp.dll, vcomp.dll etc. +set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) +include(InstallRequiredSystemLibraries) + + # Configure CPack driven installer package set(CPACK_GENERATOR "ZIP") set(CPACK_PACKAGE_VENDOR "US_EPA") diff --git a/src/outfile/CMakeLists.txt b/src/outfile/CMakeLists.txt index 9637976a1..7fd3cd20c 100644 --- a/src/outfile/CMakeLists.txt +++ b/src/outfile/CMakeLists.txt @@ -1,7 +1,11 @@ # # CMakeLists.txt - CMake configuration file for swmm/outfile # - +# Created: July 11, 2019 +# +# Author: Michael E. Tryby +# US EPA ORD/CESER +# # configure file groups set(SWMM_OUT_PUBLIC_HEADERS diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index acb48cd47..b059dcba8 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -2,12 +2,16 @@ # CMakeLists.txt - CMake configuration file for swmm-solver/library # # Created: July 11, 2019 -# Modified: Nov 22, 2019 +# Modified: Nov 25, 2019 # -# Modified by: Michael E. Tryby -# US EPA ORD/CESER +# Author: Michael E. Tryby +# US EPA ORD/CESER # + +find_package(OpenMP) + + # configure file groups set(SWMM_PUBLIC_HEADERS include/swmm5.h @@ -20,9 +24,11 @@ add_library(swmm5 ${SWMM_SOURCES} ) -find_package(OpenMP) if(OpenMP_C_FOUND) - target_link_libraries(swmm5 PUBLIC OpenMP::OpenMP_C) + target_link_libraries(swmm5 + PUBLIC + OpenMP::OpenMP_C + ) endif() target_include_directories(swmm5 diff --git a/tests/outfile/CMakeLists.txt b/tests/outfile/CMakeLists.txt index 64dcea739..4ad2f47bb 100644 --- a/tests/outfile/CMakeLists.txt +++ b/tests/outfile/CMakeLists.txt @@ -1,7 +1,11 @@ # # CMakeLists.txt - CMake configuration file for tests/outfile # - +# Created: July 11, 2019 +# +# Author: Michael E. Tryby +# US EPA ORD/CESER +# add_executable(test_output test_output.cpp From 80315174607c25f8d5d2ee24e9f35abd8011e64a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 25 Nov 2019 10:42:39 -0500 Subject: [PATCH 031/266] Update app-config.cmd Add check for git --- tools/app-config.cmd | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/app-config.cmd b/tools/app-config.cmd index a53c56dc8..09ce04daf 100644 --- a/tools/app-config.cmd +++ b/tools/app-config.cmd @@ -7,6 +7,9 @@ :: Author: Michael E. Tryby :: US EPA - ORD/CESER :: +:: Requires: +:: git +:: :: Environment Variables: :: PROJECT :: @@ -19,6 +22,12 @@ @echo off setlocal + +:: check requirements +where git > nul +if %ERRORLEVEL% NEQ 0 ( echo "ERROR: git not installed" & exit /B 1 ) + +:: check environment if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) From 7c0b7d12629303f3abf5e698b27c5fa575456568 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 25 Nov 2019 11:20:49 -0500 Subject: [PATCH 032/266] Update test_output.cpp Updating test_getProjectSize() --- tests/outfile/test_output.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/outfile/test_output.cpp b/tests/outfile/test_output.cpp index da15e24d8..43bc73ad3 100644 --- a/tests/outfile/test_output.cpp +++ b/tests/outfile/test_output.cpp @@ -149,8 +149,8 @@ BOOST_FIXTURE_TEST_CASE(test_getProjectSize, Fixture) { test.assign(i_array, i_array + array_dim); // subcatchs, nodes, links, pollutants - const int ref_dim = 4; - int ref_array[ref_dim] = {8, 14, 13, 2}; + const int ref_dim = 5; + int ref_array[ref_dim] = {8, 14, 13, 1, 2}; std::vector ref; ref.assign(ref_array, ref_array + ref_dim); From d70d754f0dd281ab6fd3957e5c39faba26bc14d3 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 26 Nov 2019 13:49:10 -0500 Subject: [PATCH 033/266] Adding error checks Adding error check for "cd" command --- tools/before-nrtest.cmd | 1 + tools/make.cmd | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/before-nrtest.cmd b/tools/before-nrtest.cmd index 0dcdd6f43..3a336209b 100644 --- a/tools/before-nrtest.cmd +++ b/tools/before-nrtest.cmd @@ -84,6 +84,7 @@ if exist %TEST_HOME% ( mkdir %TEST_HOME% if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to make %TEST_HOME% dir" & exit /B 1 ) cd %TEST_HOME% +if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to cd %TEST_HOME% dir" & exit /B 1 ) :: retrieve nrtest cases and benchmark results for regression testing diff --git a/tools/make.cmd b/tools/make.cmd index ff05393fc..3fabcc683 100644 --- a/tools/make.cmd +++ b/tools/make.cmd @@ -80,6 +80,8 @@ if exist %BUILD_HOME% ( :: perform the build cd %BUILD_HOME% +if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to cd %BUILD_HOME% dir" & exit /B 1 ) + if %TESTING% EQU 1 ( cmake -G"%GENERATOR%" -DBUILD_TESTS=ON -DBOOST_ROOT=C:\local\boost_1_67_0 ..^ && cmake --build . --config Debug^ @@ -103,4 +105,4 @@ if not defined PLATFORM ( echo "ERROR: PLATFORM could not be determined" & exit :: return to users current dir -cd %CUR_DIR% +:: cd %CUR_DIR% From 000f5fb70ff3c636205152ed3a7af30372d84c39 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 Nov 2019 10:37:39 -0500 Subject: [PATCH 034/266] Updating requirements.txt Renaming to requirements-win.txt Updating references to swmm.output and nrtest-swmm packages --- tools/requirements-win.txt | 22 ++++++++++++++++++++++ tools/requirements.txt | 22 ---------------------- 2 files changed, 22 insertions(+), 22 deletions(-) create mode 100644 tools/requirements-win.txt delete mode 100644 tools/requirements.txt diff --git a/tools/requirements-win.txt b/tools/requirements-win.txt new file mode 100644 index 000000000..0f18bb0ee --- /dev/null +++ b/tools/requirements-win.txt @@ -0,0 +1,22 @@ +# +# requirements-win.txt - Python requirements for running nrtest on Win32/Win64 +# +# Date Created: 10/17/2019 +# Date Updated: 11/26/2019 +# +# Author: Michael E. Tryby +# US EPA ORD/CESER +# +# Useful for configuring a python environment to run nrtests on swmm. +# +# usage: +# pip install -r tools/requirements-win.txt +# + +nrtest + +-f https://github.com/michaeltryby/swmm-python/releases/download/v0.3.0-dev3/swmm.output-0.4.0.dev3-cp36-cp36m-win_amd64.whl +swmm.output + +-f https://github.com/michaeltryby/swmm-python/releases/download/v0.3.0-dev3/nrtest_swmm-0.5.0-py3-none-any.whl +nrtest-swmm diff --git a/tools/requirements.txt b/tools/requirements.txt deleted file mode 100644 index e3b961478..000000000 --- a/tools/requirements.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# requirements.txt -# -# Date Created: 10/17/2019 -# Date Modified: -# -# Author: Michael E. Tryby -# US EPA ORD/CESER -# -# Useful for configuring a python environment to run nrtests on swmm. -# -# usage: -# pip install -r tools/requirements.txt -# - -nrtests - --f https://github.com/michaeltryby/swmm-python/releases/download/v0.3.0-dev2/swmm.output-0.4.0.dev2-cp36-cp36m-win_amd64.whl -swmm.output - - -#nrtest-swmm From 40a2db9f48228f1193e3f6bcc45e51edcfa9f161 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 2 Dec 2019 16:30:09 -0500 Subject: [PATCH 035/266] Adding documentation Adding build and test documentation --- Build.md | 32 +++++++++++++++++++++++++++ tests/Unit_Testing.md | 35 ++++++++++++++++++++++++++++++ tools/Reg_Testing.md | 50 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 Build.md create mode 100644 tests/Unit_Testing.md create mode 100644 tools/Reg_Testing.md diff --git a/Build.md b/Build.md new file mode 100644 index 000000000..b9352e45d --- /dev/null +++ b/Build.md @@ -0,0 +1,32 @@ + + + +## Building SWMM Locally on Windows + + +### Dependencies + +Before the project can be built the required dependencies must be installed. + +**Summary of Build Dependencies: Windows** + + - Build + - Build Tools for Visual Studio 2017 + - CMake 3.13 + + +### Build + +SWMM can be built with one simple command. +``` +\> cd swmm +\swmm>tools\make.cmd +``` diff --git a/tests/Unit_Testing.md b/tests/Unit_Testing.md new file mode 100644 index 000000000..1ab3bd404 --- /dev/null +++ b/tests/Unit_Testing.md @@ -0,0 +1,35 @@ + + + +## Unit Testing SWMM locally on Windows + +### Dependencies + +Before the project can be built and tested the required dependencies must be installed. + +**Summary of Build Dependencies: Windows** + + - Build + - Build Tools for Visual Studio 2017 + - CMake 3.13 + + - Unit Test + - Boost 1.67.0 (installed in default location "C:\\local") + + + +### Build and Unit Test + +SWMM can be built and unit tests run with one simple command. +``` +\> cd swmm +\swmm>scripts\make.cmd /t +``` diff --git a/tools/Reg_Testing.md b/tools/Reg_Testing.md new file mode 100644 index 000000000..b6d31afec --- /dev/null +++ b/tools/Reg_Testing.md @@ -0,0 +1,50 @@ + + +## Regression Testing SWMM locally on Windows + + +### Dependencies + +Before the project can be built and tested the required dependencies must be installed. + +**Summary of Build Dependencies: Windows** + + - Build + - Build Tools for Visual Studio 2017 + - CMake 3.13 + + - Regression Test + - Python 3.6 64 bit + - curl + - git + - 7z + +Once Python is present, the following command installs the required packages for regression testing. +``` +\> cd swmm +\swmm>pip install -r tools\requirements-win.txt +``` + + +### Build + +EPANET can be built with one simple command. +``` +\swmm>tools\make.cmd +``` + + +### Regression Test + +This command runs regression tests for the local build and compares them to the latest benchmark. +``` +\swmm>tools\before-nrtest.cmd && tools\run-nrtest.cmd +``` From d935212eb5ee57562bf499bd050ae2a8c979813e Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 2 Dec 2019 16:31:00 -0500 Subject: [PATCH 036/266] Update Unit_Testing.md --- tests/Unit_Testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit_Testing.md b/tests/Unit_Testing.md index 1ab3bd404..65869798d 100644 --- a/tests/Unit_Testing.md +++ b/tests/Unit_Testing.md @@ -31,5 +31,5 @@ Before the project can be built and tested the required dependencies must be ins SWMM can be built and unit tests run with one simple command. ``` \> cd swmm -\swmm>scripts\make.cmd /t +\swmm>tools\make.cmd /t ``` From c588896d39facbed812775026583884d49d28d56 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 6 Feb 2020 14:59:38 -0500 Subject: [PATCH 037/266] Changes to build system --- CMakeLists.txt | 10 +++++++++- src/outfile/CMakeLists.txt | 2 -- src/solver/CMakeLists.txt | 16 +++++++++------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fdd8bd99a..bc9b5f36a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,15 @@ install( swmm5-config.cmake ) - +install( + EXPORT + swmm-outputTargets + DESTINATION + "${CONFIG_DIST}" + FILE + swmm-output-config.cmake + ) + # Create install rules for vcruntime.dll, msvcp.dll, vcomp.dll etc. set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) include(InstallRequiredSystemLibraries) diff --git a/src/outfile/CMakeLists.txt b/src/outfile/CMakeLists.txt index 7fd3cd20c..5c5d2ed96 100644 --- a/src/outfile/CMakeLists.txt +++ b/src/outfile/CMakeLists.txt @@ -26,8 +26,6 @@ target_include_directories(swmm-output PUBLIC $ $ - PRIVATE - ${CMAKE_SOURCE_DIR}/ ) include(GenerateExportHeader) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index b059dcba8..e9453d7e5 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -17,19 +17,21 @@ set(SWMM_PUBLIC_HEADERS include/swmm5.h ) -file(GLOB SWMM_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c *.h) +file(GLOB + SWMM_SOURCES + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c *.h + ) add_library(swmm5 SHARED ${SWMM_SOURCES} ) -if(OpenMP_C_FOUND) - target_link_libraries(swmm5 - PUBLIC - OpenMP::OpenMP_C - ) -endif() +target_link_libraries(swmm5 + PUBLIC + $<$>>:m> + $<$:OpenMP::OpenMP_C> + ) target_include_directories(swmm5 PUBLIC From 8b761a68ab5cae080d20a20f5c69bbade0825d95 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 26 Mar 2020 16:43:18 -0400 Subject: [PATCH 038/266] Update README.md Close #37 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3d27255f3..95aa9f7fb 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ORD Stormwater Management Model (aka "SWMM") ## Introduction -This is the official SWMM source code repository maintained by US EPA ORD, NRMRL, Water Supply and Water Resources Division located in Cincinnati, Ohio. +This is the official SWMM source code repository maintained by US EPA Office of Research and Development, Center For Environmental Solutions & Emergency Response, Water Infrastructure Division located in Cincinnati, Ohio. SWMM is a dynamic hydrology-hydraulic water quality simulation model. It is used for single event or long-term (continuous) simulation of runoff quantity and quality from primarily urban areas. SWMM source code is written in the C Programming Language and released in the Public Domain. From 9e6c9ca63be929c2a35ded8d5fd4f575f7b4b9f1 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 27 Mar 2020 11:10:08 -0400 Subject: [PATCH 039/266] Updating .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index a441c732a..48b818a30 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,8 @@ .metadata/ .settings/ +src/outfile/include/*_export.h +src/solver/include/*_export.h + build/ nrtests/ From a97c3d6dde99b90c859ea0dc108272b61bc68c61 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 27 Mar 2020 11:16:42 -0400 Subject: [PATCH 040/266] Updating .gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 21ff70489..44f9bd3cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ -buildprod*/ +build/ + +src/outfile/include/*_export.h +src/solver/include/*_export.h # Eclipse Stuff .metadata/ From 273f6eebe68f3c71969ec8e7d1bf120850edb976 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 27 Mar 2020 12:03:34 -0400 Subject: [PATCH 041/266] Fixing build warnings on Clang --- src/outfile/swmm_output.c | 2 +- src/run/main.c | 8 ++- src/solver/lid.c | 130 +++++++++++++++++++------------------- src/solver/swmm5.c | 16 ++--- 4 files changed, 79 insertions(+), 77 deletions(-) diff --git a/src/outfile/swmm_output.c b/src/outfile/swmm_output.c index dae5dc8fb..82bbbcf6c 100644 --- a/src/outfile/swmm_output.c +++ b/src/outfile/swmm_output.c @@ -670,7 +670,7 @@ int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute errorcode = 411; else { // loop over and build time series - for (k = 0; k < length; k++) + for (k = 0; k < len; k++) temp[k] = getSystemValue(p_data, startPeriod + k, attr); *outValueArray = temp; diff --git a/src/run/main.c b/src/run/main.c index b4af1f112..0d050cf69 100644 --- a/src/run/main.c +++ b/src/run/main.c @@ -10,7 +10,9 @@ // to be run with swmm5.dll. #include +#include #include + #include "swmm5.h" int main(int argc, char *argv[]) @@ -35,7 +37,7 @@ int main(int argc, char *argv[]) int msgLen = 127; time_t start; double runTime; - + version = swmm_getVersion(); vMajor = version / 10000; vMinor = (version - 10000 * vMajor) / 1000; @@ -94,10 +96,10 @@ int main(int argc, char *argv[]) } // --- Use the code below if you need to keep the console window visible -/* +/* printf(" Press Enter to continue..."); getchar(); */ return 0; -} \ No newline at end of file +} diff --git a/src/solver/lid.c b/src/solver/lid.c index 4dcf02a1d..d760afdc4 100644 --- a/src/solver/lid.c +++ b/src/solver/lid.c @@ -69,7 +69,7 @@ // // Build 5.1.013: // - Support added for LID units treating pervious area runoff. -// - Support added for open/closed head levels and multiplier v. head +// - Support added for open/closed head levels and multiplier v. head // control curve for underdrain flow. // - Support added for unclogging permeable pavement at fixed intervals. // - Support added for pollutant removal in underdrain flow. @@ -106,7 +106,7 @@ enum LidLayerTypes { REMOVALS}; // pollutant removals //(5.1.013) //// Note: DRAINMAT must be placed before DRAIN so the two keywords can -/// be distinguished from one another when parsing a line of input. +/// be distinguished from one another when parsing a line of input. char* LidLayerWords[] = {"SURFACE", "SOIL", "STORAGE", "PAVEMENT", "DRAINMAT", "DRAIN", @@ -162,7 +162,7 @@ static double MaxNativeInfil; // native soil infil. rate limit (ft/s) //----------------------------------------------------------------------------- // Imported Variables (from SUBCATCH.C) //----------------------------------------------------------------------------- -// Volumes (ft3) for a subcatchment over a time step +// Volumes (ft3) for a subcatchment over a time step extern double Vevap; // evaporation extern double Vpevap; // pervious area evaporation extern double Vinfil; // non-LID infiltration @@ -262,10 +262,10 @@ void lid_create(int lidCount, int subcatchCount) return; } } - + //... initialize LID groups for (j = 0; j < GroupCount; j++) LidGroups[j] = NULL; - + //... create LID objects if ( LidCount == 0 ) return; LidProcs = (TLidProc *) calloc(LidCount, sizeof(TLidProc)); @@ -298,7 +298,7 @@ void lid_create(int lidCount, int subcatchCount) { // ErrorCode = ERR_MEMORY; // return; // - } // + } // } } @@ -559,7 +559,7 @@ int addLidUnit(int j, int k, int n, double x[], char* fname, //... open report file if it was supplied if ( fname != NULL ) { - if ( !createLidRptFile(lidUnit, fname) ) + if ( !createLidRptFile(lidUnit, fname) ) return error_setInpError(ERR_RPT_FILE, fname); } return 0; @@ -570,7 +570,7 @@ int addLidUnit(int j, int k, int n, double x[], char* fname, int createLidRptFile(TLidUnit* lidUnit, char* fname) { TLidRptFile* rptFile; - + rptFile = (TLidRptFile *) malloc(sizeof(TLidRptFile)); if ( rptFile == NULL ) return 0; lidUnit->rptFile = rptFile; @@ -585,7 +585,7 @@ int readSurfaceData(int j, char* toks[], int ntoks) // // Purpose: reads surface layer data for a LID process from line of input // data file -// Input: j = LID process index +// Input: j = LID process index // toks = array of string tokens // ntoks = number of tokens // Output: returns error code @@ -603,7 +603,7 @@ int readSurfaceData(int j, char* toks[], int ntoks) if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) return error_setInpError(ERR_NUMBER, toks[i]); } - if ( x[1] >= 1.0 ) return error_setInpError(ERR_NUMBER, toks[3]); + if ( x[1] >= 1.0 ) return error_setInpError(ERR_NUMBER, toks[3]); if ( x[0] == 0.0 ) x[1] = 0.0; LidProcs[j].surface.thickness = x[0] / UCF(RAINDEPTH); @@ -620,7 +620,7 @@ int readPavementData(int j, char* toks[], int ntoks) // // Purpose: reads pavement layer data for a LID process from line of input // data file -// Input: j = LID process index +// Input: j = LID process index // toks = array of string tokens // ntoks = number of tokens // Output: returns error code @@ -673,7 +673,7 @@ int readSoilData(int j, char* toks[], int ntoks) // // Purpose: reads soil layer data for a LID process from line of input // data file -// Input: j = LID process index +// Input: j = LID process index // toks = array of string tokens // ntoks = number of tokens // Output: returns error code @@ -707,13 +707,13 @@ int readStorageData(int j, char* toks[], int ntoks) // // Purpose: reads drainage layer data for a LID process from line of input // data file -// Input: j = LID process index +// Input: j = LID process index // toks = array of string tokens // ntoks = number of tokens // Output: returns error code // // Format of data is: -// LID_ID STORAGE Thickness VoidRatio Ksat ClogFactor +// LID_ID STORAGE Thickness VoidRatio Ksat ClogFactor // { int i; @@ -737,14 +737,14 @@ int readStorageData(int j, char* toks[], int ntoks) LidProcs[j].storage.clogFactor = x[3]; return 0; } - + //============================================================================= int readDrainData(int j, char* toks[], int ntoks) // // Purpose: reads underdrain data for a LID process from line of input // data file -// Input: j = LID process index +// Input: j = LID process index // toks = array of string tokens // ntoks = number of tokens // Output: returns error code @@ -761,7 +761,7 @@ int readDrainData(int j, char* toks[], int ntoks) for (i = 0; i < 6; i++) x[i] = 0.0; //(5.1.013) for (i = 2; i < 8; i++) // { - if ( ntoks > i && ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) //(5.1.013) + if ( ((ntoks > i) && (! getDouble(toks[i], &x[i-2]))) || (x[i-2] < 0.0) ) //(5.1.013) return error_setInpError(ERR_NUMBER, toks[i]); } @@ -782,14 +782,14 @@ int readDrainData(int j, char* toks[], int ntoks) LidProcs[j].drain.qCurve = i; // return 0; } - + //============================================================================= int readDrainMatData(int j, char* toks[], int ntoks) // // Purpose: reads drainage mat data for a LID process from line of input // data file -// Input: j = LID process index +// Input: j = LID process index // toks = array of string tokens // ntoks = number of tokens // Output: returns error code @@ -825,7 +825,7 @@ int readRemovalsData(int j, char* toks[], int ntoks) // // Purpose: reads pollutant removal data for a LID process from line of input // data file -// Input: j = LID process index +// Input: j = LID process index // toks = array of string tokens // ntoks = number of tokens // Output: returns error code @@ -874,7 +874,7 @@ void lid_writeSummary() TLidUnit* lidUnit; TLidList* lidList; TLidGroup lidGroup; - + fprintf(Frpt.file, "\n"); fprintf(Frpt.file, "\n"); fprintf(Frpt.file, "\n *******************"); @@ -914,7 +914,7 @@ void lid_writeSummary() void lid_validate() // // Purpose: validates LID process and group parameters. -// Input: none +// Input: none // Output: none // { @@ -928,7 +928,7 @@ void lid_validate() void validateLidProc(int j) // // Purpose: validates LID process parameters. -// Input: j = LID process index +// Input: j = LID process index // Output: none // { @@ -949,7 +949,7 @@ void validateLidProc(int j) if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; break; case GREEN_ROOF: - if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; + if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; if ( LidProcs[j].drainMat.thickness <= 0.0) layerMissing = TRUE; break; case POROUS_PAVEMENT: @@ -968,8 +968,8 @@ void validateLidProc(int j) //... check pavement layer parameters if ( LidProcs[j].lidType == POROUS_PAVEMENT ) { - if ( LidProcs[j].pavement.thickness <= 0.0 - || LidProcs[j].pavement.kSat <= 0.0 + if ( LidProcs[j].pavement.thickness <= 0.0 + || LidProcs[j].pavement.kSat <= 0.0 || LidProcs[j].pavement.voidFrac <= 0.0 || LidProcs[j].pavement.voidFrac > 1.0 || LidProcs[j].pavement.impervFrac > 1.0 ) @@ -984,7 +984,7 @@ void validateLidProc(int j) //... check soil layer parameters if ( LidProcs[j].soil.thickness > 0.0 ) { - if ( LidProcs[j].soil.porosity <= 0.0 + if ( LidProcs[j].soil.porosity <= 0.0 || LidProcs[j].soil.fieldCap >= LidProcs[j].soil.porosity || LidProcs[j].soil.wiltPoint >= LidProcs[j].soil.fieldCap || LidProcs[j].soil.kSat <= 0.0 @@ -1008,9 +1008,9 @@ void validateLidProc(int j) } } - //... if no storage layer adjust void fraction and drain offset + //... if no storage layer adjust void fraction and drain offset else - { + { LidProcs[j].storage.voidFrac = 1.0; LidProcs[j].drain.offset = 0.0; } @@ -1027,7 +1027,7 @@ void validateLidProc(int j) //... compute the surface layer's overland flow constant (alpha) if ( LidProcs[j].lidType == VEG_SWALE ) { - if ( LidProcs[j].surface.roughness * + if ( LidProcs[j].surface.roughness * LidProcs[j].surface.surfSlope <= 0.0 || LidProcs[j].surface.thickness == 0.0 ) @@ -1036,7 +1036,7 @@ void validateLidProc(int j) strcat(Msg, ERR_SWALE_SURF); report_writeErrorMsg(ERR_LID_PARAMS, Msg); } - else LidProcs[j].surface.alpha = + else LidProcs[j].surface.alpha = 1.49 * sqrt(LidProcs[j].surface.surfSlope) / LidProcs[j].surface.roughness; } @@ -1061,7 +1061,7 @@ void validateLidProc(int j) //... convert clogging factors to void volume basis if ( LidProcs[j].pavement.thickness > 0.0 ) { - LidProcs[j].pavement.clogFactor *= + LidProcs[j].pavement.clogFactor *= LidProcs[j].pavement.thickness * LidProcs[j].pavement.voidFrac * (1.0 - LidProcs[j].pavement.impervFrac); } @@ -1094,7 +1094,7 @@ void validateLidProc(int j) LidProcs[j].storage.kSat = 0.0; } - //... set storage layer parameters of a green roof + //... set storage layer parameters of a green roof if ( LidProcs[j].lidType == GREEN_ROOF ) { LidProcs[j].storage.thickness = LidProcs[j].drainMat.thickness; @@ -1109,7 +1109,7 @@ void validateLidProc(int j) void validateLidGroup(int j) // // Purpose: validates properties of LID units grouped in a subcatchment. -// Input: j = subcatchment index +// Input: j = subcatchment index // Output: returns 1 if data are valid, 0 if not // { @@ -1151,7 +1151,7 @@ void validateLidGroup(int j) report_writeErrorMsg(ERR_LID_PARAMS, Msg); } } - + //... assign vegetative swale infiltration parameters if ( LidProcs[k].lidType == VEG_SWALE ) { @@ -1208,7 +1208,7 @@ void validateLidGroup(int j) void lid_initState() // // Purpose: initializes the internal state of each LID in a subcatchment. -// Input: none +// Input: none // Output: none // { @@ -1249,7 +1249,7 @@ void lid_initState() initVol = 0.0; if ( LidProcs[k].soil.thickness > 0.0 ) { - lidUnit->soilMoisture = LidProcs[k].soil.wiltPoint + + lidUnit->soilMoisture = LidProcs[k].soil.wiltPoint + lidUnit->initSat * (LidProcs[k].soil.porosity - LidProcs[k].soil.wiltPoint); initVol += lidUnit->soilMoisture * LidProcs[k].soil.thickness; @@ -1284,7 +1284,7 @@ void lid_initState() //... set previous flux rates to 0 for (i = 0; i < MAX_LAYERS; i++) - { + { lidUnit->oldFluxRates[i] = 0.0; } @@ -1305,7 +1305,7 @@ void lid_initState() void lid_setOldGroupState(int j) // // Purpose: saves the current drain flow rate for the LIDs in a subcatchment. -// Input: j = subcatchment index +// Input: j = subcatchment index // Output: none // { @@ -1329,7 +1329,7 @@ void lid_setOldGroupState(int j) int isLidPervious(int k) // // Purpose: determines if a LID process allows infiltration or not. -// Input: k = LID process index +// Input: k = LID process index // Output: returns 1 if process is pervious or 0 if not // { @@ -1343,7 +1343,7 @@ double getSurfaceDepth(int j) // // Purpose: computes the depth (volume per unit area) of ponded water on the // surface of all LIDs within a subcatchment. -// Input: j = subcatchment index +// Input: j = subcatchment index // Output: returns volumetric depth of ponded water (ft) // { @@ -1399,9 +1399,9 @@ double lid_getFlowToPerv(int j) double lid_getStoredVolume(int j) // -// Purpose: computes stored volume of water for all LIDs +// Purpose: computes stored volume of water for all LIDs // grouped within a subcatchment. -// Input: j = subcatchment index +// Input: j = subcatchment index // Output: returns stored volume of water (ft3) // { @@ -1428,7 +1428,7 @@ double lid_getDrainFlow(int j, int timePeriod) // // Purpose: returns flow from all of a subcatchment's LID drains for // a designated time period -// Input: j = subcatchment index +// Input: j = subcatchment index // timePeriod = either PREVIOUS or CURRENT // Output: total drain flow (cfs) from the subcatchment. { @@ -1456,11 +1456,11 @@ void lid_addDrainLoads(int j, double c[], double tStep) { int isRunoffLoad; // true if drain becomes external runoff load int p; // pollutant index - double r; // pollutant fractional removal + double r; // pollutant fractional removal double w; // pollutant mass load (lb or kg) TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; + TLidList* lidList; + TLidGroup lidGroup; //... check if LID group exists lidGroup = LidGroups[j]; @@ -1471,11 +1471,11 @@ void lid_addDrainLoads(int j, double c[], double tStep) while ( lidList ) { lidUnit = lidList->lidUnit; - + //... see if unit's drain flow becomes external runoff isRunoffLoad = (lidUnit->drainNode >= 0 || lidUnit->drainSubcatch == j); - + //... for each pollutant not routed back on to subcatchment surface if (!lidUnit->toPerv) for (p = 0; p < Nobjects[POLLUT]; p++) { @@ -1502,7 +1502,7 @@ void lid_addDrainLoads(int j, double c[], double tStep) void lid_addDrainRunon(int j) // // Purpose: adds drain flows from LIDs in a given subcatchment to the -// subcatchments that were designated to receive them +// subcatchments that were designated to receive them // Input: j = index of subcatchment contributing underdrain flows // Output: none. // @@ -1513,8 +1513,8 @@ void lid_addDrainRunon(int j) double q; // drain flow rate (cfs) double w; // mass of polllutant from drain flow //(5.1.013) TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; + TLidList* lidList; + TLidGroup lidGroup; //... check if LID group exists lidGroup = LidGroups[j]; @@ -1553,7 +1553,7 @@ void lid_addDrainRunon(int j) void lid_addDrainInflow(int j, double f) // -// Purpose: adds LID drain flow to conveyance system nodes +// Purpose: adds LID drain flow to conveyance system nodes // Input: j = subcatchment index // f = time interval weighting factor // Output: none. @@ -1590,12 +1590,12 @@ void lid_addDrainInflow(int j, double f) Node[k].newLatFlow += q; massbal_addInflowFlow(WET_WEATHER_INFLOW, q); - //... add pollutant load, based on parent subcatchment quality + //... add pollutant load, based on parent subcatchment quality for (p = 0; p < Nobjects[POLLUT]; p++) { //... get previous & current drain loads w1 = lidUnit->oldDrainFlow * Subcatch[j].oldQual[p]; - w2 = lidUnit->newDrainFlow * Subcatch[j].newQual[p]; + w2 = lidUnit->newDrainFlow * Subcatch[j].newQual[p]; //... add interpolated load to node's wet weather loading w = (1.0 - f) * w1 + f * w2; @@ -1614,7 +1614,7 @@ void lid_addDrainInflow(int j, double f) void lid_getRunoff(int j, double tStep) // // Purpose: computes runoff and drain flows from the LIDs in a subcatchment. -// Input: j = subcatchment index +// Input: j = subcatchment index // tStep = time step (sec) // Output: updates following global quantities after LID treatment applied: // Vevap, Vpevap, VlidInfil, VlidIn, VlidOut, VlidDrain. @@ -1626,10 +1626,10 @@ void lid_getRunoff(int j, double tStep) double lidArea; // area of an LID unit double qImperv = 0.0; // runoff from impervious areas (cfs) double qPerv = 0.0; // runoff from pervious areas (cfs) //(5.1.013) - double lidInflow = 0.0; // inflow to an LID unit (ft/s) + double lidInflow = 0.0; // inflow to an LID unit (ft/s) double qRunoff = 0.0; // surface runoff from all LID units (cfs) double qDrain = 0.0; // drain flow from all LID units (cfs) - double qReturn = 0.0; // LID outflow returned to pervious area (cfs) + double qReturn = 0.0; // LID outflow returned to pervious area (cfs) //... return if there are no LID's theLidGroup = LidGroups[j]; @@ -1647,7 +1647,7 @@ void lid_getRunoff(int j, double tStep) //... get impervious and pervious area runoff from non-LID // portion of subcatchment (cfs) if ( Subcatch[j].area > Subcatch[j].lidArea ) - { + { qImperv = getImpervAreaRunoff(j); qPerv = getPervAreaRunoff(j); //(5.1.013) } @@ -1680,7 +1680,7 @@ void lid_getRunoff(int j, double tStep) //... evaluate the LID unit's performance, updating the LID group's // total surface runoff, drain flow, and flow returned to - // pervious area + // pervious area evalLidUnit(j, lidUnit, lidArea, lidInflow, tStep, &qRunoff, &qDrain, &qReturn); } @@ -1692,7 +1692,7 @@ void lid_getRunoff(int j, double tStep) theLidGroup->flowToPerv = qReturn; //... save the LID group's total surface, drain and return flow volumes - VlidOut = qRunoff * tStep; + VlidOut = qRunoff * tStep; VlidDrain = qDrain * tStep; VlidReturn = qReturn * tStep; } @@ -1825,7 +1825,7 @@ void evalLidUnit(int j, TLidUnit* lidUnit, double lidArea, double lidInflow, lidRunoff = lidproc_getOutflow(lidUnit, lidProc, lidInflow, EvapRate, NativeInfil, MaxNativeInfil, tStep, &lidEvap, &lidInfil, &lidDrain) * lidArea; - + //... convert drain flow to CFS lidDrain *= lidArea; @@ -1844,7 +1844,7 @@ void evalLidUnit(int j, TLidUnit* lidUnit, double lidArea, double lidInflow, lidDrain = 0.0; } } - + //... update system flow balance if drain flow goes to a // conveyance system node if ( lidUnit->drainNode >= 0 ) @@ -1912,7 +1912,7 @@ void lid_writeWaterBalance() "\n --------------------------------------------------------------------------------------------------------------------" "\n Total Evap Infil Surface Drain Initial Final Continuity" "\n Inflow Loss Loss Outflow Outflow Storage Storage Error"); - if ( UnitSystem == US ) fprintf(Frpt.file, + if ( UnitSystem == US ) fprintf(Frpt.file, "\n Subcatchment LID Control in in in in in in in %%"); else fprintf(Frpt.file, "\n Subcatchment LID Control mm mm mm mm mm mm mm %%"); @@ -1942,7 +1942,7 @@ void lid_writeWaterBalance() lidUnit->waterBalance.finalVol*ucf); //... compute flow balance error - inflow = lidUnit->waterBalance.initVol + + inflow = lidUnit->waterBalance.initVol + lidUnit->waterBalance.inflow; outflow = lidUnit->waterBalance.finalVol + lidUnit->waterBalance.evap + diff --git a/src/solver/swmm5.c b/src/solver/swmm5.c index 2917cb5e3..0edd4a9a5 100644 --- a/src/solver/swmm5.c +++ b/src/solver/swmm5.c @@ -19,7 +19,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. // @@ -38,7 +38,7 @@ // Build 5.1.013: // - Support added for saving average results within a reporting period. // - SWMM engine now always compiled to a shared object library. -// +// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -88,7 +88,7 @@ #include "datetime.h" // date/time functions #include "objects.h" // definitions of SWMM's data objects #include "funcs.h" // declaration of all global functions -#include "text.h" // listing of all text strings +#include "text.h" // listing of all text strings #define EXTERN // defined as 'extern' in headers.h #include "globals.h" // declaration of all global variables @@ -99,7 +99,7 @@ //----------------------------------------------------------------------------- // Unit conversion factors //----------------------------------------------------------------------------- -const double Ucf[10][2] = +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) @@ -277,7 +277,7 @@ int DLLEXPORT swmm_open(char* f1, char* f2, char* f3) 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. // @@ -346,7 +346,7 @@ int DLLEXPORT swmm_start(int saveResults) massbal_open(); stats_open(); - // --- write project options to report file + // --- write project options to report file report_writeOptions(); if ( RptFlags.controls ) report_writeControlActionsHeading(); } @@ -493,7 +493,7 @@ void execRouting() // --- if no runoff analysis, update climate state (for evaporation) else climate_setState(getDateTime(NewRoutingTime)); - + // --- route flows & pollutants through drainage system // (while updating NewRoutingTime) if ( DoRouting ) routing_execute(RouteModel, routingStep); @@ -816,7 +816,7 @@ void writecon(char *s) // Purpose: writes string of characters to the console. // { - fprintf(stdout,s); + fprintf(stdout, "%s", s); fflush(stdout); } From 0b1ada7f75771ce9b960157caa102f641a67bf71 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 30 Mar 2020 15:57:49 -0400 Subject: [PATCH 042/266] Update README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 95aa9f7fb..8c897a78d 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,6 @@ Stormwater-Management-Model ORD Stormwater Management Model (aka "SWMM") -## Build Status -[![Build status](https://ci.appveyor.com/api/projects/status/28d9pg7rollqfhqm/branch/develop?svg=true)](https://ci.appveyor.com/project/michaeltryby/stormwater-management-model/branch/develop) - - ## Introduction This is the official SWMM source code repository maintained by US EPA Office of Research and Development, Center For Environmental Solutions & Emergency Response, Water Infrastructure Division located in Cincinnati, Ohio. From 679e0549bc345e65eb82073f6745da1821f7b2ad Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 15 May 2020 10:04:46 -0400 Subject: [PATCH 043/266] Processing code drop --- .gitignore | 4 + src/solver/consts.h | 3 +- src/solver/dwflow.c | 1219 +++++++++++++++++++++++------------------ src/solver/dynwave.c | 3 +- src/solver/error.c | 9 +- src/solver/error.h | 4 + src/solver/flowrout.c | 6 +- src/solver/globals.h | 7 +- src/solver/hotstart.c | 7 +- src/solver/infil.c | 138 ++--- src/solver/infil.h | 21 +- src/solver/inflow.c | 174 +++--- src/solver/input.c | 5 +- src/solver/lid.c | 33 +- src/solver/node.c | 15 +- src/solver/objects.h | 9 + src/solver/project.c | 5 +- src/solver/qualrout.c | 7 +- src/solver/report.c | 42 +- src/solver/stats.c | 43 +- src/solver/statsrpt.c | 12 +- src/solver/subcatch.c | 7 +- src/solver/swmm5.c | 16 +- src/solver/text.h | 7 +- 24 files changed, 1044 insertions(+), 752 deletions(-) diff --git a/.gitignore b/.gitignore index a441c732a..827fd7b12 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,11 @@ +.DS_Store + # Eclipse Stuff .metadata/ .settings/ build/ nrtests/ + +*_export.h diff --git a/src/solver/consts.h b/src/solver/consts.h index 4421dd1eb..6217bb4bd 100644 --- a/src/solver/consts.h +++ b/src/solver/consts.h @@ -7,6 +7,7 @@ // 08/01/16 (Build 5.1.011) // 05/10/18 (Build 5.1.013) // 03/01/20 (Build 5.1.014) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman // // Various Constants @@ -16,7 +17,7 @@ // General Constants //------------------ -#define VERSION 51014 +#define VERSION 51015 #define MAGICNUMBER 516114522 #define EOFMARK 0x1A // Use 0x04 for UNIX systems #define MAXTITLE 3 // Max. # title lines diff --git a/src/solver/dwflow.c b/src/solver/dwflow.c index de096ad38..f04975235 100644 --- a/src/solver/dwflow.c +++ b/src/solver/dwflow.c @@ -3,11 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 03/19/15 (Build 5.1.008) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// 03/01/20 (Build 5.1.014) +// Date: 03/01/20 (Build 5.1.015) // Author: L. Rossman (EPA) // M. Tryby (EPA) // R. Dickinson (CDM) @@ -15,143 +11,486 @@ // Solves the momentum equation for flow in a conduit under dynamic wave // flow routing. // -// Build 5.1.008: -// - Bug in finding if conduit was upstrm/dnstrm full was fixed. -// -// Build 5.1.012: -// - Modified uniform loss rate term of conduit momentum equation. -// -// Build 5.1.013: -// - Preissmann slot surcharge option implemented. -// - Changed sign of uniform loss rate term (dq6) in flow updating equation. -// -// Build 5.1.014: -// - Conduit evap. and seepage loss initialized to 0 in dwflow_findConduitFlow. -// - Most current flow (qLast) used instead of previous time period flow -// (qOld) in call to link_getLossRate. +// Completely refactored for release 5.1.015. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE #include "headers.h" #include +// Flow cross-section variables: +// y = depth +// h = head +// a = area +// r = hydraulic radius +// w = top width +// Location notation: +// 1 = upstream +// 2 = downstream +// Mid = midstream + +// Intermediate conduit data +typedef struct +{ + double y1, y2; // upstream/downstream flow depth (ft) + double h1, h2; // upstream/downstream hydraulic head (ft) + double a1, a2; // upstream/downstream flow area (ft2) + double r1; // upstream hydraulic radius (ft) + double yMid, aMid, rMid; // mid-stream values of y, a, and r + double yFull; // conduit full depth (ft) + double yCrit; // critical flow depth (ft) + double fasnh; // fraction between normal & critical depth + double aWtd, rWtd; // upstream weighted area & hyd. radius + double flow; // estimated flow for current time step (cfs) + double aOld; // computed area from previous time step (ft2) + double velocity; // flow velocity (ft/sec) + double sigma; // inertial damping factor + double length; // effective conduit length (ft) + int isFull; // TRUE if conduit flows full + int linkIndex; // index of conduit's parent link +} ConduitData; + static const double MAXVELOCITY = 50.; // max. allowable velocity (ft/sec) -static int getFlowClass(int link, double q, double h1, double h2, - double y1, double y2, double* criticalDepth, double* normalDepth, - double* fasnh); -static void findSurfArea(int link, double q, double length, double* h1, - double* h2, double* y1, double* y2); -static double findLocalLosses(int link, double a1, double a2, double aMid, - double q); +static void initConduitData(TLink *link, ConduitData *cd); +static void setFlowDepth(TNode *node, double hInvert, double yFull, + double *h, double *y); + +static void findFlowClass(TLink *link, ConduitData *cd); +static int getWetNegativeFlowClass(TLink *link, ConduitData *cd, double yOffset1); +static int getWetPositiveFlowClass(TLink *link, ConduitData *cd, double yOffset2); +static int getDryToWetFlowClass(TLink *link, ConduitData *cd, double yOffset1); +static int getWetToDryFlowClass(TLink *link, ConduitData *cd, double yOffset2); + +static void computeSurfaceArea(TLink *link, ConduitData *cd); +static void getSubCriticalArea(TLink *link, ConduitData *cd); +static void getUpCriticalArea(TLink *link, ConduitData *cd); +static void getDownCriticalArea(TLink *link, ConduitData *cd); +static void getUpDryArea(TLink *link, ConduitData *cd); +static void getDownDryArea(TLink *link, ConduitData *cd); + +static void computeFlowSectionGeometry(TLink *link, ConduitData *cd); +static int conduitIsDryOrClosed(TLink *link, ConduitData *cd, double timeStep); +static void applyInertialDamping(TLink *link, ConduitData *cd); +static double solveMomentumEqn(TLink *link, ConduitData *cd, double timeStep); +static double findLocalLosses(TLink *link, ConduitData *cd); + +static double checkForCulvertInletControl(TLink *link, ConduitData *cd, double flow); +static double checkForNormalFlowControl(TLink *link, ConduitData *cd, double flow); +static int hasSlopeBasedNormalFlow(ConduitData *cd, int hasOutfall); +static int hasFroudeBasedNormalFlow(ConduitData *cd, int hasOutfall, double flow); +static double checkNormalFlowValue(TLink *link, ConduitData *cd, double flow); + +static double applyUnderRelaxation(ConduitData *cd, double omega, double flow); +static double checkImposedFlowLimits(TLink *link, ConduitData *cd, double flow); +static void saveFlowResult(TLink *link, ConduitData *cd, double flow); + +static double getWidth(TXsect *xsect, double y); +static double getSlotWidth(TXsect *xsect, double y); +static double getArea(TXsect *xsect, double y, double wSlot); +static double getHydRad(TXsect *xsect, double y); + +//============================================================================= + +void dwflow_findConduitFlow(int linkIndex, int trials, double omega, double timeStep) +// +// Updates flow in a conduit link by solving finite difference form of combined +// St. Venant continuity and momentum equations. +{ + double flow; // new conduit flow estimate (per barrel) (cfs) + TLink *link; // conduit's parent link + ConduitData cd; // intermediate conduit data + + link = &Link[linkIndex]; + cd.linkIndex = linkIndex; + + initConduitData(link, &cd); + findFlowClass(link, &cd); + computeSurfaceArea(link, &cd); + computeFlowSectionGeometry(link, &cd); + if (conduitIsDryOrClosed(link, &cd, timeStep)) + return; + applyInertialDamping(link, &cd); + + flow = solveMomentumEqn(link, &cd, timeStep); + flow = checkForCulvertInletControl(link, &cd, flow); + flow = checkForNormalFlowControl(link, &cd, flow); + if (trials > 0) + flow = applyUnderRelaxation(&cd, omega, flow); + flow = checkImposedFlowLimits(link, &cd, flow); + saveFlowResult(link, &cd, flow); +} + +//============================================================================= + +void initConduitData(TLink *link, ConduitData *cd) +{ + int k; // conduit index + double hInvert; // conduit's invert elevation (ft) + TNode *node; // node object + + k = link->subIndex; + cd->flow = Conduit[k].q1; + cd->yFull = link->xsect.yFull; + cd->aOld = MAX(Conduit[k].a2, FUDGE); + cd->length = Conduit[k].modLength; + cd->fasnh = 1.0; + cd->yCrit = cd->yFull; + + node = &Node[link->node1]; + hInvert = node->invertElev + link->offset1; + setFlowDepth(node, hInvert, cd->yFull, &cd->h1, &cd->y1); + node = &Node[link->node2]; + hInvert = node->invertElev + link->offset2; + setFlowDepth(node, hInvert, cd->yFull, &cd->h2, &cd->y2); + + Conduit[k].evapLossRate = 0.0; + Conduit[k].seepLossRate = 0.0; +} + +//============================================================================= + +void setFlowDepth(TNode *node, double hInvert, double yFull, double *h, double *y) +// +// Set the head (h) and flow depth (y) of a conduit connected to a specified node. +{ + *h = node->newDepth + node->invertElev; + *h = MAX(*h, hInvert); + *y = *h - hInvert; + *y = MAX(*y, FUDGE); + if (SurchargeMethod != SLOT) *y = MIN(*y, yFull); +} + +//============================================================================= + +void findFlowClass(TLink *link, ConduitData *cd) +// +// Find the type of flow a conduit is experiencing. +{ + int n1 = link->node1, + n2 = link->node2; + double yOffset1, yOffset2; + + // --- get upstream & downstream conduit invert offsets + yOffset1 = link->offset1; + yOffset2 = link->offset2; + if (Node[n1].type == OUTFALL) + yOffset1 = MAX(0.0, (yOffset1 - Node[n1].newDepth)); + if (Node[n2].type == OUTFALL) + yOffset2 = MAX(0.0, (yOffset2 - Node[n2].newDepth)); + + // --- default class is SUBCRITICAL + link->flowClass = SUBCRITICAL; + cd->fasnh = 1.0; + + // -- conduit is full + if (cd->y1 >= cd->yFull && cd->y2 >= cd->yFull) + link->flowClass = SUBCRITICAL; + + // --- both ends of conduit are wet + else if (cd->y1 > FUDGE && cd->y2 > FUDGE) + { + if (cd->flow < 0.0) + link->flowClass = getWetNegativeFlowClass(link, cd, yOffset1); + else + link->flowClass = getWetPositiveFlowClass(link, cd, yOffset2); + } + + // --- no flow at either end of conduit + else if (cd->y1 <= FUDGE && cd->y2 <= FUDGE) + link->flowClass = DRY; + + // --- downstream end of conduit is wet, upstream dry + else if (cd->y2 > FUDGE) + link->flowClass = getDryToWetFlowClass(link, cd, yOffset1); + + // --- upstream end of conduit is wet, downstream dry + else + link->flowClass = getWetToDryFlowClass(link, cd, yOffset2); +} + +//============================================================================= + +int getWetNegativeFlowClass(TLink *link, ConduitData *cd, double yOffset1) +// +// Determine flow class for a fully wetted conduit with reverse flow. +{ + double flow = fabs(cd->flow); + int flowClass = SUBCRITICAL; + + // --- upstream end at critical depth if flow depth is + // below conduit's critical depth and an upstream + // conduit offset exists + if (yOffset1 > 0.0) + { + cd->yCrit = MIN(link_getYnorm(cd->linkIndex, flow), + link_getYcrit(cd->linkIndex, flow)); + if (cd->y1 < cd->yCrit) flowClass = UP_CRITICAL; + } + return flowClass; +} + +//============================================================================= + +int getWetPositiveFlowClass(TLink *link, ConduitData *cd, double yOffset2) +// +// Determine flow class for a fully wetted conduit with positive flow. +{ + double flow = fabs(cd->flow); + double yNorm, yCrit; + double ycMin, ycMax; + int flowClass = SUBCRITICAL; + + // --- conduit has a downstream offset + if (yOffset2 > 0.0) + { + yNorm = link_getYnorm(cd->linkIndex, flow); + yCrit = link_getYcrit(cd->linkIndex, flow); + ycMin = MIN(yNorm, yCrit); + ycMax = MAX(yNorm, yCrit); + + // --- if downstream depth < smaller critical depth + // then flow class is Downstream Critical + if (cd->y2 < ycMin) flowClass = DN_CRITICAL; + + // --- if downstream depth between critical & normal + // depth compute a weighting factor (fasnh) to + // apply to downstream surface area + else if (cd->y2 < ycMax) + { + if (ycMax - ycMin < FUDGE) + cd->fasnh = 0.0; + else + cd->fasnh = (ycMax - cd->y2) / (ycMax - ycMin); + } + cd->yCrit = ycMin; + } + return flowClass; +} + +//============================================================================= + +int getDryToWetFlowClass(TLink *link, ConduitData *cd, double yOffset1) +// +// Determine flow class for a conduit that is dry at upstream end and +// wet at downstream end. +{ + double flow = fabs(cd->flow); + int flowClass = SUBCRITICAL; + + // --- flow classification is UP_DRY if downstream head < + // invert of upstream end of conduit + if (cd->h2 < Node[link->node1].invertElev + link->offset1) + flowClass = UP_DRY; + + // --- otherwise, the downstream head will be >= upstream + // conduit invert creating a flow reversal and upstream end + // should be at critical depth, providing that an upstream + // offset exists (otherwise subcritical condition is maintained) + else if (yOffset1 > 0.0) + { + cd->yCrit = MIN(link_getYnorm(cd->linkIndex, flow), + link_getYcrit(cd->linkIndex, flow)); + flowClass = UP_CRITICAL; + } + return flowClass; +} -static double getWidth(TXsect* xsect, double y); -static double getSlotWidth(TXsect* xsect, double y); //(5.1.013) -static double getArea(TXsect* xsect, double y, double wSlot); //(5.1.013) -static double getHydRad(TXsect* xsect, double y); +//============================================================================= -static double checkNormalFlow(int j, double q, double y1, double y2, - double a1, double r1); +int getWetToDryFlowClass(TLink *link, ConduitData *cd, double yOffset2) +// +// Determine flow class for a conduit that is wet at upstream end and +// dry at downstream end. +{ + double flow = fabs(cd->flow); + int flowClass = SUBCRITICAL; + + // --- flow classification is DN_DRY if upstream head < + // invert of downstream end of conduit + if (cd->h1 < Node[link->node2].invertElev + link->offset2) + flowClass = DN_DRY; + + // --- otherwise flow at downstream end should be at critical depth + // providing that a downstream offset exists (otherwise + // subcritical condition is maintained) + else if (yOffset2 > 0.0) + { + cd->yCrit = MIN(link_getYnorm(cd->linkIndex, flow), + link_getYcrit(cd->linkIndex, flow)); + flowClass = DN_CRITICAL; + } + return flowClass; +} //============================================================================= -void dwflow_findConduitFlow(int j, int steps, double omega, double dt) -// -// Input: j = link index -// steps = number of iteration steps taken -// omega = under-relaxation parameter -// dt = time step (sec) -// Output: returns new flow value (cfs) -// Purpose: updates flow in conduit link by solving finite difference -// form of continuity and momentum equations. -// -{ - int k; // index of conduit - int n1, n2; // indexes of end nodes - double z1, z2; // upstream/downstream invert elev. (ft) - double h1, h2; // upstream/dounstream flow heads (ft) - double y1, y2; // upstream/downstream flow depths (ft) - double a1, a2; // upstream/downstream flow areas (ft2) - double r1; // upstream hyd. radius (ft) - double yMid, rMid, aMid; // mid-stream or avg. values of y, r, & a - double aWtd, rWtd; // upstream weighted area & hyd. radius - double qLast; // flow from previous iteration (cfs) - double qOld; // flow from previous time step (cfs) - double aOld; // area from previous time step (ft2) - double v; // velocity (ft/sec) - double rho; // upstream weighting factor - double sigma; // inertial damping factor - double length; // effective conduit length (ft) - double wSlot; // Preissmann slot width (ft) //(5.1.013) - double dq1, dq2, dq3, dq4, dq5, // terms in momentum eqn. - dq6; // term for evap and infil losses - double denom; // denominator of flow update formula - double q; // new flow value (cfs) - double barrels; // number of barrels in conduit - TXsect* xsect = &Link[j].xsect; // ptr. to conduit's cross section data - char isFull = FALSE; // TRUE if conduit flowing full - char isClosed = FALSE; // TRUE if conduit closed - - - - // --- adjust isClosed status by any control action - if ( Link[j].setting == 0 ) isClosed = TRUE; - - // --- get flow from last time step & previous iteration - k = Link[j].subIndex; - barrels = Conduit[k].barrels; - qOld = Link[j].oldFlow / barrels; - qLast = Conduit[k].q1; - Conduit[k].evapLossRate = 0.0; //(5.1.014) - Conduit[k].seepLossRate = 0.0; //(5.1.014) - - // --- get most current heads at upstream and downstream ends of conduit - n1 = Link[j].node1; - n2 = Link[j].node2; - z1 = Node[n1].invertElev + Link[j].offset1; - z2 = Node[n2].invertElev + Link[j].offset2; - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - h1 = MAX(h1, z1); - h2 = MAX(h2, z2); - - // --- get unadjusted upstream and downstream flow depths in conduit - // (flow depth = head in conduit - elev. of conduit invert) - y1 = h1 - z1; - y2 = h2 - z2; - y1 = MAX(y1, FUDGE); - y2 = MAX(y2, FUDGE); - - // --- flow depths can't exceed full depth of conduit if slot not used - if ( SurchargeMethod != SLOT ) //(5.1.013) +void computeSurfaceArea(TLink *link, ConduitData *cd) +// +// Compute surface area that conduit contributes to its end nodes. +{ + switch (link->flowClass) { - y1 = MIN(y1, xsect->yFull); - y2 = MIN(y2, xsect->yFull); + case SUBCRITICAL: + getSubCriticalArea(link, cd); + break; + case UP_CRITICAL: + getUpCriticalArea(link, cd); + break; + case DN_CRITICAL: + getDownCriticalArea(link, cd); + break; + case UP_DRY: + getUpDryArea(link, cd); + break; + case DN_DRY: + getDownDryArea(link, cd); + break; + case DRY: + link->surfArea1 = FUDGE * cd->length / 2.0; + link->surfArea2 = link->surfArea1; + break; } +} - // -- get area from solution at previous time step - aOld = Conduit[k].a2; - aOld = MAX(aOld, FUDGE); +//============================================================================= - // --- use Courant-modified length instead of conduit's actual length - length = Conduit[k].modLength; +void getSubCriticalArea(TLink *link, ConduitData *cd) +// +// Conduit surface area when neither end is dry or has critical flow. +{ + double w1, w2, wMid; + TXsect *xsect = &link->xsect; + + cd->yMid = 0.5 * (cd->y1 + cd->y2); + if (cd->yMid < FUDGE) cd->yMid = FUDGE; + w1 = getWidth(xsect, cd->y1); + w2 = getWidth(xsect, cd->y2); + wMid = getWidth(xsect, cd->yMid); + + // --- assign each end the avg. area over its half of the conduit + link->surfArea1 = (w1 + wMid) / 2. * cd->length / 2.; + link->surfArea2 = (wMid + w2) / 2. * cd->length / 2. * (cd->fasnh); +} - // --- find surface area contributions to upstream and downstream nodes - // based on previous iteration's flow estimate - findSurfArea(j, qLast, length, &h1, &h2, &y1, &y2); +//============================================================================= - // --- compute area at each end of conduit & hyd. radius at upstream end - wSlot = getSlotWidth(xsect, y1); //(5.1.013) - a1 = getArea(xsect, y1, wSlot); //(5.1.013) - r1 = getHydRad(xsect, y1); - wSlot = getSlotWidth(xsect, y2); //(5.1.013) - a2 = getArea(xsect, y2, wSlot); //(5.1.013) +void getUpCriticalArea(TLink *link, ConduitData *cd) +// +// Conduit surface area when upstream end has critical flow. +{ + double w2, wMid; + TXsect *xsect = &link->xsect; + + cd->y1 = MAX(cd->yCrit, FUDGE); + cd->h1 = Node[link->node1].invertElev + link->offset1 + cd->y1; + cd->yMid = 0.5 * (cd->y1 + cd->y2); + if (cd->yMid < FUDGE) cd->yMid = FUDGE; + w2 = getWidth(xsect, cd->y2); + wMid = getWidth(xsect, cd->yMid); + + // --- assign downstream end avg. area over full length + link->surfArea2 = (wMid + w2) / 2. * cd->length; + link->surfArea1 = 0.0; +} + +//============================================================================= + +void getDownCriticalArea(TLink *link, ConduitData *cd) +// +// Conduit surface area when downstream end has critical flow. +{ + double w1, wMid; + TXsect *xsect = &link->xsect; + + cd->y2 = MAX(cd->yCrit, FUDGE); + cd->h2 = Node[link->node2].invertElev + link->offset2 + cd->y2; + w1 = getWidth(xsect, cd->y1); + cd->yMid = 0.5 * (cd->y1 + cd->y2); + if (cd->yMid < FUDGE) cd->yMid = FUDGE; + wMid = getWidth(xsect, cd->yMid); + + // --- assign upstream end avg. surface area over full length + link->surfArea1 = (w1 + wMid) / 2. * cd->length; + link->surfArea2 = 0.0; +} - // --- compute area & hyd. radius at midpoint - yMid = 0.5 * (y1 + y2); - wSlot = getSlotWidth(xsect, yMid); //(5.1.013) - aMid = getArea(xsect, yMid, wSlot); //(5.1.013) - rMid = getHydRad(xsect, yMid); +//============================================================================= + +void getUpDryArea(TLink *link, ConduitData *cd) +// +// Conduit surface area when upstream end is dry. +{ + double w1, w2, wMid; + TXsect *xsect = &link->xsect; + + cd->y1 = FUDGE; + cd->yMid = 0.5 * (cd->y1 + cd->y2); + if (cd->yMid < FUDGE) cd->yMid = FUDGE; + w1 = getWidth(xsect, cd->y1); + w2 = getWidth(xsect, cd->y2); + wMid = getWidth(xsect, cd->yMid); + + // --- assign avg. surface area of downstream half of conduit + // to the downstream node + link->surfArea2 = (wMid + w2) / 2. * cd->length / 2.; + + // --- if there is no free-fall at upstream end, assign the + // upstream node the avg. surface area of the upstream half + if (link->offset1 <= 0.0) + link->surfArea1 = (w1 + wMid) / 2. * cd->length / 2.; + else + link->surfArea1 = 0.0; +} + +//============================================================================= + +void getDownDryArea(TLink *link, ConduitData *cd) +// +// Conduit surface area when downstream end is dry. +{ + double w1, w2, wMid; + TXsect *xsect = &link->xsect; + + cd->y2 = FUDGE; + cd->yMid = 0.5 * (cd->y1 + cd->y2); + if (cd->yMid < FUDGE) cd->yMid = FUDGE; + w1 = getWidth(xsect, cd->y1); + w2 = getWidth(xsect, cd->y2); + wMid = getWidth(xsect, cd->yMid); + + // --- assign avg. surface area of upstream half of conduit + // to the upstream node + link->surfArea1 = (wMid + w1) / 2. * cd->length / 2.; + + // --- if there is no free-fall at downstream end, assign the + // downstream node the avg. surface area of the downstream half + if (link->offset2 <= 0.0) + link->surfArea2 = (w2 + wMid) / 2. * cd->length / 2.; + else + link->surfArea2 = 0.0; +} + +//============================================================================= + +void computeFlowSectionGeometry(TLink *link, ConduitData *cd) +// +// Compute flow area and hydraulic radius for flow depths at each end +// and midpoint of a conduit. +{ + double wSlot; // Preissmann slot width (ft) + TXsect *xsect = &link->xsect; + + wSlot = getSlotWidth(xsect, cd->y1); + cd->a1 = getArea(xsect, cd->y1, wSlot); + cd->r1 = getHydRad(xsect, cd->y1); + wSlot = getSlotWidth(xsect, cd->y2); + cd->a2 = getArea(xsect, cd->y2, wSlot); + + cd->yMid = 0.5 * (cd->y1 + cd->y2); + wSlot = getSlotWidth(xsect, cd->yMid); + cd->aMid = getArea(xsect, cd->yMid, wSlot); + cd->rMid = getHydRad(xsect, cd->yMid); // --- alternate approach not currently used, but might produce better // Bernoulli energy balance for steady flows @@ -159,451 +498,315 @@ void dwflow_findConduitFlow(int j, int steps, double omega, double dt) //rMid = (r1+getHydRad(xsect,y2))/2.0; // --- check if conduit is flowing full - if ( y1 >= xsect->yFull && - y2 >= xsect->yFull) isFull = TRUE; - - // --- set new flow to zero if conduit is dry or if flap gate is closed - if ( Link[j].flowClass == DRY || - Link[j].flowClass == UP_DRY || - Link[j].flowClass == DN_DRY || - isClosed || - aMid <= FUDGE ) + cd->isFull = (cd->y1 >= cd->yFull && + cd->y2 >= cd->yFull); +} + +//============================================================================= + +int conduitIsDryOrClosed(TLink *link, ConduitData *cd, double timeStep) +// +// Set flow to 0 if conduit is dry or closed. +{ + int k = link->subIndex; + + if (link->flowClass == DRY || + link->flowClass == UP_DRY || + link->flowClass == DN_DRY || + link->setting == 0.0 || + cd->aMid <= FUDGE) { - Conduit[k].a1 = 0.5 * (a1 + a2); + Conduit[k].a1 = 0.5 * (cd->a1 + cd->a2); Conduit[k].q1 = 0.0;; Conduit[k].q2 = 0.0; - Link[j].dqdh = GRAVITY * dt * aMid / length * barrels; - Link[j].froude = 0.0; - Link[j].newDepth = MIN(yMid, Link[j].xsect.yFull); - Link[j].newVolume = Conduit[k].a1 * link_getLength(j) * barrels; - Link[j].newFlow = 0.0; - return; + Conduit[k].fullState = 0; + link->dqdh = GRAVITY * timeStep * cd->aMid / cd->length * + Conduit[k].barrels; + link->froude = 0.0; + link->newDepth = MIN(cd->yMid, cd->yFull); + link->newVolume = Conduit[k].a1 * link_getLength(cd->linkIndex) * + Conduit[k].barrels; + link->newFlow = 0.0; + return TRUE; } + return FALSE; +} + +//============================================================================= + +void applyInertialDamping(TLink *link, ConduitData *cd) +// +// Apply inertial damping factor to weight conduit's +// average area and hydraulic radius with upstream values. +{ + double rho; + double Fr; // --- compute velocity from last flow estimate - v = qLast / aMid; - if ( fabs(v) > MAXVELOCITY ) v = MAXVELOCITY * SGN(qLast); + cd->velocity = cd->flow / cd->aMid; + if ( fabs(cd->velocity) > MAXVELOCITY ) + cd->velocity = MAXVELOCITY * SGN(cd->flow); // --- compute Froude No. - Link[j].froude = link_getFroude(j, v, yMid); - if ( Link[j].flowClass == SUBCRITICAL && - Link[j].froude > 1.0 ) Link[j].flowClass = SUPCRITICAL; + Fr = link_getFroude(cd->linkIndex, cd->velocity, cd->yMid); + if (link->flowClass == SUBCRITICAL && Fr > 1.0) + link->flowClass = SUPCRITICAL; // --- find inertial damping factor (sigma) - if ( Link[j].froude <= 0.5 ) sigma = 1.0; - else if ( Link[j].froude >= 1.0 ) sigma = 0.0; - else sigma = 2.0 * (1.0 - Link[j].froude); + if ( Fr <= 0.5 ) + cd->sigma = 1.0; + else if ( Fr >= 1.0 ) + cd->sigma = 0.0; + else + cd->sigma = 2.0 * (1.0 - Fr); + link->froude = Fr; // --- get upstream-weighted area & hyd. radius based on damping factor // (modified version of R. Dickinson's slope weighting) rho = 1.0; - if ( !isFull && qLast > 0.0 && h1 >= h2 ) rho = sigma; - aWtd = a1 + (aMid - a1) * rho; - rWtd = r1 + (rMid - r1) * rho; + if ( !cd->isFull && cd->flow > 0.0 && cd->h1 >= cd->h2 ) + rho = cd->sigma; + cd->aWtd = cd->a1 + (cd->aMid - cd->a1) * rho; + cd->rWtd = cd->r1 + (cd->rMid - cd->r1) * rho; // --- determine how much inertial damping to apply - if ( InertDamping == NO_DAMPING ) sigma = 1.0; - else if ( InertDamping == FULL_DAMPING ) sigma = 0.0; + if ( InertDamping == NO_DAMPING ) + cd->sigma = 1.0; + else if ( InertDamping == FULL_DAMPING ) + cd->sigma = 0.0; // --- use full inertial damping if closed conduit is surcharged - if ( isFull && !xsect_isOpen(xsect->type) ) sigma = 0.0; + if (cd->isFull && !xsect_isOpen(link->xsect.type)) + cd->sigma = 0.0; +} + +//============================================================================= + +double solveMomentumEqn(TLink *link, ConduitData *cd, double timeStep) +// +// Solve St. Venant momentum equation for conduit flow over a time step. +{ + int k = link->subIndex; + int linkIndex = cd->linkIndex; + double flow; // new flow value (cfs) + double dq1, dq2, dq3, dq4, dq5, dq6; // terms in momentum eqn. + double denom; + TXsect* xsect = &link->xsect; - // --- compute terms of momentum eqn.: // --- 1. friction slope term - if ( xsect->type == FORCE_MAIN && isFull ) - dq1 = dt * forcemain_getFricSlope(j, fabs(v), rMid); - else dq1 = dt * Conduit[k].roughFactor / pow(rWtd, 1.33333) * fabs(v); + if (xsect->type == FORCE_MAIN && cd->isFull) + dq1 = timeStep * + forcemain_getFricSlope(linkIndex, fabs(cd->velocity), cd->rMid); + else + dq1 = timeStep * Conduit[k].roughFactor / pow(cd->rWtd, 1.33333) * + fabs(cd->velocity); // --- 2. energy slope term - dq2 = dt * GRAVITY * aWtd * (h2 - h1) / length; + dq2 = timeStep * GRAVITY * cd->aWtd * (cd->h2 - cd->h1) / cd->length; // --- 3 & 4. inertial terms dq3 = 0.0; dq4 = 0.0; - if ( sigma > 0.0 ) + if (cd->sigma > 0.0) { - dq3 = 2.0 * v * (aMid - aOld) * sigma; - dq4 = dt * v * v * (a2 - a1) / length * sigma; + dq3 = 2.0 * cd->velocity * (cd->aMid - cd->aOld) * cd->sigma; + dq4 = timeStep * cd->velocity * cd->velocity * + (cd->a2 - cd->a1) / cd->length * cd->sigma; } // --- 5. local losses term dq5 = 0.0; - if ( Conduit[k].hasLosses ) - { - dq5 = findLocalLosses(j, a1, a2, aMid, qLast) / 2.0 / length * dt; - } + if (Conduit[k].hasLosses) + dq5 = findLocalLosses(link, cd) / 2.0 / cd->length * timeStep; // --- 6. term for evap and seepage losses per unit length - dq6 = link_getLossRate(j, qLast) * 2.5 * dt * v / link_getLength(j); //(5.1.014) + dq6 = link_getLossRate(linkIndex, cd->flow) * 2.5 * timeStep * + cd->velocity / link_getLength(linkIndex); // --- combine terms to find new conduit flow denom = 1.0 + dq1 + dq5; - q = (qOld - dq2 + dq3 + dq4 + dq6) / denom; //(5.1.013) + flow = link->oldFlow / Conduit[k].barrels; + flow = (flow - dq2 + dq3 + dq4 + dq6) / denom; // --- compute derivative of flow w.r.t. head - Link[j].dqdh = 1.0 / denom * GRAVITY * dt * aWtd / length * barrels; - - // --- check if any flow limitation applies - Link[j].inletControl = FALSE; - Link[j].normalFlow = FALSE; - if ( q > 0.0 ) - { - // --- check for inlet controlled culvert flow - if ( xsect->culvertCode > 0 && !isFull ) - q = culvert_getInflow(j, q, h1); + link->dqdh = 1.0 / denom * GRAVITY * timeStep * cd->aWtd / cd->length * + Conduit[k].barrels; + return flow; +} - // --- check for normal flow limitation based on surface slope & Fr - else - if ( y1 < Link[j].xsect.yFull && - ( Link[j].flowClass == SUBCRITICAL || - Link[j].flowClass == SUPCRITICAL ) - ) q = checkNormalFlow(j, q, y1, y2, a1, r1); - } +//============================================================================= - // --- apply under-relaxation weighting between new & old flows; - // --- do not allow change in flow direction without first being zero - if ( steps > 0 ) - { - q = (1.0 - omega) * qLast + omega * q; - if ( q * qLast < 0.0 ) q = 0.001 * SGN(q); - } +double findLocalLosses(TLink *link, ConduitData* cd) +// +// Compute local losses term of a conduit's momentum equation. +{ + double losses = 0.0; + double flow = fabs(cd->flow); - // --- check if user-supplied flow limit applies - if ( Link[j].qLimit > 0.0 ) - { - if ( fabs(q) > Link[j].qLimit ) q = SGN(q) * Link[j].qLimit; - } + if ( cd->a1 > FUDGE ) losses += link->cLossInlet * (flow/cd->a1); + if ( cd->a2 > FUDGE ) losses += link->cLossOutlet * (flow/cd->a2); + if ( cd->aMid > FUDGE ) losses += link->cLossAvg * (flow/cd->aMid); + return losses; +} - // --- check for reverse flow with closed flap gate - if ( link_setFlapGate(j, n1, n2, q) ) q = 0.0; +//============================================================================= - // --- do not allow flow out of a dry node - // (as suggested by R. Dickinson) - if( q > FUDGE && Node[n1].newDepth <= FUDGE ) q = FUDGE; - if( q < -FUDGE && Node[n2].newDepth <= FUDGE ) q = -FUDGE; - - // --- save new values of area, flow, depth, & volume - Conduit[k].a1 = aMid; - Conduit[k].q1 = q; - Conduit[k].q2 = q; - Link[j].newDepth = MIN(yMid, xsect->yFull); - aMid = (a1 + a2) / 2.0; -// aMid = MIN(aMid, xsect->aFull); //Slot can have aMid > aFull //(5.1.013) - Conduit[k].fullState = link_getFullState(a1, a2, xsect->aFull); - Link[j].newVolume = aMid * link_getLength(j) * barrels; - Link[j].newFlow = q * barrels; +double checkForCulvertInletControl(TLink *link, ConduitData *cd, double flow) +// +// Check if conduit flow is subject to culvert inlet control. +{ + link->inletControl = FALSE; + if (flow > 0.0 && link->xsect.culvertCode > 0 && !cd->isFull) + flow = culvert_getInflow(cd->linkIndex, flow, cd->h1); + return flow; } //============================================================================= -int getFlowClass(int j, double q, double h1, double h2, double y1, double y2, - double *yC, double *yN, double* fasnh) -// -// Input: j = conduit link index -// q = current conduit flow (cfs) -// h1 = head at upstream end of conduit (ft) -// h2 = head at downstream end of conduit (ft) -// y1 = upstream flow depth in conduit (ft) -// y2 = downstream flow depth in conduit (ft) -// yC = critical flow depth (ft) -// yN = normal flow depth (ft) -// fasnh = fraction between norm. & crit. depth -// Output: returns flow classification code -// Purpose: determines flow class for a conduit based on depths at each end. +double checkForNormalFlowControl(TLink *link, ConduitData *cd, double flow) // +// Check if conduit flow should be reduced to normal Manning flow value. { - int n1, n2; // indexes of upstrm/downstrm nodes - int flowClass; // flow classification code - double ycMin, ycMax; // min/max critical depths (ft) - double z1, z2; // offsets of conduit inverts (ft) + int hasOutfall; - // --- get upstream & downstream node indexes - n1 = Link[j].node1; - n2 = Link[j].node2; + link->normalFlow = FALSE; + if (link->inletControl || cd->isFull) + return flow; + if (link->flowClass == SUBCRITICAL || link->flowClass == SUPCRITICAL) + { + hasOutfall = (Node[link->node1].type == OUTFALL || + Node[link->node2].type == OUTFALL); + if (hasSlopeBasedNormalFlow(cd, hasOutfall) || + hasFroudeBasedNormalFlow(cd, hasOutfall, flow)) + flow = checkNormalFlowValue(link, cd, flow); + } + return flow; +} - // --- get upstream & downstream conduit invert offsets - z1 = Link[j].offset1; - z2 = Link[j].offset2; +//============================================================================= - // --- base offset of an outfall conduit on outfall's depth - if ( Node[n1].type == OUTFALL ) z1 = MAX(0.0, (z1 - Node[n1].newDepth)); - if ( Node[n2].type == OUTFALL ) z2 = MAX(0.0, (z2 - Node[n2].newDepth)); +int hasSlopeBasedNormalFlow(ConduitData *cd, int hasOutfall) +// +// Check if upstream flow depth is lower than downstream depth. +{ + if (NormalFlowLtd == SLOPE || NormalFlowLtd == BOTH || hasOutfall) + return (cd->y1 < cd->y2); + return FALSE; +} - // --- default class is SUBCRITICAL - flowClass = SUBCRITICAL; - *fasnh = 1.0; +//============================================================================= - // --- case where both ends of conduit are wet - if ( y1 > FUDGE && y2 > FUDGE ) +int hasFroudeBasedNormalFlow(ConduitData *cd, int hasOutfall, double flow) +// +// Check if Froude number at upstream end of conduit is >= 1. +{ + if ( (NormalFlowLtd == FROUDE || NormalFlowLtd == BOTH) && !hasOutfall) { - if ( q < 0.0 ) - { - // --- upstream end at critical depth if flow depth is - // below conduit's critical depth and an upstream - // conduit offset exists - if ( z1 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - ycMin = MIN(*yN, *yC); - if ( y1 < ycMin ) flowClass = UP_CRITICAL; - } - } - - // --- case of normal direction flow - else - { - // --- downstream end at smaller of critical and normal depth - // if downstream flow depth below this and a downstream - // conduit offset exists - if ( z2 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - ycMin = MIN(*yN, *yC); - ycMax = MAX(*yN, *yC); - if ( y2 < ycMin ) flowClass = DN_CRITICAL; - else if ( y2 < ycMax ) - { - if ( ycMax - ycMin < FUDGE ) *fasnh = 0.0; - else *fasnh = (ycMax - y2) / (ycMax - ycMin); - } - } - } + if (link_getFroude(cd->linkIndex, flow / cd->a1, cd->y1) >= 1.0) + return TRUE; } + return FALSE; +} - // --- case where no flow at either end of conduit - else if ( y1 <= FUDGE && y2 <= FUDGE ) flowClass = DRY; +//============================================================================= - // --- case where downstream end of pipe is wet, upstream dry - else if ( y2 > FUDGE ) - { - // --- flow classification is UP_DRY if downstream head < - // invert of upstream end of conduit - if ( h2 < Node[n1].invertElev + Link[j].offset1 ) flowClass = UP_DRY; - - // --- otherwise, the downstream head will be >= upstream - // conduit invert creating a flow reversal and upstream end - // should be at critical depth, providing that an upstream - // offset exists (otherwise subcritical condition is maintained) - else if ( z1 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - flowClass = UP_CRITICAL; - } - } +double checkNormalFlowValue(TLink *link, ConduitData *cd, double flow) +// +// Return smaller of normal Manning flow and current dynamic wave flow. +{ + int k = link->subIndex; + double normalFlow = Conduit[k].beta * cd->a1 * pow(cd->r1, 2./3.); - // --- case where upstream end of pipe is wet, downstream dry - else + if ( normalFlow < flow ) { - // --- flow classification is DN_DRY if upstream head < - // invert of downstream end of conduit - if ( h1 < Node[n2].invertElev + Link[j].offset2 ) flowClass = DN_DRY; - - // --- otherwise flow at downstream end should be at critical depth - // providing that a downstream offset exists (otherwise - // subcritical condition is maintained) - else if ( z2 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - flowClass = DN_CRITICAL; - } + link->normalFlow = TRUE; + flow = normalFlow; } - return flowClass; + return flow; } //============================================================================= -void findSurfArea(int j, double q, double length, double* h1, double* h2, - double* y1, double* y2) -// -// Input: j = conduit link index -// q = current conduit flow (cfs) -// length = conduit length (ft) -// h1 = head at upstream end of conduit (ft) -// h2 = head at downstream end of conduit (ft) -// y1 = upstream flow depth (ft) -// y2 = downstream flow depth (ft) -// Output: updated values of h1, h2, y1, & y2; -// Purpose: assigns surface area of conduit to its up and downstream nodes. -// -{ - int n1, n2; // indexes of upstrm/downstrm nodes - double flowDepth1; // flow depth at upstrm end (ft) - double flowDepth2; // flow depth at downstrm end (ft) - double flowDepthMid; // flow depth at midpt. (ft) - double width1; // top width at upstrm end (ft) - double width2; // top width at downstrm end (ft) - double widthMid; // top width at midpt. (ft) - double surfArea1 = 0.0; // surface area at upstream node (ft2) - double surfArea2 = 0.0; // surface area st downstrm node (ft2) - double criticalDepth; // critical flow depth (ft) - double normalDepth; // normal flow depth (ft) - double fullDepth; // full depth (ft) //(5.1.013) - double fasnh = 1.0; // fraction between norm. & crit. depth //(5.1.013) - TXsect* xsect = &Link[j].xsect; // pointer to cross-section data - - // --- get node indexes & current flow depths - n1 = Link[j].node1; - n2 = Link[j].node2; - flowDepth1 = *y1; - flowDepth2 = *y2; - - normalDepth = (flowDepth1 + flowDepth2) / 2.0; - criticalDepth = normalDepth; - -//// Following code segment modified for release 5.1.013. //// //(5.1.013) - // --- find conduit's flow classification - fullDepth = xsect->yFull; - if (flowDepth1 >= fullDepth && flowDepth2 >= fullDepth) - { - Link[j].flowClass = SUBCRITICAL; - } - else Link[j].flowClass = getFlowClass(j, q, *h1, *h2, *y1, *y2, - &criticalDepth, &normalDepth, &fasnh); -/////////////////////////////////////////////////////////////// +double applyUnderRelaxation(ConduitData *cd, double omega, double flow) +// +// Weight current flow estimate with previous estimate. +{ + flow = (1.0 - omega) * cd->flow + omega * flow; - // --- add conduit's surface area to its end nodes depending on flow class - switch ( Link[j].flowClass ) - { - case SUBCRITICAL: - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width1 = getWidth(xsect, flowDepth1); - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - surfArea1 = (width1 + widthMid) * length / 4.; - surfArea2 = (widthMid + width2) * length / 4. * fasnh; - break; + // --- flow can't switch sign without first being close to 0 + if (flow * cd->flow < 0.0) flow = 0.001 * SGN(flow); + return flow; +} - case UP_CRITICAL: - flowDepth1 = criticalDepth; - if ( normalDepth < criticalDepth ) flowDepth1 = normalDepth; - flowDepth1 = MAX(flowDepth1, FUDGE); - *h1 = Node[n1].invertElev + Link[j].offset1 + flowDepth1; - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - surfArea2 = (widthMid + width2) * length * 0.5; - break; +//============================================================================= - case DN_CRITICAL: - flowDepth2 = criticalDepth; - if ( normalDepth < criticalDepth ) flowDepth2 = normalDepth; - flowDepth2 = MAX(flowDepth2, FUDGE); - *h2 = Node[n2].invertElev + Link[j].offset2 + flowDepth2; - width1 = getWidth(xsect, flowDepth1); - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - widthMid = getWidth(xsect, flowDepthMid); - surfArea1 = (width1 + widthMid) * length * 0.5; - break; +double checkImposedFlowLimits(TLink *link, ConduitData *cd, double flow) +// +// Perform additional checks that limit a conduit's flow. +{ + int n1 = link->node1; + int n2 = link->node2; - case UP_DRY: - flowDepth1 = FUDGE; - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width1 = getWidth(xsect, flowDepth1); - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - - // --- assign avg. surface area of downstream half of conduit - // to the downstream node - surfArea2 = (widthMid + width2) * length / 4.; - - // --- if there is no free-fall at upstream end, assign the - // upstream node the avg. surface area of the upstream half - if ( Link[j].offset1 <= 0.0 ) - { - surfArea1 = (width1 + widthMid) * length / 4.; - } - break; + // --- check if user-supplied flow limit applies + if (link->qLimit > 0.0) + { + if (fabs(flow) > link->qLimit) flow = SGN(flow) * link->qLimit; + } - case DN_DRY: - flowDepth2 = FUDGE; - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width1 = getWidth(xsect, flowDepth1); - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - - // --- assign avg. surface area of upstream half of conduit - // to the upstream node - surfArea1 = (widthMid + width1) * length / 4.; - - // --- if there is no free-fall at downstream end, assign the - // downstream node the avg. surface area of the downstream half - if ( Link[j].offset2 <= 0.0 ) - { - surfArea2 = (width2 + widthMid) * length / 4.; - } - break; + // --- check for reverse flow with closed flap gate + if (link_setFlapGate(cd->linkIndex, n1, n2, flow)) flow = 0.0; - case DRY: - surfArea1 = FUDGE * length / 2.0; - surfArea2 = surfArea1; - break; - } - Link[j].surfArea1 = surfArea1; - Link[j].surfArea2 = surfArea2; - *y1 = flowDepth1; - *y2 = flowDepth2; + // --- do not allow flow out of a dry node + // (as suggested by R. Dickinson) + if (flow > FUDGE && Node[n1].newDepth <= FUDGE) + flow = FUDGE; + if (flow < -FUDGE && Node[n2].newDepth <= FUDGE) + flow = -FUDGE; + return flow; } //============================================================================= -double findLocalLosses(int j, double a1, double a2, double aMid, double q) -// -// Input: j = link index -// a1 = upstream area (ft2) -// a2 = downstream area (ft2) -// aMid = midpoint area (ft2) -// q = flow rate (cfs) -// Output: returns local losses (ft/sec) -// Purpose: computes local losses term of momentum equation. -// +void saveFlowResult(TLink *link, ConduitData *cd, double flow) { - double losses = 0.0; - q = fabs(q); - if ( a1 > FUDGE ) losses += Link[j].cLossInlet * (q/a1); - if ( a2 > FUDGE ) losses += Link[j].cLossOutlet * (q/a2); - if ( aMid > FUDGE ) losses += Link[j].cLossAvg * (q/aMid); - return losses; + int k = link->subIndex; + double aFull = link->xsect.aFull; + double aAvg = (cd->a1 + cd->a2) / 2.0; + + Conduit[k].a1 = cd->aMid; + Conduit[k].q1 = flow; + Conduit[k].q2 = flow; + Conduit[k].fullState = link_getFullState(cd->a1, cd->a2, aFull); + link->newDepth = MIN(cd->yMid, cd->yFull); + link->newVolume = aAvg * link_getLength(cd->linkIndex) * Conduit[k].barrels; + link->newFlow = flow * Conduit[k].barrels; } //============================================================================= -//// New function added to release 5.1.013. //// //(5.1.013) - double getSlotWidth(TXsect* xsect, double y) +// +// Compute width of Preissmann slot atop a conduit at surcharged depth y. { double yNorm = y / xsect->yFull; - // --- return 0.0 if slot surcharge method not used + // --- check if slot is needed if (SurchargeMethod != SLOT || xsect_isOpen(xsect->type) || - yNorm < CrownCutoff) return 0.0; + yNorm < CrownCutoff) + return 0.0; // --- for depth > 1.78 * pipe depth, slot width = 1% of max. width - if (yNorm > 1.78) return 0.01 * xsect->wMax; + if (yNorm > 1.78) return xsect->wMax * 0.01; // --- otherwise use the Sjoberg formula - return xsect->wMax * 0.5423 * exp(-pow(yNorm, 2.4)); + return xsect->wMax * exp(-pow(yNorm, 2.4)) * 0.5423; } //============================================================================= -//// This function was re-written for release 5.1.013. //// //(5.1.013) - double getWidth(TXsect* xsect, double y) // -// Input: xsect = ptr. to conduit cross section -// y = flow depth (ft) -// Output: returns top width (ft) -// Purpose: computes top width of flow surface in conduit. -// +// Compute top width of conduit cross section (xsect) at flow depth y. { double wSlot = getSlotWidth(xsect, y); if (wSlot > 0.0) return wSlot; @@ -614,85 +817,21 @@ double getWidth(TXsect* xsect, double y) //============================================================================= -//// This function was re-written for release 5.1.013. //// //(5.1.013) - double getArea(TXsect* xsect, double y, double wSlot) // -// Input: xsect = ptr. to conduit cross section -// y = flow depth (ft) -// Output: returns flow area (ft2) -// Purpose: computes area of flow cross-section in a conduit. -// +// Compute area of conduit cross-section (xsect) at flow depth y. { - if ( y >= xsect->yFull ) return xsect->aFull + (y - xsect->yFull) * wSlot; + if ( y >= xsect->yFull ) + return xsect->aFull + (y - xsect->yFull) * wSlot; return xsect_getAofY(xsect, y); } //============================================================================= -//// This function was re-written for release 5.1.013. //// //(5.1.013) - double getHydRad(TXsect* xsect, double y) // -// Input: xsect = ptr. to conduit cross section -// y = flow depth (ft) -// Output: returns hydraulic radius (ft) -// Purpose: computes hydraulic radius of flow cross-section in a conduit. -// +// Compute hydraulic radius of conduit cross-section (xsect) at flow depth y. { if (y >= xsect->yFull) return xsect->rFull; return xsect_getRofY(xsect, y); } - -//============================================================================= - -double checkNormalFlow(int j, double q, double y1, double y2, double a1, - double r1) -// -// Input: j = link index -// q = link flow found from dynamic wave equations (cfs) -// y1 = flow depth at upstream end (ft) -// y2 = flow depth at downstream end (ft) -// a1 = flow area at upstream end (ft2) -// r1 = hyd. radius at upstream end (ft) -// Output: returns modifed flow in link (cfs) -// Purpose: checks if flow in link should be replaced by normal flow. -// -{ - int check = FALSE; - int k = Link[j].subIndex; - int n1 = Link[j].node1; - int n2 = Link[j].node2; - int hasOutfall = (Node[n1].type == OUTFALL || Node[n2].type == OUTFALL); - double qNorm; - double f1; - - // --- check if water surface slope < conduit slope - if ( NormalFlowLtd == SLOPE || NormalFlowLtd == BOTH || hasOutfall ) - { - if ( y1 < y2 ) check = TRUE; - } - - // --- check if Fr >= 1.0 at upstream end of conduit - if ( !check && (NormalFlowLtd == FROUDE || NormalFlowLtd == BOTH) && - !hasOutfall ) - { - if ( y1 > FUDGE && y2 > FUDGE ) - { - f1 = link_getFroude(j, q/a1, y1); - if ( f1 >= 1.0 ) check = TRUE; - } - } - - // --- check if normal flow < dynamic flow - if ( check ) - { - qNorm = Conduit[k].beta * a1 * pow(r1, 2./3.); - if ( qNorm < q ) - { - Link[j].normalFlow = TRUE; - return qNorm; - } - } - return q; -} diff --git a/src/solver/dynwave.c b/src/solver/dynwave.c index 294adc873..df630c36d 100644 --- a/src/solver/dynwave.c +++ b/src/solver/dynwave.c @@ -9,6 +9,7 @@ // 03/19/15 (5.1.008) // 08/01/16 (5.1.011) // 05/10/18 (5.1.013) +// 03/01/20 (5.1.014) // Author: L. Rossman (EPA) // M. Tryby (EPA) // R. Dickinson (CDM) @@ -403,7 +404,7 @@ void findLinkFlows(double dt) for ( i = 0; i < Nobjects[LINK]; i++) { if ( !isTrueConduit(i) ) - { + { if ( !Link[i].bypassed ) findNonConduitFlow(i, dt); updateNodeFlows(i); } diff --git a/src/solver/error.c b/src/solver/error.c index 843823878..17c0a0ed5 100644 --- a/src/solver/error.c +++ b/src/solver/error.c @@ -6,6 +6,7 @@ // Date: 03/20/14 (Build 5.1.001) // 03/19/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) +// 04/14/20 (Build 5.1.015) // Author: L. Rossman // // Error messages @@ -16,6 +17,8 @@ // Build 5.1.010: // - Text of Error 318 for rainfall data files modified. // +// Build 5.1.015: +// - Added new Error 140 for storage nodes. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -55,6 +58,8 @@ #define ERR138 \ "\n ERROR 138: Node %s has initial depth greater than maximum depth." #define ERR139 "\n ERROR 139: Regulator %s is the outlet of a non-storage node." +#define ERR140 \ +"\n ERROR 140: Storage node %s has negative volume at full depth." //(5.1.015) #define ERR141 \ "\n ERROR 141: Outfall %s has more than 1 inlet link or an outlet link." #define ERR143 "\n ERROR 143: Regulator %s has invalid cross-section shape." @@ -197,7 +202,7 @@ char* ErrorMsgs[] = ERR327, ERR329, ERR330, ERR331, ERR333, ERR335, ERR336, ERR337, ERR338, ERR339, ERR341, ERR343, ERR345, ERR351, ERR353, ERR355, ERR357, ERR361, ERR363, ERR401, ERR402, ERR403, ERR405, ERR501, ERR502, ERR503, ERR504, - ERR505, ERR506, ERR507, ERR508, ERR509}; + ERR505, ERR506, ERR507, ERR508, ERR509, ERR140}; //(5.1.015) int ErrorCodes[] = { 0, 101, 103, 105, 107, 108, 109, 110, 111, @@ -212,7 +217,7 @@ int ErrorCodes[] = 327, 329, 330, 331, 333, 335, 336, 337, 338, 339, 341, 343, 345, 351, 353, 355, 357, 361, 363, 401, 402, 403, 405, 501, 502, 503, 504, - 505, 506, 507, 508, 509}; + 505, 506, 507, 508, 509, 140}; //(5.1.015) char ErrString[256]; diff --git a/src/solver/error.h b/src/solver/error.h index 7ffef1b7e..8ce6cc07d 100644 --- a/src/solver/error.h +++ b/src/solver/error.h @@ -4,6 +4,7 @@ // Project: EPA SWMM5 // Version: 5.1 // Date: 03/20/14 (Build 5.1.001) +// 04/14/20 (Build 5.1.015) // Author: L. Rossman // // Error codes @@ -170,6 +171,9 @@ enum ErrorType { ERR_API_INFLOWTYPE, //507 110 ERR_API_TSERIES_INDEX, //508 111 ERR_API_PATTERN_INDEX, //509 112 + + //... Additional Errors + ERR_STORAGE_VOLUME, //140 113 //(5.1.015) MAXERRMSG}; char* error_getMsg(int i); diff --git a/src/solver/flowrout.c b/src/solver/flowrout.c index b50b12787..675e39300 100644 --- a/src/solver/flowrout.c +++ b/src/solver/flowrout.c @@ -640,9 +640,9 @@ void setNewNodeState(int j, double dt) // --- update terminal storage nodes if ( Node[j].type == STORAGE ) - { - if ( Node[j].updated == FALSE ) - updateStorageState(j, Nobjects[LINK], NULL, dt); + { + if ( Node[j].updated == FALSE ) + updateStorageState(j, Nobjects[LINK], NULL, dt); return; } diff --git a/src/solver/globals.h b/src/solver/globals.h index 1d7c9207e..b9f110cdb 100644 --- a/src/solver/globals.h +++ b/src/solver/globals.h @@ -10,6 +10,7 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman // // Global Variables @@ -35,6 +36,9 @@ // // Build 5.1.013: // - CrownCutoff and RuleStep added as analysis option variables. +// +// Build 5.1.015: +// - Fixes bug in summary statistics when Report Start date > Start Date. //----------------------------------------------------------------------------- EXTERN TFile @@ -52,7 +56,8 @@ EXTERN TFile EXTERN long Nperiods, // Number of reporting periods - StepCount, // Number of routing steps used + TotalStepCount, // Total routing steps used //(5.1.015) + ReportStepCount, // Reporting routing steps used //(5.1.015) NonConvergeCount; // Number of non-converging steps EXTERN char diff --git a/src/solver/hotstart.c b/src/solver/hotstart.c index be81670bd..33c005e45 100644 --- a/src/solver/hotstart.c +++ b/src/solver/hotstart.c @@ -8,6 +8,7 @@ // 04/23/14 (Build 5.1.005) // 03/19/15 (Build 5.1.008) // 08/01/16 (Build 5.1.011) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman (EPA) // // Hot Start file functions. @@ -37,6 +38,8 @@ // Build 5.1.011: // - Link control setting bug when reading a hot start file fixed. // +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -378,7 +381,7 @@ void saveRunoff(void) // Infiltration state (max. of 6 elements) for (j=0; j= 0 ) + { + m = i; + --ntoks; + } + // --- number of input tokens depends on infiltration model m - if ( m == HORTON ) n = 5; + if ( m == HORTON ) n = 5; else if ( m == MOD_HORTON ) n = 5; else if ( m == GREEN_AMPT ) n = 4; else if ( m == MOD_GREEN_AMPT ) n = 4; else if ( m == CURVE_NUMBER ) n = 4; - else return 0; - if ( ntoks < n ) return error_setInpError(ERR_ITEMS, ""); + else return 0; + if ( ntoks < n ) return error_setInpError(ERR_ITEMS, ""); + // --- parse numerical values from tokens for (i = 0; i < 5; i++) x[i] = 0.0; for (i = 1; i < n; i++) { - if ( ! getDouble(tok[i], &x[i-1]) ) + if (!getDouble(tok[i], &x[i - 1])) return error_setInpError(ERR_NUMBER, tok[i]); } @@ -183,18 +181,19 @@ int infil_readParams(int m, char* tok[], int ntoks) return error_setInpError(ERR_NUMBER, tok[n]); } - // --- assign parameter values to infil. object + // --- assign parameter values to infil, infilModel object Subcatch[j].infil = j; + Subcatch[j].infilModel = m; switch (m) { case HORTON: - case MOD_HORTON: status = horton_setParams(&HortInfil[j], x); + case MOD_HORTON: status = horton_setParams(&Infil[j].horton, x); break; case GREEN_AMPT: case MOD_GREEN_AMPT: - status = grnampt_setParams(&GAInfil[j], x); + status = grnampt_setParams(&Infil[j].grnAmpt, x); break; - case CURVE_NUMBER: status = curvenum_setParams(&CNInfil[j], x); + case CURVE_NUMBER: status = curvenum_setParams(&Infil[j].curveNum, x); break; default: status = TRUE; } @@ -204,49 +203,47 @@ int infil_readParams(int m, char* tok[], int ntoks) //============================================================================= -void infil_initState(int j, int m) +void infil_initState(int j) // // Input: j = subcatchment index -// m = infiltration method code // Output: none // Purpose: initializes state of infiltration for a subcatchment. // { - switch (m) + switch (Subcatch[j].infilModel) { case HORTON: - case MOD_HORTON: horton_initState(&HortInfil[j]); break; + case MOD_HORTON: horton_initState(&Infil[j].horton); break; case GREEN_AMPT: case MOD_GREEN_AMPT: - grnampt_initState(&GAInfil[j]); break; - case CURVE_NUMBER: curvenum_initState(&CNInfil[j]); break; + grnampt_initState(&Infil[j].grnAmpt); break; + case CURVE_NUMBER: curvenum_initState(&Infil[j].curveNum); break; } } //============================================================================= -void infil_getState(int j, int m, double x[]) +void infil_getState(int j, double x[]) // // Input: j = subcatchment index -// m = infiltration method code -// Output: none +// Output: x = subcatchment's infiltration state // Purpose: retrieves the current infiltration state for a subcatchment. // { - switch (m) + switch (Subcatch[j].infilModel) { case HORTON: - case MOD_HORTON: horton_getState(&HortInfil[j], x); break; + case MOD_HORTON: horton_getState(&Infil[j].horton, x); break; case GREEN_AMPT: case MOD_GREEN_AMPT: - grnampt_getState(&GAInfil[j],x); break; - case CURVE_NUMBER: curvenum_getState(&CNInfil[j], x); break; + grnampt_getState(&Infil[j].grnAmpt, x); break; + case CURVE_NUMBER: curvenum_getState(&Infil[j].curveNum, x); break; } } //============================================================================= -void infil_setState(int j, int m, double x[]) +void infil_setState(int j, double x[]) // // Input: j = subcatchment index // m = infiltration method code @@ -254,14 +251,14 @@ void infil_setState(int j, int m, double x[]) // Purpose: sets the current infiltration state for a subcatchment. // { - switch (m) + switch (Subcatch[j].infilModel) { case HORTON: - case MOD_HORTON: horton_setState(&HortInfil[j], x); break; + case MOD_HORTON: horton_setState(&Infil[j].horton, x); break; case GREEN_AMPT: case MOD_GREEN_AMPT: - grnampt_setState(&GAInfil[j],x); break; - case CURVE_NUMBER: curvenum_setState(&CNInfil[j], x); break; + grnampt_setState(&Infil[j].grnAmpt, x); break; + case CURVE_NUMBER: curvenum_setState(&Infil[j].curveNum, x); break; } } @@ -295,11 +292,10 @@ void infil_setInfilFactor(int j) //============================================================================= -double infil_getInfil(int j, int m, double tstep, double rainfall, +double infil_getInfil(int j, double tstep, double rainfall, double runon, double depth) // // Input: j = subcatchment index -// m = infiltration method code // tstep = runoff time step (sec) // rainfall = rainfall rate (ft/sec) // runon = runon rate from other sub-areas or subcatchments (ft/sec) @@ -308,22 +304,23 @@ double infil_getInfil(int j, int m, double tstep, double rainfall, // Purpose: computes infiltration rate depending on infiltration method. // { - switch (m) + switch (Subcatch[j].infilModel) { case HORTON: - return horton_getInfil(&HortInfil[j], tstep, rainfall+runon, depth); + return horton_getInfil(&Infil[j].horton, tstep, rainfall+runon, depth); case MOD_HORTON: - return modHorton_getInfil(&HortInfil[j], tstep, rainfall+runon, + return modHorton_getInfil(&Infil[j].horton, tstep, rainfall+runon, depth); case GREEN_AMPT: case MOD_GREEN_AMPT: - return grnampt_getInfil(&GAInfil[j], tstep, rainfall+runon, depth, m); + return grnampt_getInfil(&Infil[j].grnAmpt, tstep, rainfall+runon, depth, + Subcatch[j].infilModel); case CURVE_NUMBER: depth += runon / tstep; - return curvenum_getInfil(&CNInfil[j], tstep, rainfall, depth); + return curvenum_getInfil(&Infil[j].curveNum, tstep, rainfall, depth); default: return 0.0; @@ -341,7 +338,7 @@ int horton_setParams(THorton *infil, double p[]) // { int k; - for (k=0; k<5; k++) if ( p[k] < 0.0 ) return FALSE; + for (k = 0; k < 5; k++) if ( p[k] < 0.0 ) return FALSE; // --- max. & min. infil rates (ft/sec) infil->f0 = p[0] / UCF(RAINFALL); @@ -566,6 +563,21 @@ double modHorton_getInfil(THorton *infil, double tstep, double irate, //============================================================================= +void grnampt_getParams(int j, double p[]) +// +// Input: j = subcatchment index +// p[] = array of parameter values +// Output: none +// Purpose: retrieves Green-Ampt infiltration parameters for a subcatchment. +// +{ + p[0] = Infil[j].grnAmpt.S * UCF(RAINDEPTH); // Capillary suction head (ft) + p[1] = Infil[j].grnAmpt.Ks * UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) + p[2] = Infil[j].grnAmpt.IMDmax; // Max. init. moisture deficit +} + +//============================================================================= + int grnampt_setParams(TGrnAmpt *infil, double p[]) // // Input: infil = ptr. to Green-Ampt infiltration object diff --git a/src/solver/infil.h b/src/solver/infil.h index cf208e234..3aff6f2f4 100644 --- a/src/solver/infil.h +++ b/src/solver/infil.h @@ -7,6 +7,7 @@ // 09/15/14 (Build 5.1.007) // 08/05/15 (Build 5.1.010) // 05/10/18 (Build 5.1.013) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman (US EPA) // // Public interface for infiltration functions. @@ -16,6 +17,9 @@ // // Build 5.1.013: // - New function infil_setInfilFactor() added. +// +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. //----------------------------------------------------------------------------- #ifndef INFIL_H @@ -93,16 +97,17 @@ extern TCurveNum* CNInfil; //----------------------------------------------------------------------------- // Infiltration Methods //----------------------------------------------------------------------------- -void infil_create(int subcatchCount, int model); +void infil_create(int n); void infil_delete(void); -int infil_readParams(int model, char* tok[], int ntoks); -void infil_initState(int area, int model); -void infil_getState(int j, int m, double x[]); -void infil_setState(int j, int m, double x[]); -void infil_setInfilFactor(int j); //(5.1.013) -double infil_getInfil(int area, int model, double tstep, double rainfall, - double runon, double depth); +int infil_readParams(int m, char* tok[], int ntoks); +void infil_initState(int j); +void infil_getState(int j, double x[]); +void infil_setState(int j, double x[]); +void infil_setInfilFactor(int j); +double infil_getInfil(int area, double tstep, double rainfall, double runon, + double depth); +void grnampt_getParams(int j, double p[]); int grnampt_setParams(TGrnAmpt *infil, double p[]); void grnampt_initState(TGrnAmpt *infil); double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, diff --git a/src/solver/inflow.c b/src/solver/inflow.c index e11e15086..42976c13e 100644 --- a/src/solver/inflow.c +++ b/src/solver/inflow.c @@ -71,11 +71,11 @@ int inflow_readExtInflow(char* tok[], int ntoks) Tseries[tseries].refersTo = EXTERNAL_INFLOW; } - // --- assign type & cf values for a FLOW inflow - if (param == -1) - { - type = FLOW_INFLOW; - } + // --- assign type & cf values for a FLOW inflow + if (param == -1) + { + type = FLOW_INFLOW; + } // --- do the same for a pollutant inflow if ( ntoks >= 4 && param > -1) @@ -115,9 +115,9 @@ int inflow_readExtInflow(char* tok[], int ntoks) basePat = project_findObject(TIMEPATTERN, tok[7]); if ( basePat < 0 ) return error_setInpError(ERR_NAME, tok[7]); } - - return(inflow_setExtInflow(j, param, type, tseries, basePat, - cf, baseline, sf)); + + return(inflow_setExtInflow(j, param, type, tseries, basePat, + cf, baseline, sf)); } int inflow_validate(int param, int type, int tseries, int basePat, double *cf) @@ -130,49 +130,49 @@ int inflow_validate(int param, int type, int tseries, int basePat, double *cf) // Output: cf = Unit Conversion // Return: returns Error Code { - int errcode = 0; - // Validate param - if (param >= Nobjects[POLLUT]) - { - errcode = ERR_API_POLLUT_INDEX; - } - // Validate Type - else if (type != FLOW_INFLOW - && type != CONCEN_INFLOW - && type != MASS_INFLOW) - { - errcode = ERR_KEYWORD; - } - // Validate Timeseries Index - else if (tseries >= Nobjects[TSERIES]) - { - errcode = ERR_API_TSERIES_INDEX; - } - // Validate Timepattern Index - else if (basePat >= Nobjects[TIMEPATTERN]) - { - errcode = ERR_API_PATTERN_INDEX; - } - else - { - // --- assign type & cf values for a FLOW inflow - if ( type == FLOW_INFLOW ) - { - *cf = 1.0/UCF(FLOW); - } - // --- include LperFT3 term in conversion factor for MASS_INFLOW - else if ( type == MASS_INFLOW ) - { - *cf /= LperFT3; - } - } - - return(errcode); + int errcode = 0; + // Validate param + if (param >= Nobjects[POLLUT]) + { + errcode = ERR_API_POLLUT_INDEX; + } + // Validate Type + else if (type != FLOW_INFLOW + && type != CONCEN_INFLOW + && type != MASS_INFLOW) + { + errcode = ERR_KEYWORD; + } + // Validate Timeseries Index + else if (tseries >= Nobjects[TSERIES]) + { + errcode = ERR_API_TSERIES_INDEX; + } + // Validate Timepattern Index + else if (basePat >= Nobjects[TIMEPATTERN]) + { + errcode = ERR_API_PATTERN_INDEX; + } + else + { + // --- assign type & cf values for a FLOW inflow + if ( type == FLOW_INFLOW ) + { + *cf = 1.0/UCF(FLOW); + } + // --- include LperFT3 term in conversion factor for MASS_INFLOW + else if ( type == MASS_INFLOW ) + { + *cf /= LperFT3; + } + } + + return(errcode); } int inflow_setExtInflow(int j, int param, int type, int tseries, int basePat, - double cf, double baseline, double sf) + double cf, double baseline, double sf) // Purpose: This function assigns property values to the inflow object // Inputs: j = Node index // param = FLOW (-1) or pollutant index @@ -185,45 +185,45 @@ int inflow_setExtInflow(int j, int param, int type, int tseries, int basePat, // Return: returns Error Code { - int errcode = 0; - - // Validate Inflow - errcode = inflow_validate(param, type, tseries, basePat, &cf); - - if (errcode == 0) - { - TExtInflow* inflow; // external inflow object - - // --- check if an external inflow object for this constituent already exists - inflow = Node[j].extInflow; - while ( inflow ) - { - if ( inflow->param == param ) break; - inflow = inflow->next; - } - - // --- if it doesn't exist, then create it - if ( inflow == NULL ) - { - inflow = (TExtInflow *) malloc(sizeof(TExtInflow)); - if ( inflow == NULL ) - { - return error_setInpError(ERR_MEMORY, ""); - } - inflow->next = Node[j].extInflow; - Node[j].extInflow = inflow; - } - - // Assigning Values to the inflow object - inflow->param = param; - inflow->type = type; - inflow->tSeries = tseries; - inflow->cFactor = cf; - inflow->sFactor = sf; - inflow->baseline = baseline; - inflow->basePat = basePat; - inflow->extIfaceInflow = 0.0; - } + int errcode = 0; + TExtInflow* inflow; // external inflow object + + // Validate Inflow + errcode = inflow_validate(param, type, tseries, basePat, &cf); + + if (errcode == 0) + { + + // --- check if an external inflow object for this constituent already exists + inflow = Node[j].extInflow; + while ( inflow ) + { + if ( inflow->param == param ) break; + inflow = inflow->next; + } + + // --- if it doesn't exist, then create it + if ( inflow == NULL ) + { + inflow = (TExtInflow *) malloc(sizeof(TExtInflow)); + if ( inflow == NULL ) + { + return error_setInpError(ERR_MEMORY, ""); + } + inflow->next = Node[j].extInflow; + Node[j].extInflow = inflow; + } + + // Assigning Values to the inflow object + inflow->param = param; + inflow->type = type; + inflow->tSeries = tseries; + inflow->cFactor = cf; + inflow->sFactor = sf; + inflow->baseline = baseline; + inflow->basePat = basePat; + inflow->extIfaceInflow = 0.0; + } return(errcode); } @@ -265,7 +265,7 @@ double inflow_getExtInflow(TExtInflow* inflow, DateTime aDate) double sf = inflow->sFactor; // scaling factor double blv = inflow->baseline; // baseline value double tsv = 0.0; // time series value - double extIfaceInflow = inflow->extIfaceInflow;// external interfacing inflow + double extIfaceInflow = inflow->extIfaceInflow;// external interfacing inflow if ( p >= 0 ) { diff --git a/src/solver/input.c b/src/solver/input.c index b36fd4289..390b6f088 100644 --- a/src/solver/input.c +++ b/src/solver/input.c @@ -6,6 +6,7 @@ // Date: 03/20/14 (Build 5.1.001) // 09/15/14 (Build 5.1.007) // 08/01/16 (Build 5.1.011) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman // // Input data processing functions. @@ -16,6 +17,8 @@ // Build 5.1.011: // - Support added for reading hydraulic event dates. // +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -478,7 +481,7 @@ int parseLine(int sect, char *line) return subcatch_readSubareaParams(Tok, Ntokens); case s_INFIL: - return infil_readParams(InfilModel, Tok, Ntokens); + return infil_readParams(InfilModel, Tok, Ntokens); //(5.1.015) case s_AQUIFER: j = Mobjects[AQUIFER]; diff --git a/src/solver/lid.c b/src/solver/lid.c index 4dcf02a1d..57dd9c60d 100644 --- a/src/solver/lid.c +++ b/src/solver/lid.c @@ -13,6 +13,7 @@ // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) // 03/01/20 (Build 5.1.014) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman (US EPA) // // This module handles all data processing involving LID (Low Impact @@ -77,6 +78,9 @@ // Build 5.1.014: // - Fixed bug in creating LidProcs when there are no subcatchments. // - Fixed bug in adding underdrain pollutant loads to mass balances. +// +// Build 5.1.015: +// - Support added for mutiple infiltration methods within a project. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -262,7 +266,7 @@ void lid_create(int lidCount, int subcatchCount) return; } } - + //... initialize LID groups for (j = 0; j < GroupCount; j++) LidGroups[j] = NULL; @@ -761,7 +765,7 @@ int readDrainData(int j, char* toks[], int ntoks) for (i = 0; i < 6; i++) x[i] = 0.0; //(5.1.013) for (i = 2; i < 8; i++) // { - if ( ntoks > i && ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) //(5.1.013) + if ( ntoks > i && ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) //(5.1.013) return error_setInpError(ERR_NUMBER, toks[i]); } @@ -803,7 +807,7 @@ int readDrainMatData(int j, char* toks[], int ntoks) //... read numerical parameters if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); - if ( LidProcs[j].lidType != GREEN_ROOF ) return 0; + if ( LidProcs[j].lidType != GREEN_ROOF ) return 0; for (i = 2; i < 5; i++) { if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) @@ -1096,12 +1100,12 @@ void validateLidProc(int j) //... set storage layer parameters of a green roof if ( LidProcs[j].lidType == GREEN_ROOF ) - { - LidProcs[j].storage.thickness = LidProcs[j].drainMat.thickness; - LidProcs[j].storage.voidFrac = LidProcs[j].drainMat.voidFrac; - LidProcs[j].storage.clogFactor = 0.0; - LidProcs[j].storage.kSat = 0.0; - } + { + LidProcs[j].storage.thickness = LidProcs[j].drainMat.thickness; + LidProcs[j].storage.voidFrac = LidProcs[j].drainMat.voidFrac; + LidProcs[j].storage.clogFactor = 0.0; + LidProcs[j].storage.kSat = 0.0; + } } //============================================================================= @@ -1155,11 +1159,10 @@ void validateLidGroup(int j) //... assign vegetative swale infiltration parameters if ( LidProcs[k].lidType == VEG_SWALE ) { - if ( InfilModel == GREEN_AMPT || InfilModel == MOD_GREEN_AMPT ) + if ( Subcatch[j].infilModel == GREEN_AMPT || //(5.1.015) + Subcatch[j].infilModel == MOD_GREEN_AMPT ) //(5.1.015) { - p[0] = GAInfil[j].S * UCF(RAINDEPTH); - p[1] = GAInfil[j].Ks * UCF(RAINFALL); - p[2] = GAInfil[j].IMDmax; + grnampt_getParams(j, p); //(5.1.015) if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) { strcpy(Msg, LidProcs[k].ID); @@ -1720,10 +1723,10 @@ void findNativeInfil(int j, double tStep) //... otherwise find infil. rate for the subcatchment's rainfall + runon else { - NativeInfil = infil_getInfil(j, InfilModel, tStep, + NativeInfil = infil_getInfil(j, tStep, Subcatch[j].rainfall, Subcatch[j].runon, - getSurfaceDepth(j)); //(5.1.008) + getSurfaceDepth(j)); //(5.1.015) } //... see if there is any groundwater-imposed limit on infil. diff --git a/src/solver/node.c b/src/solver/node.c index 4312b2d01..ac1061621 100644 --- a/src/solver/node.c +++ b/src/solver/node.c @@ -9,6 +9,7 @@ // 08/05/15 (Build 5.1.010) // 05/10/18 (Build 5.1.013) // 03/01/20 (Build 5.1.014) +// 04/14/20 (Build 5.1.015) // Author: L. Rossman // // Conveyance system node functions. @@ -31,6 +32,10 @@ // // Build 5.1.014: // - Fixed bug in storage_losses() that affected storage exfiltration. +// +// Build 5.1.015: +// - Fatal error issued if a storage node's area curve produces a negative +// volume when extrapolated to the node's full depth. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -211,6 +216,11 @@ void node_validate(int j) if ( Node[j].initDepth > Node[j].fullDepth + Node[j].surDepth ) report_writeErrorMsg(ERR_NODE_DEPTH, Node[j].ID); + // --- check for negative volume for storage node at full depth //(5.1.015) + if (Node[j].type == STORAGE) // + if (node_getVolume(j, Node[j].fullDepth) < 0.0) // + report_writeErrorMsg(ERR_STORAGE_VOLUME, Node[j].ID); // + if ( Node[j].type == DIVIDER ) divider_validate(j); // --- initialize dry weather inflows @@ -795,7 +805,7 @@ void storage_getVolDiff(double y, double* f, double* df, void* p) int k; double e, v; TStorageVol* storageVol; - + // ... cast void pointer p to a TStorageVol object storageVol = (TStorageVol *)p; k = storageVol->k; @@ -831,7 +841,7 @@ double storage_getVolume(int j, double d) // --- use table integration if area v. depth table exists if ( i >= 0 ) - return table_getArea(&Curve[i], d*UCF(LENGTH)) / UCF(VOLUME); + return table_getArea(&Curve[i], d*UCF(LENGTH)) / UCF(VOLUME); // --- otherwise use functional area v. depth relation else @@ -841,6 +851,7 @@ double storage_getVolume(int j, double d) v += Storage[k].aCoeff / (Storage[k].aExpon+1.0) * pow(d, Storage[k].aExpon+1.0); return v / UCF(VOLUME); + } } diff --git a/src/solver/objects.h b/src/solver/objects.h index 9fd738067..8815a5efc 100644 --- a/src/solver/objects.h +++ b/src/solver/objects.h @@ -9,6 +9,7 @@ // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) // 05/10/18 (Build 5.1.013) +// 04/01/20 (Build 5.1.015) // // Author: L. Rossman (EPA) // M. Tryby (EPA) @@ -54,6 +55,10 @@ // - Adjustment patterns added to TSubcatch structure. // - Members impervRunoff and pervRunoff added to TSubcatchStats structure. // - Member cdCurve (weir coeff. curve) added to TWeir structure. +// +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. +// - Support added for grouped freqency table of routing time steps. //----------------------------------------------------------------------------- #include "mathexpr.h" @@ -366,6 +371,7 @@ typedef struct int gage; // raingage index int outNode; // outlet node index int outSubcatch; // outlet subcatchment index + int infilModel; // infiltration method index //(5.1.015) int infil; // infiltration object index TSubarea subArea[3]; // sub-area data double width; // overland flow width (ft) @@ -900,6 +906,7 @@ typedef struct //----------------------- // SYSTEM-WIDE STATISTICS //----------------------- +#define TIMELEVELS 6 //(5.1.015) typedef struct { double minTimeStep; @@ -907,6 +914,8 @@ typedef struct double avgTimeStep; double avgStepCount; double steadyStateCount; + double timeStepIntervals[TIMELEVELS]; //(5.1.015) + int timeStepCounts[TIMELEVELS]; //(5.1.015) } TSysStats; //-------------------- diff --git a/src/solver/project.c b/src/solver/project.c index 0fae207d5..db9d116c6 100644 --- a/src/solver/project.c +++ b/src/solver/project.c @@ -11,6 +11,7 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman // // Project management functions. @@ -55,6 +56,8 @@ // - More robust parsing of MinSurfarea option provided. // - Support added for new RuleStep analysis option. // +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -1014,7 +1017,7 @@ void createObjects() if ( ErrorCode ) return; // --- allocate memory for infiltration data - infil_create(Nobjects[SUBCATCH], InfilModel); + infil_create(Nobjects[SUBCATCH]); //(5.1.015) // --- allocate memory for water quality state variables for (j = 0; j < Nobjects[SUBCATCH]; j++) diff --git a/src/solver/qualrout.c b/src/solver/qualrout.c index 7c16e45af..3a23e4e72 100644 --- a/src/solver/qualrout.c +++ b/src/solver/qualrout.c @@ -7,6 +7,7 @@ // 04/02/15 (Build 5.1.008) // 04/30/15 (Build 5.1.009) // 08/05/15 (Build 5.1.010) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman // // Water quality routing functions. @@ -22,6 +23,8 @@ // - Entire module re-written to be more compact and easier to follow. // - Neglible depth limit replaced with a negligible volume limit. // +// Build 5.1.015: +// - Fixed mass balance issue for empty storage nodes that flood. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -443,8 +446,8 @@ void findStorageQual(int j, double tStep) wIn = Node[j].newQual[p]; c2 = getMixedQual(c1, v1, wIn, qIn, tStep); - // --- set concen. to zero if remaining volume is negligible - if ( Node[j].newVolume <= ZeroVolume ) +// --- set concen. to zero if remaining volume & inflow is negligible //(5.1.015) + if (Node[j].newVolume <= ZeroVolume && qIn <= FLOW_TOL) //(5.1.015) { massbal_addToFinalStorage(p, c2 * Node[j].newVolume); c2 = 0.0; diff --git a/src/solver/report.c b/src/solver/report.c index 7a0ced556..01e3cbe29 100644 --- a/src/solver/report.c +++ b/src/solver/report.c @@ -11,6 +11,7 @@ // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) // 03/01/20 (Build 5.1.014) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman (EPA) // // Report writing functions. @@ -43,6 +44,10 @@ // // Build 5.1.014: // - Fixed bug in confusing keywords with ID names in report_readOptions(). +// +// Build 5.1.015: +// - Fixes bug in summary statistics when Report Start date > Start Date. +// - Support added for grouped freqency table of routing time steps. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -86,7 +91,7 @@ static void report_Nodes(void); static void report_NodeHeader(char *id); static void report_Links(void); static void report_LinkHeader(char *id); - +static void report_RouteStepFreq(TSysStats* sysStats); //(5.1.015) //============================================================================= @@ -1031,10 +1036,11 @@ void report_writeSysStats(TSysStats* sysStats) // { double x; - double eventStepCount = (double)StepCount - sysStats->steadyStateCount; + double eventStepCount; // Routing steps taken during reporting period //(5.1.015) - if ( Nobjects[LINK] == 0 || StepCount == 0 - || eventStepCount == 0.0 ) return; + eventStepCount = ReportStepCount - sysStats->steadyStateCount; //(5.1.015) + if ( Nobjects[LINK] == 0 || TotalStepCount == 0 + || eventStepCount == 0.0 ) return; WRITE(""); WRITE("*************************"); WRITE("Routing Time Step Summary"); @@ -1057,9 +1063,37 @@ void report_writeSysStats(TSysStats* sysStats) fprintf(Frpt.file, "\n Percent Not Converging : %7.2f", 100.0 * (double)NonConvergeCount / eventStepCount); + + // --- write grouped frequency table of variable routing time steps //(5.1.015) + if (RouteModel == DW && CourantFactor > 0.0) // + report_RouteStepFreq(sysStats); // WRITE(""); } +//============================================================================= + +//// New function added to release 5.1.015. //// //(5.1.015) +void report_RouteStepFreq(TSysStats* sysStats) +// +// Input: sysStats = simulation statistics for overall system +// Output: none +// Purpose: writes grouped frequency table of routing time steps to report file. +// +{ + double totalSteps = 0.0; + int i; + + for (i = 1; i < TIMELEVELS; i++) + totalSteps += sysStats->timeStepCounts[i]; + fprintf(Frpt.file, + "\n Time Step Frequencies :"); + for (i = 1; i < TIMELEVELS; i++) + fprintf(Frpt.file, + "\n %5.2f - %5.2f sec : %7.2f %%", + sysStats->timeStepIntervals[i-1], sysStats->timeStepIntervals[i], + 100.0 * (double)(sysStats->timeStepCounts[i]) / totalSteps); +} + //============================================================================= // SIMULATION RESULTS REPORTING diff --git a/src/solver/stats.c b/src/solver/stats.c index 7d6cb8b90..13dbbc60f 100644 --- a/src/solver/stats.c +++ b/src/solver/stats.c @@ -9,6 +9,7 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman (EPA) // R. Dickinson (CDM) // @@ -36,6 +37,10 @@ // - Statistics on impervious and pervious runoff totals added. // - Storage nodes with a non-zero surcharge depth (e.g. enclosed tanks) // can now be classified as being surcharged. +// +// Build 5.1.015: +// - Fixes bug in summary statistics when Report Start date > Start Date. +// - Support added for grouped freqency table of routing time steps. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -106,6 +111,9 @@ int stats_open() // { int j, k; + double timeStepDelta; //(5.1.015) + double logMaxTimeStep; //(5.1.015) + double logMinTimeStep; //(5.1.015) // --- set all pointers to NULL NodeStats = NULL; @@ -286,6 +294,20 @@ int stats_open() SysStats.avgTimeStep = 0.0; SysStats.avgStepCount = 0.0; SysStats.steadyStateCount = 0.0; + + // --- divide range between min and max routing time steps into //(5.1.015) + // equal intervals using a logarithmic scale // + logMaxTimeStep = log10(RouteStep); // + logMinTimeStep = log10(MinRouteStep); // + timeStepDelta = (logMaxTimeStep - logMinTimeStep) / (TIMELEVELS-1); // + SysStats.timeStepIntervals[0] = RouteStep; // + for (j = 1; j < TIMELEVELS; j++) // + { // + SysStats.timeStepIntervals[j] = // + pow(10., logMaxTimeStep - j * timeStepDelta); // + SysStats.timeStepCounts[j] = 0; // + } // + SysStats.timeStepIntervals[TIMELEVELS - 1] = MinRouteStep; // return 0; } @@ -446,6 +468,7 @@ void stats_updateFlowStats(double tStep, DateTime aDate, int stepCount, } // --- update count of times in steady state + ReportStepCount++; SysStats.steadyStateCount += steadyState; // --- update time step stats if not in steady state @@ -455,6 +478,15 @@ void stats_updateFlowStats(double tStep, DateTime aDate, int stepCount, if ( OldRoutingTime > 0 ) { SysStats.minTimeStep = MIN(SysStats.minTimeStep, tStep); + + // --- locate interval that logged time step falls in //(5.1.015) + // and update its count // + for (j = 1; j < TIMELEVELS; j++) // + if (tStep >= SysStats.timeStepIntervals[j]) // + { // + SysStats.timeStepCounts[j]++; // + break; // + } // } SysStats.avgTimeStep += tStep; SysStats.maxTimeStep = MAX(SysStats.maxTimeStep, tStep); @@ -703,6 +735,7 @@ void stats_findMaxStats() { int j; double x; + double stepCount = ReportStepCount - SysStats.steadyStateCount; //(5.1.015) // --- initialize max. stats arrays for (j=0; j 2 ) + if ( stepCount > 2 ) //(5.1.015) { for (j=0; j Start Date. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -337,7 +341,7 @@ void writeNodeDepths() fprintf(Frpt.file, " %-9s ", NodeTypeWords[Node[j].type]); getElapsedTime(NodeStats[j].maxDepthDate, &days, &hrs, &mins); fprintf(Frpt.file, "%7.2f %7.2f %7.2f %4d %02d:%02d %10.2f", - NodeStats[j].avgDepth / StepCount * UCF(LENGTH), + NodeStats[j].avgDepth / ReportStepCount * UCF(LENGTH), //(5.1.015) NodeStats[j].maxDepth * UCF(LENGTH), (NodeStats[j].maxDepth + Node[j].invertElev) * UCF(LENGTH), days, hrs, mins, NodeStats[j].maxRptDepth); @@ -538,7 +542,7 @@ void writeStorageVolumes() if ( Node[j].type != STORAGE ) continue; k = Node[j].subIndex; fprintf(Frpt.file, "\n %-20s", Node[j].ID); - avgVol = StorageStats[k].avgVol / StepCount; + avgVol = StorageStats[k].avgVol / (double)ReportStepCount; //(5.1.015) maxVol = StorageStats[k].maxVol; pctMaxVol = 0.0; pctAvgVol = 0.0; @@ -634,7 +638,7 @@ void writeOutfallLoads() // --- print node ID, flow freq., avg. flow, max. flow & flow vol. fprintf(Frpt.file, "\n %-20s", Node[j].ID); - x = 100.*flowCount/(double)StepCount; + x = 100.*flowCount/(double)ReportStepCount; //(5.1.015) fprintf(Frpt.file, "%7.2f", x); freqSum += x; if ( flowCount > 0 ) @@ -804,7 +808,7 @@ void writeFlowClass() for ( i=0; iinflow, subarea->depth); // --- limit infiltration rate by available void space in unsaturated diff --git a/src/solver/swmm5.c b/src/solver/swmm5.c index 2917cb5e3..ab4ae2c1e 100644 --- a/src/solver/swmm5.c +++ b/src/solver/swmm5.c @@ -8,6 +8,7 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman // // This is the main module of the computational engine for Version 5 of @@ -38,7 +39,9 @@ // Build 5.1.013: // - Support added for saving average results within a reporting period. // - SWMM engine now always compiled to a shared object library. -// +// +// Build 5.1.015: +// - Fixes bug in summary statistics when Report Start date > Start Date. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -306,7 +309,8 @@ int DLLEXPORT swmm_start(int saveResults) NewRunoffTime = 0.0; NewRoutingTime = 0.0; ReportTime = (double)(1000 * ReportStep); - StepCount = 0; + TotalStepCount = 0; //(5.1.015) + ReportStepCount = 0; //(5.1.015) NonConvergeCount = 0; IsStartedFlag = TRUE; @@ -440,7 +444,7 @@ int DLLEXPORT swmm_step(double* elapsedTime) #ifdef EXH // --- end of try loop; handle exception here - __except(xfilter(GetExceptionCode(), "swmm_step", ElapsedTime, StepCount)) + __except(xfilter(GetExceptionCode(), "swmm_step", ElapsedTime, TotalStepCount)) //(5.1.015) { ErrorCode = ERR_SYSTEM; } @@ -466,7 +470,7 @@ void execRouting() #endif { // --- determine when next routing time occurs - StepCount++; + TotalStepCount++; //(5.1.015) if ( !DoRouting ) routingStep = MIN(WetStep, ReportStep); else routingStep = routing_getRoutingStep(RouteModel, RouteStep); if ( routingStep <= 0.0 ) @@ -480,7 +484,7 @@ void execRouting() if ( nextRoutingTime > TotalDuration ) { routingStep = (TotalDuration - NewRoutingTime) / 1000.0; - routingStep = MAX(routingStep, 1./1000.0); + routingStep = MAX(routingStep, 1. / 1000.0); nextRoutingTime = TotalDuration; } @@ -504,7 +508,7 @@ void execRouting() #ifdef EXH // --- end of try loop; handle exception here __except(xfilter(GetExceptionCode(), "execRouting", - ElapsedTime, StepCount)) + ElapsedTime, TotalStepCount)) //(5.1.015) { ErrorCode = ERR_SYSTEM; return; diff --git a/src/solver/text.h b/src/solver/text.h index 53cc0817a..929dedd0b 100644 --- a/src/solver/text.h +++ b/src/solver/text.h @@ -15,6 +15,9 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 03/01/20 (Build 5.1.014) +// 04/01/20 (Build 5.1.015) +// // Author: L. Rossman // // Text strings @@ -29,9 +32,9 @@ #define FMT06 "\n o Retrieving project data" #define FMT07 "\n o Writing output report" #define FMT08 \ - "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build 5.1.014)" //(5.1.014) + "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build 5.1.015)" //(5.1.015) #define FMT09 \ - "\n --------------------------------------------------------------" + "\n ---------------------------------------------------------------------" #define FMT10 "\n" #define FMT11 "\n Cannot use duplicate file names." #define FMT12 "\n Cannot open input file " From 917771e519dbceebe0e26d4a4d29cfa567050e2c Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 18 May 2020 17:09:26 -0400 Subject: [PATCH 044/266] Create ccpp.yml --- .github/workflows/ccpp.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/ccpp.yml diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml new file mode 100644 index 000000000..ed0ae0c6b --- /dev/null +++ b/.github/workflows/ccpp.yml @@ -0,0 +1,20 @@ +name: C/C++ CI + +on: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] + +jobs: + win_build: + runs-on: windows-latest + defaults: + run: + shell: cmd + working-directory: tools + + steps: + - uses: actions/checkout@v2 + - name: build + run: build.cmd From 700a6c62205840ea6436ed2d4d51f7d2e9ca7d91 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 18 May 2020 17:11:28 -0400 Subject: [PATCH 045/266] Update ccpp.yml --- .github/workflows/ccpp.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index ed0ae0c6b..685d3f74f 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -16,5 +16,5 @@ jobs: steps: - uses: actions/checkout@v2 - - name: build - run: build.cmd + - name: make + run: make.cmd From 01fa6c7dda9ab2b9c82d8dac841476d60fa952f2 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 18 May 2020 17:15:59 -0400 Subject: [PATCH 046/266] Update ccpp.yml Windows latest provisioned with VS 2019 --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 685d3f74f..f89b0aeb8 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -17,4 +17,4 @@ jobs: steps: - uses: actions/checkout@v2 - name: make - run: make.cmd + run: make.cmd /g "Visual Studio 16 2019" From f34398ca218f8e5af26b9afb74a7e1ede6dc2145 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 18 May 2020 17:20:06 -0400 Subject: [PATCH 047/266] Update ccpp.yml Adds before-test --- .github/workflows/ccpp.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index f89b0aeb8..2b529e3e2 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -18,3 +18,5 @@ jobs: - uses: actions/checkout@v2 - name: make run: make.cmd /g "Visual Studio 16 2019" + - name: before-test + run: before-test.cmd From 6f15d496e8bc2a1ccc5b74c2266aac1bb18ea653 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 18 May 2020 17:22:43 -0400 Subject: [PATCH 048/266] Update ccpp.yml --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 2b529e3e2..c1bb7ec78 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -19,4 +19,4 @@ jobs: - name: make run: make.cmd /g "Visual Studio 16 2019" - name: before-test - run: before-test.cmd + run: before-nrtest.cmd From de419c6e3d13d855461cacc01e7d773d95aa3228 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 18 May 2020 17:35:41 -0400 Subject: [PATCH 049/266] Update ccpp.yml Adds env variable PROJECT --- .github/workflows/ccpp.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index c1bb7ec78..431ebd131 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -12,7 +12,9 @@ jobs: defaults: run: shell: cmd - working-directory: tools + working-directory: tools + env: + PROJECT: swmm steps: - uses: actions/checkout@v2 From bbfa99fa719e3e383e525975b8549f002a4ae831 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 18 May 2020 17:42:28 -0400 Subject: [PATCH 050/266] Update ccpp.yml Sets BUILD_HOME --- .github/workflows/ccpp.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 431ebd131..81b8d7005 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -19,6 +19,8 @@ jobs: steps: - uses: actions/checkout@v2 - name: make - run: make.cmd /g "Visual Studio 16 2019" + run: | + make.cmd /g "Visual Studio 16 2019" + ::set-env name=BUILD_HOME::%BUILD_HOME% - name: before-test run: before-nrtest.cmd From 5f45680363580b92c570c50d7135a4c334b9e43e Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 18 May 2020 17:53:14 -0400 Subject: [PATCH 051/266] Update ccpp.yml Sets PLATFORM --- .github/workflows/ccpp.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 81b8d7005..7eb4675cb 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -15,12 +15,13 @@ jobs: working-directory: tools env: PROJECT: swmm + BUILD_HOME: build + PLATFORM: win64 steps: - uses: actions/checkout@v2 - name: make run: | - make.cmd /g "Visual Studio 16 2019" - ::set-env name=BUILD_HOME::%BUILD_HOME% + make.cmd /g "Visual Studio 16 2019 Win64" - name: before-test run: before-nrtest.cmd From cbde9350b197a8deb73b63920d8d050715615f55 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 18 May 2020 17:55:39 -0400 Subject: [PATCH 052/266] Update ccpp.yml --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 7eb4675cb..b80f030da 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -22,6 +22,6 @@ jobs: - uses: actions/checkout@v2 - name: make run: | - make.cmd /g "Visual Studio 16 2019 Win64" + make.cmd /g "Visual Studio 16 2019" - name: before-test run: before-nrtest.cmd From cff77e3f410e567e037d250b755d8065782508c1 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 18 May 2020 17:56:28 -0400 Subject: [PATCH 053/266] Update ccpp.yml --- .github/workflows/ccpp.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index b80f030da..f84349ccc 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -21,7 +21,6 @@ jobs: steps: - uses: actions/checkout@v2 - name: make - run: | - make.cmd /g "Visual Studio 16 2019" + run: make.cmd /g "Visual Studio 16 2019" - name: before-test run: before-nrtest.cmd From 76d72d8a8d3fb1c83b3044e9f8364b1dbce16a06 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 18 May 2020 18:13:37 -0400 Subject: [PATCH 054/266] Update ccpp.yml Sets REF_BUILD_ID --- .github/workflows/ccpp.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index f84349ccc..347ed8622 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -17,10 +17,14 @@ jobs: PROJECT: swmm BUILD_HOME: build PLATFORM: win64 - + TEST_HOME: nrtests steps: - uses: actions/checkout@v2 - name: make run: make.cmd /g "Visual Studio 16 2019" - name: before-test - run: before-nrtest.cmd + run: | + before-nrtest.cmd + echo ::set-env name=REF_BUILD_ID::%REF_BUILD_ID% + - name: run-test + run: run-nrtests.cmd From 916b17cbcbbbb2e1567feabab07957d59b3d5228 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 18 May 2020 18:28:09 -0400 Subject: [PATCH 055/266] Update ccpp.yml --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 347ed8622..06230c25d 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -25,6 +25,6 @@ jobs: - name: before-test run: | before-nrtest.cmd - echo ::set-env name=REF_BUILD_ID::%REF_BUILD_ID% + echo %REF_BUILD_ID% - name: run-test run: run-nrtests.cmd From 24b67468b585c541c48bd6f2e78e306715850e37 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 09:43:34 -0400 Subject: [PATCH 056/266] Delete ccpp.yml Moving development of actions workflow for now --- .github/workflows/ccpp.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 .github/workflows/ccpp.yml diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml deleted file mode 100644 index 06230c25d..000000000 --- a/.github/workflows/ccpp.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: C/C++ CI - -on: - push: - branches: [ develop ] - pull_request: - branches: [ develop ] - -jobs: - win_build: - runs-on: windows-latest - defaults: - run: - shell: cmd - working-directory: tools - env: - PROJECT: swmm - BUILD_HOME: build - PLATFORM: win64 - TEST_HOME: nrtests - steps: - - uses: actions/checkout@v2 - - name: make - run: make.cmd /g "Visual Studio 16 2019" - - name: before-test - run: | - before-nrtest.cmd - echo %REF_BUILD_ID% - - name: run-test - run: run-nrtests.cmd From 102e314b75207c1d8ea776cfbb0605484973ed59 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 10:03:32 -0400 Subject: [PATCH 057/266] Create build-and-test.yml --- .github/workflows/build-and-test.yml | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/build-and-test.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 000000000..fd6435734 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,30 @@ +name: Build and Test + +on: + push: + branches: [ upstream-dev ] + pull_request: + branches: [ upstream-dev ] + +jobs: + win_build: + runs-on: windows-latest + defaults: + run: + shell: cmd + working-directory: tools + env: + PROJECT: swmm + BUILD_HOME: build + PLATFORM: win64 + TEST_HOME: nrtests + steps: + - uses: actions/checkout@v2 + - name: make + run: make.cmd /g "Visual Studio 16 2019" + - name: before-test + run: | + before-nrtest.cmd + echo %REF_BUILD_ID% + - name: run-test + run: run-nrtests.cmd From f9cedceb4e51f5c9c6c9d025d8b12a6d2644a305 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 10:38:34 -0400 Subject: [PATCH 058/266] Update before-nrtest.cmd Sets NRTEST_URL --- tools/before-nrtest.cmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/before-nrtest.cmd b/tools/before-nrtest.cmd index 3a336209b..6a7a36428 100644 --- a/tools/before-nrtest.cmd +++ b/tools/before-nrtest.cmd @@ -47,7 +47,7 @@ if %ERRORLEVEL% neq 0 ( echo "ERROR: 7zip not installed" & exit /B 1 ) :: set URL to github repo with test files -set "NRTESTS_URL=https://github.com/michaeltryby/%PROJECT%-nrtests" +set "NRTESTS_URL=https://github.com/SWMM-Project/%PROJECT%-nrtestsuite" :: if release tag isn't provided latest tag will be retrieved @@ -99,7 +99,7 @@ curl -fsSL -o benchmark.zip %BENCHFILES_URL% :: set up symlinks for tests directory -mklink /D .\tests .\%PROJECT%-nrtests-%RELEASE_TAG:~1%\public > nul +mklink /D .\tests .\%PROJECT%-nrtestsuite-%RELEASE_TAG:~1%\public > nul endlocal From 99ed3127d02d634b182321df7ccb7561479eaca8 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 10:54:27 -0400 Subject: [PATCH 059/266] Update build-and-test.yml Sets REF_BUILD_ID --- .github/workflows/build-and-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index fd6435734..527e5a2da 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -18,6 +18,7 @@ jobs: BUILD_HOME: build PLATFORM: win64 TEST_HOME: nrtests + REF_BUILD_ID: unknown steps: - uses: actions/checkout@v2 - name: make @@ -25,6 +26,6 @@ jobs: - name: before-test run: | before-nrtest.cmd - echo %REF_BUILD_ID% + echo "::set-env name=REF_BUILD_ID::%REF_BUILD_ID%" - name: run-test run: run-nrtests.cmd From 9e8db8e6fbfae9328acfedc5de4f57d9926d5cc7 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 11:17:12 -0400 Subject: [PATCH 060/266] Update build-and-test.yml Configures python --- .github/workflows/build-and-test.yml | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 527e5a2da..bbc1517e3 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -16,16 +16,26 @@ jobs: env: PROJECT: swmm BUILD_HOME: build - PLATFORM: win64 + PLATFORM: unknown TEST_HOME: nrtests REF_BUILD_ID: unknown + steps: - uses: actions/checkout@v2 - - name: make - run: make.cmd /g "Visual Studio 16 2019" - - name: before-test + - name: Setup python + uses: actions/setup-python@v2 + with: + python-version: '3.7' + - name: Install requirements + run: | + python -m pip install -f requirements-win.txt + - name: Make + run: | + make.cmd /g "Visual Studio 16 2019" + echo "::set-env name=PLATFORM::%PLATFORM%" + - name: Before test run: | before-nrtest.cmd echo "::set-env name=REF_BUILD_ID::%REF_BUILD_ID%" - - name: run-test + - name: Run test run: run-nrtests.cmd From e4549b94ae228385fedc24472bc68172d7f06bb9 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 11:20:56 -0400 Subject: [PATCH 061/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index bbc1517e3..4158806fa 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -28,7 +28,8 @@ jobs: python-version: '3.7' - name: Install requirements run: | - python -m pip install -f requirements-win.txt + python -m pip install --upgrade pip + python -m pip install -r requirements-win.txt - name: Make run: | make.cmd /g "Visual Studio 16 2019" From bb041b2d164d0d4a783d280920eca0283815e810 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 11:33:13 -0400 Subject: [PATCH 062/266] Update requirements-win.txt --- tools/requirements-win.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/requirements-win.txt b/tools/requirements-win.txt index 0f18bb0ee..1e645a196 100644 --- a/tools/requirements-win.txt +++ b/tools/requirements-win.txt @@ -15,8 +15,8 @@ nrtest --f https://github.com/michaeltryby/swmm-python/releases/download/v0.3.0-dev3/swmm.output-0.4.0.dev3-cp36-cp36m-win_amd64.whl -swmm.output +-f https://github.com/SWMM-Project/swmm-python/releases/download/v0.6.0-rc.1/swmm_toolkit-0.5.0-cp37-cp37m-win_amd64.whl +swmm-toolkit --f https://github.com/michaeltryby/swmm-python/releases/download/v0.3.0-dev3/nrtest_swmm-0.5.0-py3-none-any.whl +-f https://github.com/SWMM-Project/swmm-python/releases/download/v0.6.0-rc.1/nrtest_swmm-0.6.0-py3-none-any.whl nrtest-swmm From c1073bcd4a019a8ed853ff2bfd3a4eeee8e212dd Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 11:40:27 -0400 Subject: [PATCH 063/266] Update build-and-test.yml Sets REF_BUILD_ID --- .github/workflows/build-and-test.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 4158806fa..48a85464f 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -18,8 +18,6 @@ jobs: BUILD_HOME: build PLATFORM: unknown TEST_HOME: nrtests - REF_BUILD_ID: unknown - steps: - uses: actions/checkout@v2 - name: Setup python From 1eed6f25f807faf691d83cf973668564d43ff7a4 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 11:50:11 -0400 Subject: [PATCH 064/266] Update build-and-test.yml Sets env vars --- .github/workflows/build-and-test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 48a85464f..f62abc5a6 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -34,7 +34,10 @@ jobs: echo "::set-env name=PLATFORM::%PLATFORM%" - name: Before test run: | + echo INFO: Platform %PLATFORM% before-nrtest.cmd echo "::set-env name=REF_BUILD_ID::%REF_BUILD_ID%" - name: Run test - run: run-nrtests.cmd + run: | + echo INFO: REF_BUILD_ID %REF_BUILD_ID% + run-nrtests.cmd From fcfdb84bdb7c37a437d31361d4e25fa34113c81d Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 11:53:42 -0400 Subject: [PATCH 065/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index f62abc5a6..e8e5a4d66 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -16,7 +16,6 @@ jobs: env: PROJECT: swmm BUILD_HOME: build - PLATFORM: unknown TEST_HOME: nrtests steps: - uses: actions/checkout@v2 From d1404f403eadc3e9cbe96d5440914711ad778ce3 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 12:06:51 -0400 Subject: [PATCH 066/266] Update build-and-test.yml Quoting? --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index e8e5a4d66..0eaa94b68 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -30,12 +30,12 @@ jobs: - name: Make run: | make.cmd /g "Visual Studio 16 2019" - echo "::set-env name=PLATFORM::%PLATFORM%" + echo ::set-env name=PLATFORM::%PLATFORM% - name: Before test run: | echo INFO: Platform %PLATFORM% before-nrtest.cmd - echo "::set-env name=REF_BUILD_ID::%REF_BUILD_ID%" + echo ::set-env name=REF_BUILD_ID::%REF_BUILD_ID% - name: Run test run: | echo INFO: REF_BUILD_ID %REF_BUILD_ID% From 88193c805d182407c2a2d559314c8dbe6ea96aa4 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 12:14:13 -0400 Subject: [PATCH 067/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 0eaa94b68..7ed2c02f4 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -28,9 +28,7 @@ jobs: python -m pip install --upgrade pip python -m pip install -r requirements-win.txt - name: Make - run: | - make.cmd /g "Visual Studio 16 2019" - echo ::set-env name=PLATFORM::%PLATFORM% + run: make.cmd /g "Visual Studio 16 2019" && echo ::set-env name=PLATFORM::%PLATFORM% - name: Before test run: | echo INFO: Platform %PLATFORM% From 24bca49aaab8aa40b3b161118d6bd863e2c44c05 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 12:27:26 -0400 Subject: [PATCH 068/266] Work in progress Trys to set PLATFORM --- .github/workflows/build-and-test.yml | 2 +- tools/make.cmd | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 7ed2c02f4..c9b32b8b1 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -28,7 +28,7 @@ jobs: python -m pip install --upgrade pip python -m pip install -r requirements-win.txt - name: Make - run: make.cmd /g "Visual Studio 16 2019" && echo ::set-env name=PLATFORM::%PLATFORM% + run: make.cmd /g "Visual Studio 16 2019" - name: Before test run: | echo INFO: Platform %PLATFORM% diff --git a/tools/make.cmd b/tools/make.cmd index 3fabcc683..ad0659207 100644 --- a/tools/make.cmd +++ b/tools/make.cmd @@ -103,6 +103,10 @@ for /F "tokens=*" %%f in ( 'findstr CMAKE_SHARED_LINKER_FLAGS:STRING %BUILD_HOME ) if not defined PLATFORM ( echo "ERROR: PLATFORM could not be determined" & exit /B 1 ) +:: GitHub Actions +if exists %CI% ( + echo ::set-env name=PLATFORM::%PLATFORM% +) :: return to users current dir :: cd %CUR_DIR% From 71812320354c6fd62ab9fc15edc0065bc190652d Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 12:31:04 -0400 Subject: [PATCH 069/266] Update make.cmd --- tools/make.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/make.cmd b/tools/make.cmd index ad0659207..b4df31adb 100644 --- a/tools/make.cmd +++ b/tools/make.cmd @@ -104,7 +104,7 @@ for /F "tokens=*" %%f in ( 'findstr CMAKE_SHARED_LINKER_FLAGS:STRING %BUILD_HOME if not defined PLATFORM ( echo "ERROR: PLATFORM could not be determined" & exit /B 1 ) :: GitHub Actions -if exists %CI% ( +if exist %CI% ( echo ::set-env name=PLATFORM::%PLATFORM% ) From 43ae4c2caf8967fef752d2961afaba8086928410 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 12:36:12 -0400 Subject: [PATCH 070/266] Update make.cmd --- tools/make.cmd | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/make.cmd b/tools/make.cmd index b4df31adb..ee8aff0a1 100644 --- a/tools/make.cmd +++ b/tools/make.cmd @@ -104,9 +104,7 @@ for /F "tokens=*" %%f in ( 'findstr CMAKE_SHARED_LINKER_FLAGS:STRING %BUILD_HOME if not defined PLATFORM ( echo "ERROR: PLATFORM could not be determined" & exit /B 1 ) :: GitHub Actions -if exist %CI% ( - echo ::set-env name=PLATFORM::%PLATFORM% -) +echo ::set-env name=PLATFORM::%PLATFORM% :: return to users current dir :: cd %CUR_DIR% From 75275346ce5345ac866f4c534c5a5760762bd689 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 12:41:51 -0400 Subject: [PATCH 071/266] Work in progress Sets PLATFORM and REF_BUILD_ID --- .github/workflows/build-and-test.yml | 9 ++------- tools/before-nrtest.cmd | 3 +++ tools/make.cmd | 1 + 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index c9b32b8b1..438e59209 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -30,11 +30,6 @@ jobs: - name: Make run: make.cmd /g "Visual Studio 16 2019" - name: Before test - run: | - echo INFO: Platform %PLATFORM% - before-nrtest.cmd - echo ::set-env name=REF_BUILD_ID::%REF_BUILD_ID% + run: before-nrtest.cmd - name: Run test - run: | - echo INFO: REF_BUILD_ID %REF_BUILD_ID% - run-nrtests.cmd + run: run-nrtests.cmd diff --git a/tools/before-nrtest.cmd b/tools/before-nrtest.cmd index 6a7a36428..dd558fbde 100644 --- a/tools/before-nrtest.cmd +++ b/tools/before-nrtest.cmd @@ -111,6 +111,9 @@ for /F delims^=^"^ tokens^=4 %%d in ( 'findstr %PLATFORM% %TEST_HOME%\manifest.j ) if not defined REF_BUILD_ID ( echo "ERROR: REF_BUILD_ID could not be determined" & exit /B 1 ) +:: GitHub Actions +echo ::set-env name=REF_BUILD_ID::%REF_BUILD_ID% + :: return to users current directory cd %CUR_DIR% diff --git a/tools/make.cmd b/tools/make.cmd index ee8aff0a1..005b644b8 100644 --- a/tools/make.cmd +++ b/tools/make.cmd @@ -106,5 +106,6 @@ if not defined PLATFORM ( echo "ERROR: PLATFORM could not be determined" & exit :: GitHub Actions echo ::set-env name=PLATFORM::%PLATFORM% + :: return to users current dir :: cd %CUR_DIR% From e905bcd5504d03fcaf069d380e9f55fc0acaf31e Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 19 May 2020 15:25:58 -0400 Subject: [PATCH 072/266] Update build-and-test.yml Debugging package install --- .github/workflows/build-and-test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 438e59209..97cf780c5 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -32,4 +32,6 @@ jobs: - name: Before test run: before-nrtest.cmd - name: Run test - run: run-nrtests.cmd + run: | + echo INFO: Path %PATH% + echo INFO: Pythonpath %PYTHONPATH% From 08712de187844a4152c7d47ab3745c610be6f3d4 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 21 May 2020 12:23:57 -0400 Subject: [PATCH 073/266] Update requirements-win.txt Updates swmm-toolkit package fixing path issue --- tools/requirements-win.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/requirements-win.txt b/tools/requirements-win.txt index 1e645a196..032b2efdc 100644 --- a/tools/requirements-win.txt +++ b/tools/requirements-win.txt @@ -15,7 +15,7 @@ nrtest --f https://github.com/SWMM-Project/swmm-python/releases/download/v0.6.0-rc.1/swmm_toolkit-0.5.0-cp37-cp37m-win_amd64.whl +-f https://github.com/SWMM-Project/swmm-python/releases/download/v0.6.0-rc.2/swmm_toolkit-0.5.0-cp37-cp37m-win_amd64.whl swmm-toolkit -f https://github.com/SWMM-Project/swmm-python/releases/download/v0.6.0-rc.1/nrtest_swmm-0.6.0-py3-none-any.whl From 08f0357020e3bbaffc61a6b7f9af1a48831f9201 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 21 May 2020 12:27:42 -0400 Subject: [PATCH 074/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 97cf780c5..438e59209 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -32,6 +32,4 @@ jobs: - name: Before test run: before-nrtest.cmd - name: Run test - run: | - echo INFO: Path %PATH% - echo INFO: Pythonpath %PYTHONPATH% + run: run-nrtests.cmd From 9a484bb8b9d66253b47d0d6cf1fd76ce7502c79a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 21 May 2020 15:17:07 -0400 Subject: [PATCH 075/266] Configures boost as extern lib --- extern/boost.cmake | 31 +++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 18 ++++++++---------- tools/make.cmd | 3 +-- tools/requirements-win.txt | 4 ++-- 4 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 extern/boost.cmake diff --git a/extern/boost.cmake b/extern/boost.cmake new file mode 100644 index 000000000..04c515327 --- /dev/null +++ b/extern/boost.cmake @@ -0,0 +1,31 @@ +# +# CMakeLists.txt - CMake configuration file for swmm-solver/extern +# +# Created: March 16, 2020 +# Updated: May 21, 2020 +# +# Author: Michael E. Tryby +# US EPA - ORD/CESER +# + + +if(WIN32) + set(Boost_USE_STATIC_LIBS ON) +else() + set(Boost_USE_STATIC_LIBS OFF) + add_definitions(-DBOOST_ALL_DYN_LINK) +endif() + + +# Environment variable "BOOST_ROOT_X_XX_X" points to local install location +if (DEFINED ENV{BOOST_ROOT_1_72_0}) + set(BOOST_ROOT $ENV{BOOST_ROOT_1_72_0}) +endif() + + +find_package(Boost 1.67.0 + COMPONENTS + unit_test_framework + ) + +include_directories (${Boost_INCLUDE_DIRS}) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4e5a06dbf..6849149e9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,18 +1,16 @@ # -# CMakeLists.txt - CMake configuration file for epanet/tests +# CMakeLists.txt - CMake configuration file for swmm-solver/tests +# +# Created: Mar 4, 2020 +# Updated: May 21, 2020 +# +# Author: Michael E. Tryby +# US EPA ORD/CESER # #Prep ourselves for compiling with boost -if(WIN32) - set(Boost_USE_STATIC_LIBS ON) -else() - set(Boost_USE_STATIC_LIBS OFF) - add_definitions(-DBOOST_ALL_DYN_LINK) -endif() - -find_package(Boost COMPONENTS unit_test_framework system thread filesystem) -include_directories (${Boost_INCLUDE_DIRS}) +include(../extern/boost.cmake) add_subdirectory(outfile) diff --git a/tools/make.cmd b/tools/make.cmd index 005b644b8..01c3f583a 100644 --- a/tools/make.cmd +++ b/tools/make.cmd @@ -83,8 +83,7 @@ cd %BUILD_HOME% if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to cd %BUILD_HOME% dir" & exit /B 1 ) if %TESTING% EQU 1 ( - cmake -G"%GENERATOR%" -DBUILD_TESTS=ON -DBOOST_ROOT=C:\local\boost_1_67_0 ..^ - && cmake --build . --config Debug^ + cmake -G"%GENERATOR%" -DBUILD_TESTS=ON .. && cmake --build . --config Debug^ & echo. && ctest -C Debug --output-on-failure ) else ( cmake -G"%GENERATOR%" -DBUILD_TESTS=OFF ..^ diff --git a/tools/requirements-win.txt b/tools/requirements-win.txt index 032b2efdc..45d66b5b3 100644 --- a/tools/requirements-win.txt +++ b/tools/requirements-win.txt @@ -1,8 +1,8 @@ # # requirements-win.txt - Python requirements for running nrtest on Win32/Win64 # -# Date Created: 10/17/2019 -# Date Updated: 11/26/2019 +# Date Created: Oct 17, 2019 +# Date Updated: May 21, 2020 # # Author: Michael E. Tryby # US EPA ORD/CESER From 891d8bcfe233d3927fa9d6e32475c993a6bb2d1b Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 21 May 2020 15:24:34 -0400 Subject: [PATCH 076/266] Configures Actions to run unit tests --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 438e59209..8100b6ef8 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -28,7 +28,7 @@ jobs: python -m pip install --upgrade pip python -m pip install -r requirements-win.txt - name: Make - run: make.cmd /g "Visual Studio 16 2019" + run: make.cmd /t /g "Visual Studio 16 2019" - name: Before test run: before-nrtest.cmd - name: Run test From 3003e5b798480e253bcaad2692a7159c25008d2c Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 21 May 2020 17:10:40 -0400 Subject: [PATCH 077/266] Update make.cmd Adds build configuration as env variable --- tools/make.cmd | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/make.cmd b/tools/make.cmd index 01c3f583a..fc7862a7f 100644 --- a/tools/make.cmd +++ b/tools/make.cmd @@ -27,6 +27,8 @@ set "PROJECT=swmm" set "BUILD_HOME=build" set "PLATFORM=win32" +set "CONFIG=Release" + :: determine project directory set "CUR_DIR=%CD%" @@ -58,6 +60,7 @@ if NOT [%1]==[] ( ) if "%1"=="/t" ( set "TESTING=1" + set "CONFIG=Debug" ) shift goto :loop @@ -83,11 +86,12 @@ cd %BUILD_HOME% if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to cd %BUILD_HOME% dir" & exit /B 1 ) if %TESTING% EQU 1 ( - cmake -G"%GENERATOR%" -DBUILD_TESTS=ON .. && cmake --build . --config Debug^ - & echo. && ctest -C Debug --output-on-failure + cmake -G"%GENERATOR%" -DBUILD_TESTS=ON ..^ + && cmake --build . --config %CONFIG%^ + & echo. && ctest -C %CONFIG% --output-on-failure ) else ( cmake -G"%GENERATOR%" -DBUILD_TESTS=OFF ..^ - && cmake --build . --config Release --target install + && cmake --build . --config %CONFIG% --target install ) @@ -104,7 +108,8 @@ if not defined PLATFORM ( echo "ERROR: PLATFORM could not be determined" & exit :: GitHub Actions echo ::set-env name=PLATFORM::%PLATFORM% +echo ::set-env name=CONFIG::%CONFIG% :: return to users current dir -:: cd %CUR_DIR% +cd %CUR_DIR% From b4d146563dc54ca922bf3a0bb9e919ca2cae56ca Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 22 May 2020 17:29:34 -0400 Subject: [PATCH 078/266] Work in progress --- CMakeLists.txt | 5 +++-- tools/make.cmd | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bc9b5f36a..120b8b1a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ project(swmm-solver ) # Append local dir to module search path -list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +# list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) # Sets the position independent code property for all targets set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -72,7 +72,8 @@ install( FILE swmm-output-config.cmake ) - + + # Create install rules for vcruntime.dll, msvcp.dll, vcomp.dll etc. set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) include(InstallRequiredSystemLibraries) diff --git a/tools/make.cmd b/tools/make.cmd index fc7862a7f..30a11a00c 100644 --- a/tools/make.cmd +++ b/tools/make.cmd @@ -60,7 +60,6 @@ if NOT [%1]==[] ( ) if "%1"=="/t" ( set "TESTING=1" - set "CONFIG=Debug" ) shift goto :loop @@ -87,11 +86,11 @@ if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to cd %BUILD_HOME% dir" & exit /B 1 if %TESTING% EQU 1 ( cmake -G"%GENERATOR%" -DBUILD_TESTS=ON ..^ - && cmake --build . --config %CONFIG%^ - & echo. && ctest -C %CONFIG% --output-on-failure + && cmake --build . --config Debug^ + & echo. && ctest -C Debug --output-on-failure ) else ( cmake -G"%GENERATOR%" -DBUILD_TESTS=OFF ..^ - && cmake --build . --config %CONFIG% --target install + && cmake --build . --config Release --target install ) @@ -106,9 +105,9 @@ for /F "tokens=*" %%f in ( 'findstr CMAKE_SHARED_LINKER_FLAGS:STRING %BUILD_HOME ) if not defined PLATFORM ( echo "ERROR: PLATFORM could not be determined" & exit /B 1 ) + :: GitHub Actions echo ::set-env name=PLATFORM::%PLATFORM% -echo ::set-env name=CONFIG::%CONFIG% :: return to users current dir From 49e090c5f63e2e0cfd2b34625754463159e4a84a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 22 May 2020 17:48:48 -0400 Subject: [PATCH 079/266] Processing latest update --- src/solver/report.c | 4 ++-- src/solver/subcatch.c | 42 +++++++++++++++++++++++------------------- src/solver/text.h | 2 +- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/solver/report.c b/src/solver/report.c index 01e3cbe29..12d73fd7c 100644 --- a/src/solver/report.c +++ b/src/solver/report.c @@ -11,7 +11,7 @@ // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) // 03/01/20 (Build 5.1.014) -// 04/01/20 (Build 5.1.015) +// 05/18/20 (Build 5.1.015) // Author: L. Rossman (EPA) // // Report writing functions. @@ -1089,7 +1089,7 @@ void report_RouteStepFreq(TSysStats* sysStats) "\n Time Step Frequencies :"); for (i = 1; i < TIMELEVELS; i++) fprintf(Frpt.file, - "\n %5.2f - %5.2f sec : %7.2f %%", + "\n %6.3f - %6.3f sec : %7.2f %%", sysStats->timeStepIntervals[i-1], sysStats->timeStepIntervals[i], 100.0 * (double)(sysStats->timeStepCounts[i]) / totalSteps); } diff --git a/src/solver/subcatch.c b/src/solver/subcatch.c index e15a13797..e773137b5 100644 --- a/src/solver/subcatch.c +++ b/src/solver/subcatch.c @@ -11,7 +11,7 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) -// 04/01/20 (Build 5.1.015) +// 05/18/20 (Build 5.1.015) // Author: L. Rossman // // Subcatchment runoff functions. @@ -48,6 +48,7 @@ // // Build 5.1.015: // - Support added for multiple infiltration methods within a project. +// - Only pervious area depression storage receives monthly adjustment. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -1142,7 +1143,7 @@ void adjustSubareaParams(int i, int j) // Input: i = type of subarea being analyzed // j = index of current subcatchment being analyzed // Output adjusted values of module-level variables Dstore & Alpha -// Purpose: adjusts a subarea's depression storage and its pervious +// Purpose: adjusts a pervious subarea's depression storage and its //(5.1.015) // runoff coeff. by month of the year. // { @@ -1150,22 +1151,25 @@ void adjustSubareaParams(int i, int j) int m; // current month of the year double f; // adjustment factor - // --- depression storage adjustment - p = Subcatch[j].dStorePattern; - if (p >= 0 && Pattern[p].type == MONTHLY_PATTERN) - { - m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; - f = Pattern[p].factor[m]; - if (f >= 0.0) Dstore *= f; - } - - // --- pervious area roughness - p = Subcatch[j].nPervPattern; - if (i == PERV && p >= 0 && Pattern[p].type == MONTHLY_PATTERN) + if (i == PERV) //(5.1.015) { - m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; - f = Pattern[p].factor[m]; - if (f <= 0.0) Alpha = 0.0; - else Alpha /= f; - } + // --- depression storage adjustment + p = Subcatch[j].dStorePattern; + if (p >= 0 && Pattern[p].type == MONTHLY_PATTERN) + { + m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; + f = Pattern[p].factor[m]; + if (f >= 0.0) Dstore *= f; + } + + // --- roughness adjustment to runoff coeff. //(5.1.015) + p = Subcatch[j].nPervPattern; + if (p >= 0 && Pattern[p].type == MONTHLY_PATTERN) //(5.1.015) + { + m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; + f = Pattern[p].factor[m]; + if (f <= 0.0) Alpha = 0.0; + else Alpha /= f; + } + } } diff --git a/src/solver/text.h b/src/solver/text.h index 929dedd0b..00e274058 100644 --- a/src/solver/text.h +++ b/src/solver/text.h @@ -34,7 +34,7 @@ #define FMT08 \ "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build 5.1.015)" //(5.1.015) #define FMT09 \ - "\n ---------------------------------------------------------------------" + "\n --------------------------------------------------------------" #define FMT10 "\n" #define FMT11 "\n Cannot use duplicate file names." #define FMT12 "\n Cannot open input file " From ea362f49d96af01533888db175dfa6cf38f3097f Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 22 May 2020 18:23:47 -0400 Subject: [PATCH 080/266] Update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 8f2b3edd9..29525d250 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ + +.DS_Store + build*/ nrtest*/ From b1b67e962831c5b28e6b0046cd2a0975121d385e Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sun, 24 May 2020 21:36:06 -0400 Subject: [PATCH 081/266] Work in progress --- .github/workflows/build-and-test.yml | 10 ++++++---- extern/boost.cmake | 4 ++++ tools/make.cmd | 20 ++++++++------------ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 8100b6ef8..68bc87d78 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -27,9 +27,11 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -r requirements-win.txt - - name: Make - run: make.cmd /t /g "Visual Studio 16 2019" + - name: Build and unit test + run: make.cmd /g "Visual Studio 16 2019" /t - name: Before test run: before-nrtest.cmd - - name: Run test - run: run-nrtests.cmd + - name: Build and reg test + run: | + make.cmd /g "Visual Studio 16 2019" + run-nrtests.cmd diff --git a/extern/boost.cmake b/extern/boost.cmake index 04c515327..4f9c10aaa 100644 --- a/extern/boost.cmake +++ b/extern/boost.cmake @@ -20,6 +20,10 @@ endif() # Environment variable "BOOST_ROOT_X_XX_X" points to local install location if (DEFINED ENV{BOOST_ROOT_1_72_0}) set(BOOST_ROOT $ENV{BOOST_ROOT_1_72_0}) + +elseif(DEFINED ENV{BOOST_ROOT_1_67_0}) + set(BOOST_ROOT $ENV{BOOST_ROOT_1_67_0}) + endif() diff --git a/tools/make.cmd b/tools/make.cmd index 30a11a00c..93561bce9 100644 --- a/tools/make.cmd +++ b/tools/make.cmd @@ -71,26 +71,22 @@ if exist %BUILD_HOME% ( for /F "tokens=*" %%f in ( 'findstr CMAKE_GENERATOR:INTERNAL %BUILD_HOME%\CmakeCache.txt' ) do ( for /F "delims=:= tokens=3" %%m in ( 'echo %%f' ) do ( set CACHE_GEN=%%m - if not "!CACHE_GEN!" == "!GENERATOR!" ( rmdir /s /q %BUILD_HOME% & mkdir %BUILD_HOME% ) + if not "!CACHE_GEN!" == "!GENERATOR!" ( rmdir /s /q %BUILD_HOME% ) ) ) -) else ( - mkdir %BUILD_HOME%^ - & if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to make %BUILD_HOME% dir" & exit /B 1 ) ) - :: perform the build -cd %BUILD_HOME% -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to cd %BUILD_HOME% dir" & exit /B 1 ) +cmake -E make_directory %BUILD_HOME% + if %TESTING% EQU 1 ( - cmake -G"%GENERATOR%" -DBUILD_TESTS=ON ..^ - && cmake --build . --config Debug^ - & echo. && ctest -C Debug --output-on-failure + cmake -E chdir ./%BUILD_HOME% cmake -G"%GENERATOR%" -DBUILD_TESTS=ON ..^ + && cmake --build ./%BUILD_HOME% --config Debug^ + & echo. && cmake -E chdir ./%BUILD_HOME% ctest -C Debug --output-on-failure ) else ( - cmake -G"%GENERATOR%" -DBUILD_TESTS=OFF ..^ - && cmake --build . --config Release --target install + cmake -E chdir ./%BUILD_HOME% cmake -G"%GENERATOR%" -DBUILD_TESTS=OFF ..^ + && cmake --build ./%BUILD_HOME% --config Release --target package ) From 7566b7d2b9137d3bff73ac32903852162bc487ba Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sun, 24 May 2020 21:46:43 -0400 Subject: [PATCH 082/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 68bc87d78..3be77118e 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -34,4 +34,5 @@ jobs: - name: Build and reg test run: | make.cmd /g "Visual Studio 16 2019" + echo. run-nrtests.cmd From b3f42f3a70ef20ef2172c9802400baa7a455bb36 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sun, 24 May 2020 21:55:15 -0400 Subject: [PATCH 083/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 3be77118e..dd66c96cf 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -29,10 +29,9 @@ jobs: python -m pip install -r requirements-win.txt - name: Build and unit test run: make.cmd /g "Visual Studio 16 2019" /t + - name: Build + run: make.cmd /g "Visual Studio 16 2019" - name: Before test run: before-nrtest.cmd - - name: Build and reg test - run: | - make.cmd /g "Visual Studio 16 2019" - echo. - run-nrtests.cmd + - name: Reg test + run: run-nrtests.cmd From 476abe95bb6376a6ee4621653e341f03e96c357f Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 11:58:23 -0400 Subject: [PATCH 084/266] Work in progress Creates benchmark archive --- .github/workflows/build-and-test.yml | 3 +++ tools/run-nrtests.cmd | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index dd66c96cf..543ce3f57 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -35,3 +35,6 @@ jobs: run: before-nrtest.cmd - name: Reg test run: run-nrtests.cmd + - uses: edgarrc/action-7z@v1.0.4 + with: + args: 7z a benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip %TEST_HOME%\benchmark\swmm-%SUT_BUILD_ID% diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index fb02a11ae..b8948ed84 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -110,5 +110,9 @@ echo INFO: Comparing SUT artifacts to REF %REF_BUILD_ID% set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --rtol %RTOL_VALUE% --atol %ATOL_VALUE% -o benchmark\receipt.json %NRTEST_COMMAND% + +:: GitHub Actions +echo ::set-env name=SUT_BUILD_ID::%SUT_BUILD_ID% + :: Return user to their current dir cd %CUR_DIR% From e1f073cb5ddba137eaded9abb5307639724e33ac Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 12:12:13 -0400 Subject: [PATCH 085/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 543ce3f57..192116482 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -27,14 +27,13 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -r requirements-win.txt - - name: Build and unit test - run: make.cmd /g "Visual Studio 16 2019" /t +# - name: Build and unit test +# run: make.cmd /g "Visual Studio 16 2019" /t - name: Build run: make.cmd /g "Visual Studio 16 2019" - name: Before test run: before-nrtest.cmd - name: Reg test run: run-nrtests.cmd - - uses: edgarrc/action-7z@v1.0.4 - with: - args: 7z a benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip %TEST_HOME%\benchmark\swmm-%SUT_BUILD_ID% + - name: Create archive + run: 7z a benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip %TEST_HOME%\benchmark\swmm-%SUT_BUILD_ID% From 7296008247e9e2c60660d1bb3aad59685c4d6b9a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 12:23:21 -0400 Subject: [PATCH 086/266] Work in progress --- .github/workflows/build-and-test.yml | 2 +- tools/run-nrtests.cmd | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 192116482..d5ecefe62 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -36,4 +36,4 @@ jobs: - name: Reg test run: run-nrtests.cmd - name: Create archive - run: 7z a benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip %TEST_HOME%\benchmark\swmm-%SUT_BUILD_ID% + run: 7z a benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip .\%TEST_HOME%\benchmark\swmm-%SUT_BUILD_ID% diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index b8948ed84..b7ffbecf1 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -62,11 +62,11 @@ if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( :: recursively build test list -set TESTS= -for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( - set FULL_PATH=%%T - set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! -) +set "TESTS=examples" +:: for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( +:: set FULL_PATH=%%T +:: set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! +:: ) :: determine location of python Scripts folder From 757432ba4c3a4a447cdbdfeccae7a06f405e8450 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 12:39:30 -0400 Subject: [PATCH 087/266] Work in progress --- .github/workflows/build-and-test.yml | 2 +- tools/run-nrtests.cmd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index d5ecefe62..41b5b03d6 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -36,4 +36,4 @@ jobs: - name: Reg test run: run-nrtests.cmd - name: Create archive - run: 7z a benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip .\%TEST_HOME%\benchmark\swmm-%SUT_BUILD_ID% + run: 7z a benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip ..\%TEST_HOME%\benchmark\swmm-%SUT_BUILD_ID% diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index b7ffbecf1..b29ecde70 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -62,7 +62,7 @@ if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( :: recursively build test list -set "TESTS=examples" +set "TESTS=tests\examples" :: for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( :: set FULL_PATH=%%T :: set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! From b6ff4946088410fdd1dab2e1bbf850feac315ca5 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 12:57:36 -0400 Subject: [PATCH 088/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 41b5b03d6..41f50519a 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -36,4 +36,10 @@ jobs: - name: Reg test run: run-nrtests.cmd - name: Create archive - run: 7z a benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip ..\%TEST_HOME%\benchmark\swmm-%SUT_BUILD_ID% + run: | + cd ..\%TEST_HOME%\benchmark + 7z a benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip .\swmm-%SUT_BUILD_ID% + - uses: actions/upload-artifact@v2 + with: + name: Upload benchmark + path: ..\%TEST_HOME%\benchmark\benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip From 24d84c46ec06d33841be4296879ba410b4de6d04 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 13:03:51 -0400 Subject: [PATCH 089/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 41f50519a..a2a7683ff 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -42,4 +42,4 @@ jobs: - uses: actions/upload-artifact@v2 with: name: Upload benchmark - path: ..\%TEST_HOME%\benchmark\benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip + path: %CD%\..\%TEST_HOME%\benchmark\benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip From f48d92987f5ddf2cc338f78ddf522a3f3c6bf64a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 13:08:11 -0400 Subject: [PATCH 090/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a2a7683ff..8e0f245c9 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -41,5 +41,5 @@ jobs: 7z a benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip .\swmm-%SUT_BUILD_ID% - uses: actions/upload-artifact@v2 with: - name: Upload benchmark - path: %CD%\..\%TEST_HOME%\benchmark\benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip + name: benchmark + path: %HOME%\..\%TEST_HOME%\benchmark\benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip From 8ef54687f71e6b4b8c54514d1cd16834dfcd89f6 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 13:09:42 -0400 Subject: [PATCH 091/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 8e0f245c9..d2380ad8e 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -42,4 +42,4 @@ jobs: - uses: actions/upload-artifact@v2 with: name: benchmark - path: %HOME%\..\%TEST_HOME%\benchmark\benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip + path: %HOME%\%TEST_HOME%\benchmark\benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip From 14667de9f1cbc5aa4c10185b5bc480ef7a537a58 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 13:26:41 -0400 Subject: [PATCH 092/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index d2380ad8e..659bd02b0 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -42,4 +42,4 @@ jobs: - uses: actions/upload-artifact@v2 with: name: benchmark - path: %HOME%\%TEST_HOME%\benchmark\benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip + path: *\benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip From f330915ea7eb7ad8307a5d549332fc0c687974ca Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 13:30:02 -0400 Subject: [PATCH 093/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 659bd02b0..358f4aa22 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -42,4 +42,4 @@ jobs: - uses: actions/upload-artifact@v2 with: name: benchmark - path: *\benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip + path: benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip From 48e4fe288bec2800a778593a4b5a14dad5ada54a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 13:33:40 -0400 Subject: [PATCH 094/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 358f4aa22..3a9ad7b7b 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -39,6 +39,7 @@ jobs: run: | cd ..\%TEST_HOME%\benchmark 7z a benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip .\swmm-%SUT_BUILD_ID% + echo ::set-env name=ARCHIVE_PATH::%CD% - uses: actions/upload-artifact@v2 with: name: benchmark From bcee952b70d9a3c525980d2fa08bd9556133a87d Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 13:36:51 -0400 Subject: [PATCH 095/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 3a9ad7b7b..a011c7e54 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -43,4 +43,4 @@ jobs: - uses: actions/upload-artifact@v2 with: name: benchmark - path: benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip + path: %ARCHIVE_PATH%\benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip From cefdb09693435be026f0bf13f68ee7cf8f65d0b3 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 13:49:39 -0400 Subject: [PATCH 096/266] Rolling back --- .github/workflows/build-and-test.yml | 9 --------- tools/run-nrtests.cmd | 10 +++++----- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a011c7e54..8ab641c5c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -35,12 +35,3 @@ jobs: run: before-nrtest.cmd - name: Reg test run: run-nrtests.cmd - - name: Create archive - run: | - cd ..\%TEST_HOME%\benchmark - 7z a benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip .\swmm-%SUT_BUILD_ID% - echo ::set-env name=ARCHIVE_PATH::%CD% - - uses: actions/upload-artifact@v2 - with: - name: benchmark - path: %ARCHIVE_PATH%\benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index b29ecde70..3e62705bd 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -62,11 +62,11 @@ if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( :: recursively build test list -set "TESTS=tests\examples" -:: for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( -:: set FULL_PATH=%%T -:: set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! -:: ) +set "TESTS=" + for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( + set FULL_PATH=%%T + set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! +) :: determine location of python Scripts folder From 0ff00297d366c287deb3f076b85b41ad6ee1fc30 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 13:54:15 -0400 Subject: [PATCH 097/266] Update build-and-test.yml Switching to Lew develop --- .github/workflows/build-and-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 8ab641c5c..afb4eee11 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,9 +2,9 @@ name: Build and Test on: push: - branches: [ upstream-dev ] + branches: [ lew-develop ] pull_request: - branches: [ upstream-dev ] + branches: [ lew-develop ] jobs: win_build: @@ -27,8 +27,8 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -r requirements-win.txt -# - name: Build and unit test -# run: make.cmd /g "Visual Studio 16 2019" /t + - name: Build and unit test + run: make.cmd /g "Visual Studio 16 2019" /t - name: Build run: make.cmd /g "Visual Studio 16 2019" - name: Before test From 9192d649ec97e5545de0ae48f1acfeb5329f9b38 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 14:04:08 -0400 Subject: [PATCH 098/266] Create build-and-test.yml Configures for Visual Studio 15 2017 --- .github/workflows/build-and-test.yml | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/build-and-test.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 000000000..cc2230d04 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,37 @@ +name: Build and Test + +on: + push: + branches: [ lew-develop ] + pull_request: + branches: [ lew-develop ] + +jobs: + win_build: + runs-on: windows-2016 + defaults: + run: + shell: cmd + working-directory: tools + env: + PROJECT: swmm + BUILD_HOME: build + TEST_HOME: nrtests + steps: + - uses: actions/checkout@v2 + - name: Setup python + uses: actions/setup-python@v2 + with: + python-version: '3.7' + - name: Install requirements + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements-win.txt + - name: Build and unit test + run: make.cmd /t + - name: Build + run: make.cmd + - name: Before test + run: before-nrtest.cmd + - name: Reg test + run: run-nrtests.cmd From 0f46c1a312861f8fd6f27f68e0d899de4b54c8ac Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 14:07:29 -0400 Subject: [PATCH 099/266] Update requirements-win.txt --- tools/requirements-win.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/requirements-win.txt b/tools/requirements-win.txt index 0f18bb0ee..45d66b5b3 100644 --- a/tools/requirements-win.txt +++ b/tools/requirements-win.txt @@ -1,8 +1,8 @@ # # requirements-win.txt - Python requirements for running nrtest on Win32/Win64 # -# Date Created: 10/17/2019 -# Date Updated: 11/26/2019 +# Date Created: Oct 17, 2019 +# Date Updated: May 21, 2020 # # Author: Michael E. Tryby # US EPA ORD/CESER @@ -15,8 +15,8 @@ nrtest --f https://github.com/michaeltryby/swmm-python/releases/download/v0.3.0-dev3/swmm.output-0.4.0.dev3-cp36-cp36m-win_amd64.whl -swmm.output +-f https://github.com/SWMM-Project/swmm-python/releases/download/v0.6.0-rc.2/swmm_toolkit-0.5.0-cp37-cp37m-win_amd64.whl +swmm-toolkit --f https://github.com/michaeltryby/swmm-python/releases/download/v0.3.0-dev3/nrtest_swmm-0.5.0-py3-none-any.whl +-f https://github.com/SWMM-Project/swmm-python/releases/download/v0.6.0-rc.1/nrtest_swmm-0.6.0-py3-none-any.whl nrtest-swmm From 0c064ab2c705ba3291b4f5b7d40a6e5d2775ed60 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 14:11:41 -0400 Subject: [PATCH 100/266] Work in progress Updates scripts for github actions --- tools/before-nrtest.cmd | 3 +++ tools/make.cmd | 28 +++++++++++++++------------- tools/run-nrtests.cmd | 10 +++++++--- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/tools/before-nrtest.cmd b/tools/before-nrtest.cmd index 6a7a36428..dd558fbde 100644 --- a/tools/before-nrtest.cmd +++ b/tools/before-nrtest.cmd @@ -111,6 +111,9 @@ for /F delims^=^"^ tokens^=4 %%d in ( 'findstr %PLATFORM% %TEST_HOME%\manifest.j ) if not defined REF_BUILD_ID ( echo "ERROR: REF_BUILD_ID could not be determined" & exit /B 1 ) +:: GitHub Actions +echo ::set-env name=REF_BUILD_ID::%REF_BUILD_ID% + :: return to users current directory cd %CUR_DIR% diff --git a/tools/make.cmd b/tools/make.cmd index 3fabcc683..93561bce9 100644 --- a/tools/make.cmd +++ b/tools/make.cmd @@ -27,6 +27,8 @@ set "PROJECT=swmm" set "BUILD_HOME=build" set "PLATFORM=win32" +set "CONFIG=Release" + :: determine project directory set "CUR_DIR=%CD%" @@ -69,26 +71,22 @@ if exist %BUILD_HOME% ( for /F "tokens=*" %%f in ( 'findstr CMAKE_GENERATOR:INTERNAL %BUILD_HOME%\CmakeCache.txt' ) do ( for /F "delims=:= tokens=3" %%m in ( 'echo %%f' ) do ( set CACHE_GEN=%%m - if not "!CACHE_GEN!" == "!GENERATOR!" ( rmdir /s /q %BUILD_HOME% & mkdir %BUILD_HOME% ) + if not "!CACHE_GEN!" == "!GENERATOR!" ( rmdir /s /q %BUILD_HOME% ) ) ) -) else ( - mkdir %BUILD_HOME%^ - & if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to make %BUILD_HOME% dir" & exit /B 1 ) ) - :: perform the build -cd %BUILD_HOME% -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to cd %BUILD_HOME% dir" & exit /B 1 ) +cmake -E make_directory %BUILD_HOME% + if %TESTING% EQU 1 ( - cmake -G"%GENERATOR%" -DBUILD_TESTS=ON -DBOOST_ROOT=C:\local\boost_1_67_0 ..^ - && cmake --build . --config Debug^ - & echo. && ctest -C Debug --output-on-failure + cmake -E chdir ./%BUILD_HOME% cmake -G"%GENERATOR%" -DBUILD_TESTS=ON ..^ + && cmake --build ./%BUILD_HOME% --config Debug^ + & echo. && cmake -E chdir ./%BUILD_HOME% ctest -C Debug --output-on-failure ) else ( - cmake -G"%GENERATOR%" -DBUILD_TESTS=OFF ..^ - && cmake --build . --config Release --target install + cmake -E chdir ./%BUILD_HOME% cmake -G"%GENERATOR%" -DBUILD_TESTS=OFF ..^ + && cmake --build ./%BUILD_HOME% --config Release --target package ) @@ -104,5 +102,9 @@ for /F "tokens=*" %%f in ( 'findstr CMAKE_SHARED_LINKER_FLAGS:STRING %BUILD_HOME if not defined PLATFORM ( echo "ERROR: PLATFORM could not be determined" & exit /B 1 ) +:: GitHub Actions +echo ::set-env name=PLATFORM::%PLATFORM% + + :: return to users current dir -:: cd %CUR_DIR% +cd %CUR_DIR% diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index c51d96c80..3e62705bd 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -62,8 +62,8 @@ if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( :: recursively build test list -set TESTS= -for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( +set "TESTS=" + for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( set FULL_PATH=%%T set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! ) @@ -87,7 +87,7 @@ set TEST_OUTPUT_PATH=benchmark\%PROJECT%-%SUT_BUILD_ID% set NRTEST_COMPARE_CMD=python.exe %NRTEST_SCRIPT_PATH%\nrtest compare set REF_OUTPUT_PATH=benchmark\%PROJECT%-%REF_BUILD_ID% set RTOL_VALUE=0.01 -set ATOL_VALUE=1.E-6 +set ATOL_VALUE=0.0 :: change current directory to test suite ::cd %TEST_HOME% @@ -110,5 +110,9 @@ echo INFO: Comparing SUT artifacts to REF %REF_BUILD_ID% set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --rtol %RTOL_VALUE% --atol %ATOL_VALUE% -o benchmark\receipt.json %NRTEST_COMMAND% + +:: GitHub Actions +echo ::set-env name=SUT_BUILD_ID::%SUT_BUILD_ID% + :: Return user to their current dir cd %CUR_DIR% From 7556e8933e05327453a051e3bbf156845c0d1f34 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 14:19:24 -0400 Subject: [PATCH 101/266] Work in progress Configures unit test build --- extern/boost.cmake | 35 +++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 18 ++++++++---------- 2 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 extern/boost.cmake diff --git a/extern/boost.cmake b/extern/boost.cmake new file mode 100644 index 000000000..4f9c10aaa --- /dev/null +++ b/extern/boost.cmake @@ -0,0 +1,35 @@ +# +# CMakeLists.txt - CMake configuration file for swmm-solver/extern +# +# Created: March 16, 2020 +# Updated: May 21, 2020 +# +# Author: Michael E. Tryby +# US EPA - ORD/CESER +# + + +if(WIN32) + set(Boost_USE_STATIC_LIBS ON) +else() + set(Boost_USE_STATIC_LIBS OFF) + add_definitions(-DBOOST_ALL_DYN_LINK) +endif() + + +# Environment variable "BOOST_ROOT_X_XX_X" points to local install location +if (DEFINED ENV{BOOST_ROOT_1_72_0}) + set(BOOST_ROOT $ENV{BOOST_ROOT_1_72_0}) + +elseif(DEFINED ENV{BOOST_ROOT_1_67_0}) + set(BOOST_ROOT $ENV{BOOST_ROOT_1_67_0}) + +endif() + + +find_package(Boost 1.67.0 + COMPONENTS + unit_test_framework + ) + +include_directories (${Boost_INCLUDE_DIRS}) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4e5a06dbf..6849149e9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,18 +1,16 @@ # -# CMakeLists.txt - CMake configuration file for epanet/tests +# CMakeLists.txt - CMake configuration file for swmm-solver/tests +# +# Created: Mar 4, 2020 +# Updated: May 21, 2020 +# +# Author: Michael E. Tryby +# US EPA ORD/CESER # #Prep ourselves for compiling with boost -if(WIN32) - set(Boost_USE_STATIC_LIBS ON) -else() - set(Boost_USE_STATIC_LIBS OFF) - add_definitions(-DBOOST_ALL_DYN_LINK) -endif() - -find_package(Boost COMPONENTS unit_test_framework system thread filesystem) -include_directories (${Boost_INCLUDE_DIRS}) +include(../extern/boost.cmake) add_subdirectory(outfile) From 053b0ff8a002c002a90b4050c3beb9aa763bf679 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 14:27:47 -0400 Subject: [PATCH 102/266] Update build-and-test.yml Configures test metadata --- .github/workflows/build-and-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index cc2230d04..07ed7b5d2 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -17,6 +17,7 @@ jobs: PROJECT: swmm BUILD_HOME: build TEST_HOME: nrtests + SUT_BUILD_ID: %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% steps: - uses: actions/checkout@v2 - name: Setup python @@ -34,4 +35,4 @@ jobs: - name: Before test run: before-nrtest.cmd - name: Reg test - run: run-nrtests.cmd + run: run-nrtests.cmd %GITHUB_SHA% %SUT_BUILD_ID% From 2ea1e53320c6334e51e96e08c24dd193867a316c Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 14:29:19 -0400 Subject: [PATCH 103/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 07ed7b5d2..424d24823 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -17,7 +17,6 @@ jobs: PROJECT: swmm BUILD_HOME: build TEST_HOME: nrtests - SUT_BUILD_ID: %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% steps: - uses: actions/checkout@v2 - name: Setup python @@ -35,4 +34,4 @@ jobs: - name: Before test run: before-nrtest.cmd - name: Reg test - run: run-nrtests.cmd %GITHUB_SHA% %SUT_BUILD_ID% + run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% From 484ae73df220c8d2e9595e2cb8cccd62015f7706 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 27 May 2020 14:38:58 -0400 Subject: [PATCH 104/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 424d24823..d55d1099d 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -32,6 +32,8 @@ jobs: - name: Build run: make.cmd - name: Before test - run: before-nrtest.cmd + run: | + before-nrtest.cmd + dir ..\nrtests\benchmark - name: Reg test run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% From 88c0e58df24a625f645d8e61d95cd1f09ac5454f Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 10:52:51 -0400 Subject: [PATCH 105/266] Update build-and-test.yml Tests override of default shell setting --- .github/workflows/build-and-test.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index afb4eee11..8e3d12c1f 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,13 +2,13 @@ name: Build and Test on: push: - branches: [ lew-develop ] + branches: [ upstream-dev ] pull_request: - branches: [ lew-develop ] + branches: [ upstream-dev ] jobs: win_build: - runs-on: windows-latest + runs-on: windows-2016 defaults: run: shell: cmd @@ -27,11 +27,15 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -r requirements-win.txt - - name: Build and unit test - run: make.cmd /g "Visual Studio 16 2019" /t +# - name: Build and unit test +# run: make.cmd /t - name: Build - run: make.cmd /g "Visual Studio 16 2019" + run: make.cmd - name: Before test run: before-nrtest.cmd - name: Reg test run: run-nrtests.cmd + - name: Shell test + run: echo $PATH + shell: bash + From 6e10b3926bf249670030d0b255e35a815c2bc614 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 12:48:38 -0400 Subject: [PATCH 106/266] Update build-and-test.yml Adds version and build_id --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 8e3d12c1f..df8b318f2 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -34,7 +34,7 @@ jobs: - name: Before test run: before-nrtest.cmd - name: Reg test - run: run-nrtests.cmd + run: run-nrtests.cmd %GITHUB_SHA% %GUTHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Shell test run: echo $PATH shell: bash From b45396c7f70c2d784f5ad00b468902675152001e Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 12:55:22 -0400 Subject: [PATCH 107/266] Update build-and-test.yml Fixes Typo --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index df8b318f2..417de30f8 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -34,7 +34,7 @@ jobs: - name: Before test run: before-nrtest.cmd - name: Reg test - run: run-nrtests.cmd %GITHUB_SHA% %GUTHUB_RUN_ID%_%GITHUB_RUN_NUMBER% + run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Shell test run: echo $PATH shell: bash From a00cf5e0a3bd3ea74d427d84d521e2969f4be0f5 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 13:11:43 -0400 Subject: [PATCH 108/266] Update build-and-test.yml Adds artifact upload --- .github/workflows/build-and-test.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 417de30f8..e304e1d8c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -35,7 +35,10 @@ jobs: run: before-nrtest.cmd - name: Reg test run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - - name: Shell test - run: echo $PATH + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: receipt + path: $HOME/$BUILD_HOME/benchmark/receipt.json shell: bash From 18a2efae64a5031484515dcb54f1ebfb9e1bb109 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 13:13:53 -0400 Subject: [PATCH 109/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index e304e1d8c..6b8a684fd 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -36,9 +36,10 @@ jobs: - name: Reg test run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Upload artifacts + shell: bash uses: actions/upload-artifact@v2 with: name: receipt path: $HOME/$BUILD_HOME/benchmark/receipt.json - shell: bash + From 60404e67215f941af6d6ac73dfcf20eb81f36bc7 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 13:22:12 -0400 Subject: [PATCH 110/266] Update build-and-test.yml Fixes bug --- .github/workflows/build-and-test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 6b8a684fd..78d798df7 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -36,7 +36,6 @@ jobs: - name: Reg test run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Upload artifacts - shell: bash uses: actions/upload-artifact@v2 with: name: receipt From aed1b3fca0cd6d6844c694449bc42b4c6cc70f82 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 13:29:04 -0400 Subject: [PATCH 111/266] Update build-and-test.yml Fixes bug --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 78d798df7..352acaf68 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -39,6 +39,6 @@ jobs: uses: actions/upload-artifact@v2 with: name: receipt - path: $HOME/$BUILD_HOME/benchmark/receipt.json + path: $HOME/$TEST_HOME/benchmark/receipt.json From 080c74289c288c9cc3eea1c8ca7a874d5fd18d52 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 13:35:16 -0400 Subject: [PATCH 112/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 352acaf68..a2fae0536 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -36,6 +36,8 @@ jobs: - name: Reg test run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Upload artifacts + run: echo $HOME + shell: bash uses: actions/upload-artifact@v2 with: name: receipt From 79558d47b9a7878a635e38fb1347b20f129b8fcf Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 13:36:24 -0400 Subject: [PATCH 113/266] Update build-and-test.yml Debugging --- .github/workflows/build-and-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a2fae0536..783a33381 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -38,9 +38,9 @@ jobs: - name: Upload artifacts run: echo $HOME shell: bash - uses: actions/upload-artifact@v2 - with: - name: receipt - path: $HOME/$TEST_HOME/benchmark/receipt.json +# uses: actions/upload-artifact@v2 +# with: +# name: receipt +# path: $HOME/$TEST_HOME/benchmark/receipt.json From d5ee99c94b761e3a293bd71bac705c7baa029569 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 13:44:29 -0400 Subject: [PATCH 114/266] Update build-and-test.yml Fixes path issue --- .github/workflows/build-and-test.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 783a33381..06694f944 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -36,11 +36,7 @@ jobs: - name: Reg test run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Upload artifacts - run: echo $HOME - shell: bash -# uses: actions/upload-artifact@v2 -# with: -# name: receipt -# path: $HOME/$TEST_HOME/benchmark/receipt.json - - + uses: actions/upload-artifact@v2 + with: + name: receipt + path: $GITHUB_WORKSPACE/$TEST_HOME/benchmark/receipt.json From ea4f6f96ab1470048a266aa4e58ea5375b59fd07 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 13:53:45 -0400 Subject: [PATCH 115/266] Update build-and-test.yml Debugging --- .github/workflows/build-and-test.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 06694f944..173d16281 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -34,9 +34,13 @@ jobs: - name: Before test run: before-nrtest.cmd - name: Reg test - run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% + run: | + run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% + echo %CD% - name: Upload artifacts - uses: actions/upload-artifact@v2 - with: - name: receipt - path: $GITHUB_WORKSPACE/$TEST_HOME/benchmark/receipt.json + run: echo $GITHUB_WORKSPACE + shell: bash +# uses: actions/upload-artifact@v2 +# with: +# name: receipt +# path: $GITHUB_WORKSPACE/$TEST_HOME/benchmark/receipt.json From 9f71dadf4769c537ffa2b8bd4d8c315edc8e1505 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 14:01:26 -0400 Subject: [PATCH 116/266] Update build-and-test.yml Debugging --- .github/workflows/build-and-test.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 173d16281..ac509345c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -34,9 +34,8 @@ jobs: - name: Before test run: before-nrtest.cmd - name: Reg test - run: | - run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - echo %CD% + run: echo %CD% +# run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Upload artifacts run: echo $GITHUB_WORKSPACE shell: bash From 32368123862dd1473455e4189815a6d28c739c31 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 14:38:34 -0400 Subject: [PATCH 117/266] Work in progress Adds upload --- tools/run-nrtests.cmd | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index 3e62705bd..3b2f2c94d 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -62,7 +62,7 @@ if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( :: recursively build test list -set "TESTS=" +set TESTS= for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( set FULL_PATH=%%T set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! @@ -111,8 +111,14 @@ set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --r %NRTEST_COMMAND% +:: create zip archive or SUT benchmarks +cd .\benchmark +7z a %PROJECT_DIR\upload\benchmark-%PLATFORM%.zip .\%PROJECT%-%SUT_BUILD_ID% + + :: GitHub Actions echo ::set-env name=SUT_BUILD_ID::%SUT_BUILD_ID% + :: Return user to their current dir cd %CUR_DIR% From 4cc914a3731ee72813621dbbadb70852582b1241 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 14:48:30 -0400 Subject: [PATCH 118/266] Update run-nrtests.cmd --- tools/run-nrtests.cmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index 3b2f2c94d..b5251c15e 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -111,9 +111,9 @@ set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --r %NRTEST_COMMAND% -:: create zip archive or SUT benchmarks +:: create SUT benchmark archive cd .\benchmark -7z a %PROJECT_DIR\upload\benchmark-%PLATFORM%.zip .\%PROJECT%-%SUT_BUILD_ID% +7z a benchmark-%PLATFORM%.zip .\%PROJECT%-%SUT_BUILD_ID% :: GitHub Actions From 9818d7cfbdea5f46f1432f3ae7d2af45858f1c35 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 15:04:39 -0400 Subject: [PATCH 119/266] Update run-nrtests.cmd --- tools/run-nrtests.cmd | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index b5251c15e..73cdafcde 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -39,7 +39,7 @@ set "CUR_DIR=%CD%" set "SCRIPT_HOME=%~dp0" cd %SCRIPT_HOME% pushd .. -set PROJ_DIR=%CD% +set "PROJ_DIR=%CD%" popd @@ -116,6 +116,11 @@ cd .\benchmark 7z a benchmark-%PLATFORM%.zip .\%PROJECT%-%SUT_BUILD_ID% +:: stage artifacts for upload +mkdir %PROJECT_DIR%\upload +move /Y receipt.json %PROJECT_DIR%\upload\receipt.json + + :: GitHub Actions echo ::set-env name=SUT_BUILD_ID::%SUT_BUILD_ID% From cc6c3c461c99f7d83a777d4c7807e8af8d98da57 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 15:09:55 -0400 Subject: [PATCH 120/266] Update run-nrtests.cmd --- tools/run-nrtests.cmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index 73cdafcde..f5075f4b5 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -117,8 +117,8 @@ cd .\benchmark :: stage artifacts for upload -mkdir %PROJECT_DIR%\upload -move /Y receipt.json %PROJECT_DIR%\upload\receipt.json +mkdir %PROJ_DIR%\upload +move /Y receipt.json %PROJ_DIR%\upload\receipt.json :: GitHub Actions From e316472be03dd0c0b30d1ada3aca878a99e71948 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 15:14:08 -0400 Subject: [PATCH 121/266] Update run-nrtests.cmd --- tools/run-nrtests.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index f5075f4b5..f069c3cfa 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -119,7 +119,7 @@ cd .\benchmark :: stage artifacts for upload mkdir %PROJ_DIR%\upload move /Y receipt.json %PROJ_DIR%\upload\receipt.json - +move /Y benchmark-%PLATFORM%.zip %PROJ_DIR%\upload\benchmark-%PLATFORM%.zip :: GitHub Actions echo ::set-env name=SUT_BUILD_ID::%SUT_BUILD_ID% From 0b2b8a57f1b56e76971b8dbafe0ec7ca0ee3f65b Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 15:24:22 -0400 Subject: [PATCH 122/266] Update run-nrtests.cmd --- tools/run-nrtests.cmd | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index f069c3cfa..6136b44bc 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -26,6 +26,11 @@ setlocal EnableDelayedExpansion +:: check that dependencies are installed +where 7z > nul +if %ERRORLEVEL% neq 0 ( echo "ERROR: 7zip not installed" & exit /B 1 ) + + :: Check that required environment variables are set if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) if not defined BUILD_HOME ( echo "ERROR: BUILD_HOME must be defined" & exit /B 1 ) @@ -113,14 +118,17 @@ set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --r :: create SUT benchmark archive cd .\benchmark -7z a benchmark-%PLATFORM%.zip .\%PROJECT%-%SUT_BUILD_ID% +7z a benchmark-%PLATFORM%.zip .\%PROJECT%-%SUT_BUILD_ID% > nul :: stage artifacts for upload -mkdir %PROJ_DIR%\upload +if not exist %PROJ_DIR\upload ( + mkdir %PROJ_DIR%\upload +) move /Y receipt.json %PROJ_DIR%\upload\receipt.json move /Y benchmark-%PLATFORM%.zip %PROJ_DIR%\upload\benchmark-%PLATFORM%.zip + :: GitHub Actions echo ::set-env name=SUT_BUILD_ID::%SUT_BUILD_ID% From dd499fe721ae4cada0a81b7187290472728bfe03 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 15:28:25 -0400 Subject: [PATCH 123/266] Update run-nrtests.cmd --- tools/run-nrtests.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index 6136b44bc..bd1a2b834 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -122,7 +122,7 @@ cd .\benchmark :: stage artifacts for upload -if not exist %PROJ_DIR\upload ( +if not exist %PROJ_DIR%\upload ( mkdir %PROJ_DIR%\upload ) move /Y receipt.json %PROJ_DIR%\upload\receipt.json From 84cb32b5125e6fc93572c134c3251aa91a870381 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 15:36:15 -0400 Subject: [PATCH 124/266] Update build-and-test.yml Working on upload --- .github/workflows/build-and-test.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ac509345c..42331064f 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -34,10 +34,9 @@ jobs: - name: Before test run: before-nrtest.cmd - name: Reg test - run: echo %CD% -# run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% + run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Upload artifacts - run: echo $GITHUB_WORKSPACE + run: ls -l $GITHUB_WORKSPACE/upload shell: bash # uses: actions/upload-artifact@v2 # with: From a9e400d14cc273a77bf21691ed7aa5727bf2cda2 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 15:47:32 -0400 Subject: [PATCH 125/266] Work in progress Adds artifact upload --- .github/workflows/build-and-test.yml | 10 ++++------ tools/run-nrtests.cmd | 7 +++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 42331064f..4e084e1aa 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -36,9 +36,7 @@ jobs: - name: Reg test run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Upload artifacts - run: ls -l $GITHUB_WORKSPACE/upload - shell: bash -# uses: actions/upload-artifact@v2 -# with: -# name: receipt -# path: $GITHUB_WORKSPACE/$TEST_HOME/benchmark/receipt.json + uses: actions/upload-artifact@v2 + with: + name: artifacts + path: $GITHUB_WORKSPACE/upload/* diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index bd1a2b834..07faf3f78 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -117,16 +117,15 @@ set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --r :: create SUT benchmark archive +echo INFO: Staging nrtest artifacts for upload cd .\benchmark 7z a benchmark-%PLATFORM%.zip .\%PROJECT%-%SUT_BUILD_ID% > nul - -:: stage artifacts for upload if not exist %PROJ_DIR%\upload ( mkdir %PROJ_DIR%\upload ) -move /Y receipt.json %PROJ_DIR%\upload\receipt.json -move /Y benchmark-%PLATFORM%.zip %PROJ_DIR%\upload\benchmark-%PLATFORM%.zip +move /Y receipt.json %PROJ_DIR%\upload\receipt.json > nul +move /Y benchmark-%PLATFORM%.zip %PROJ_DIR%\upload\benchmark-%PLATFORM%.zip > nul :: GitHub Actions From bf0e62b61e8fc7856205b7bb651254eb51f58df9 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 15:56:29 -0400 Subject: [PATCH 126/266] Work in progress Debugging --- .github/workflows/build-and-test.yml | 4 ++-- tools/run-nrtests.cmd | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 4e084e1aa..5d1d33aff 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -38,5 +38,5 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v2 with: - name: artifacts - path: $GITHUB_WORKSPACE/upload/* + name: nrtest artifacts + path: $GITHUB_WORKSPACE/upload/ diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index 07faf3f78..d064443ba 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -115,6 +115,7 @@ echo INFO: Comparing SUT artifacts to REF %REF_BUILD_ID% set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --rtol %RTOL_VALUE% --atol %ATOL_VALUE% -o benchmark\receipt.json %NRTEST_COMMAND% +echo. :: create SUT benchmark archive echo INFO: Staging nrtest artifacts for upload From d1a2c535650a189e76d2e7b35d2cc5435c09cf67 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 16:05:36 -0400 Subject: [PATCH 127/266] Work in progress Debugging --- .github/workflows/build-and-test.yml | 2 +- tools/run-nrtests.cmd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5d1d33aff..2864a12d4 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -39,4 +39,4 @@ jobs: uses: actions/upload-artifact@v2 with: name: nrtest artifacts - path: $GITHUB_WORKSPACE/upload/ + path: $GITHUB_WORKSPACE\upload\ diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index d064443ba..7124f8643 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -67,7 +67,7 @@ if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( :: recursively build test list -set TESTS= +set "TESTS=tests\examples" for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( set FULL_PATH=%%T set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! From d10e391508b2ed62cf34237833cc89c030c2141b Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 16:20:57 -0400 Subject: [PATCH 128/266] Update run-nrtests.cmd --- tools/run-nrtests.cmd | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index 7124f8643..067832d3d 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -68,10 +68,11 @@ if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( :: recursively build test list set "TESTS=tests\examples" - for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( - set FULL_PATH=%%T - set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! -) +:: set TESTS= +:: for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( +:: set FULL_PATH=%%T +:: set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! +::) :: determine location of python Scripts folder @@ -128,6 +129,8 @@ if not exist %PROJ_DIR%\upload ( move /Y receipt.json %PROJ_DIR%\upload\receipt.json > nul move /Y benchmark-%PLATFORM%.zip %PROJ_DIR%\upload\benchmark-%PLATFORM%.zip > nul +echo INFO: Artifacts staged at %PROJ_DIR%\upload + :: GitHub Actions echo ::set-env name=SUT_BUILD_ID::%SUT_BUILD_ID% From b1228f4abd061574f67362816263c27256da555c Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 16:25:08 -0400 Subject: [PATCH 129/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 2864a12d4..704bef7e4 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -39,4 +39,4 @@ jobs: uses: actions/upload-artifact@v2 with: name: nrtest artifacts - path: $GITHUB_WORKSPACE\upload\ + path: $GITHUB_WORKSPACE\upload\receipt.json From 3b80cc8c612ef07ba1e009486ce8039fd09b295d Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 16:37:04 -0400 Subject: [PATCH 130/266] Update build-and-test.yml Debugging --- .github/workflows/build-and-test.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 704bef7e4..cbee88ccf 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -12,7 +12,6 @@ jobs: defaults: run: shell: cmd - working-directory: tools env: PROJECT: swmm BUILD_HOME: build @@ -30,13 +29,13 @@ jobs: # - name: Build and unit test # run: make.cmd /t - name: Build - run: make.cmd + run: tools/make.cmd - name: Before test - run: before-nrtest.cmd + run: tools/before-nrtest.cmd - name: Reg test - run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% + run: tools/run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Upload artifacts uses: actions/upload-artifact@v2 with: name: nrtest artifacts - path: $GITHUB_WORKSPACE\upload\receipt.json + path: $GITHUB_WORKSPACE/upload/receipt.json From 87da9ee50a2f3b6223fed0edaacde40bcc311757 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 16:39:02 -0400 Subject: [PATCH 131/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index cbee88ccf..6d92dae45 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -25,7 +25,7 @@ jobs: - name: Install requirements run: | python -m pip install --upgrade pip - python -m pip install -r requirements-win.txt + python -m pip install -r tools/requirements-win.txt # - name: Build and unit test # run: make.cmd /t - name: Build From 44575402b438575e2a4d6f6ceebc0b80127690af Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 16:42:28 -0400 Subject: [PATCH 132/266] Update build-and-test.yml Debugging --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 6d92dae45..a8bd027c0 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -38,4 +38,4 @@ jobs: uses: actions/upload-artifact@v2 with: name: nrtest artifacts - path: $GITHUB_WORKSPACE/upload/receipt.json + path: upload/receipt.json From 386a978603d3db7982c1dfa1892e6acbc32b03e4 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 16:46:12 -0400 Subject: [PATCH 133/266] Update build-and-test.yml Adds upload --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a8bd027c0..a71e256bd 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -38,4 +38,4 @@ jobs: uses: actions/upload-artifact@v2 with: name: nrtest artifacts - path: upload/receipt.json + path: upload/ From 6e45c60358858d517cc4833989d30eef9087167a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 16:52:19 -0400 Subject: [PATCH 134/266] Update run-nrtests.cmd --- tools/run-nrtests.cmd | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index 067832d3d..fa91f65bc 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -67,12 +67,12 @@ if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( :: recursively build test list -set "TESTS=tests\examples" -:: set TESTS= -:: for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( -:: set FULL_PATH=%%T -:: set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! -::) +:: set "TESTS=tests\examples" +set TESTS= +for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( + set FULL_PATH=%%T + set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! +) :: determine location of python Scripts folder From 35e2a6afac317eb745e2a22fc458f58ee71d7a7d Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 17:08:11 -0400 Subject: [PATCH 135/266] Github Actions Adds actions workflow configuration --- .github/workflows/build-and-test.yml | 25 ++++++++++++++++++------- tools/run-nrtests.cmd | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a71e256bd..5e411774a 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -9,33 +9,44 @@ on: jobs: win_build: runs-on: windows-2016 + defaults: run: shell: cmd + env: PROJECT: swmm BUILD_HOME: build TEST_HOME: nrtests + steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 + - name: Setup python uses: actions/setup-python@v2 with: python-version: '3.7' + - name: Install requirements run: | python -m pip install --upgrade pip python -m pip install -r tools/requirements-win.txt -# - name: Build and unit test -# run: make.cmd /t - - name: Build + + - name: Build for unit test + run: tools/make.cmd /t + + - name: Build for reg test run: tools/make.cmd - - name: Before test + + - name: Before reg test run: tools/before-nrtest.cmd - - name: Reg test + + - name: Run reg test run: tools/run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% + - name: Upload artifacts uses: actions/upload-artifact@v2 with: - name: nrtest artifacts + name: nrtest-artifacts path: upload/ diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index fa91f65bc..c96c57ab6 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -129,7 +129,7 @@ if not exist %PROJ_DIR%\upload ( move /Y receipt.json %PROJ_DIR%\upload\receipt.json > nul move /Y benchmark-%PLATFORM%.zip %PROJ_DIR%\upload\benchmark-%PLATFORM%.zip > nul -echo INFO: Artifacts staged at %PROJ_DIR%\upload +:: echo INFO: Artifacts staged at %PROJ_DIR%\upload :: GitHub Actions From 240734465528cca2a15abe630fe46a86ca535ecb Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 17:22:53 -0400 Subject: [PATCH 136/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5e411774a..e3e0ea97c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -7,7 +7,14 @@ on: branches: [ upstream-dev ] jobs: + checkout: + runs-on: windows-2016 + steps: + - name: Checkout + uses: actions/checkout@v2 + win_build: + needs: checkout runs-on: windows-2016 defaults: @@ -20,9 +27,6 @@ jobs: TEST_HOME: nrtests steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup python uses: actions/setup-python@v2 with: From 55bba166ba064e6c49413b034aaffaa5fb3266a1 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 17:33:43 -0400 Subject: [PATCH 137/266] Update build-and-test.yml Separates unit and reg tests into two separate jobs --- .github/workflows/build-and-test.yml | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index e3e0ea97c..44d6f487e 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -7,16 +7,26 @@ on: branches: [ upstream-dev ] jobs: - checkout: + unit_test: runs-on: windows-2016 + defaults: + run: + shell: cmd + + env: + PROJECT: swmm + BUILD_HOME: build + steps: - name: Checkout uses: actions/checkout@v2 - win_build: - needs: checkout - runs-on: windows-2016 + - name: Build for unit test + run: tools/make.cmd /t + + reg_test: + runs-on: windows-2016 defaults: run: shell: cmd @@ -27,6 +37,9 @@ jobs: TEST_HOME: nrtests steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup python uses: actions/setup-python@v2 with: @@ -37,9 +50,6 @@ jobs: python -m pip install --upgrade pip python -m pip install -r tools/requirements-win.txt - - name: Build for unit test - run: tools/make.cmd /t - - name: Build for reg test run: tools/make.cmd From a36c449f58bd74c1b7e5d7f5829ab308939e7d9c Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 18:55:25 -0400 Subject: [PATCH 138/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 44d6f487e..10e9b0853 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -6,31 +6,13 @@ on: pull_request: branches: [ upstream-dev ] -jobs: - unit_test: - runs-on: windows-2016 - defaults: - run: - shell: cmd - - env: - PROJECT: swmm - BUILD_HOME: build - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Build for unit test - run: tools/make.cmd /t - - reg_test: +jobs: + win_build: runs-on: windows-2016 defaults: run: shell: cmd - env: PROJECT: swmm BUILD_HOME: build @@ -50,6 +32,9 @@ jobs: python -m pip install --upgrade pip python -m pip install -r tools/requirements-win.txt + - name: Build and unit test + run: tools/make.cmd /t + - name: Build for reg test run: tools/make.cmd From d2877dc30a2e46e76be1c5b48515005cafa388b7 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 19:04:36 -0400 Subject: [PATCH 139/266] Configures GitHub actions --- .github/workflows/build-and-test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 10e9b0853..1e163b741 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -6,7 +6,6 @@ on: pull_request: branches: [ upstream-dev ] - jobs: win_build: runs-on: windows-2016 From e320cc8f168f7e1a5e3cc555b2978e7301755a3c Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 19:54:33 -0400 Subject: [PATCH 140/266] Updating headers --- CMakeLists.txt | 2 +- tools/before-nrtest.cmd | 4 ++-- tools/make.cmd | 4 ++-- tools/run-nrtests.cmd | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 120b8b1a6..90694faf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ # CMakeLists.txt - CMake configuration file for swmm-solver # # Created: July 11, 2019 -# Modified: Nov 25, 2019 +# Updated: May 29, 2020 # # Author: Michael E. Tryby # US EPA ORD/CESER diff --git a/tools/before-nrtest.cmd b/tools/before-nrtest.cmd index dd558fbde..c49bfa119 100644 --- a/tools/before-nrtest.cmd +++ b/tools/before-nrtest.cmd @@ -1,8 +1,8 @@ :: :: before-test.cmd - Stages test and benchmark files for nrtest :: -:: Date Created: 10/16/2019 -:: Date Updated: +:: Created: Oct 16, 2019 +:: Updated: May 29, 2020 :: :: Author: Michael E. Tryby :: US EPA - ORD/CESER diff --git a/tools/make.cmd b/tools/make.cmd index 29455010a..7f8d8479d 100644 --- a/tools/make.cmd +++ b/tools/make.cmd @@ -1,8 +1,8 @@ :: :: make.cmd - builds project :: -:: Date Created: 10/15/2019 -:: Date Updated: +:: Created: Oct 15, 2019 +:: Updated: May 29, 2020 :: :: Author: Michael E. Tryby :: US EPA - ORD/CESER diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index ba458a073..be7814f0f 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -1,8 +1,8 @@ :: :: run_nrtest.cmd - Runs numerical regression test :: -:: Date Created: 10/16/2019 -:: Date Updated: +:: Created: Oct 16, 2019 +:: Updated: May 29, 2020 :: :: Author: Michael E. Tryby :: US EPA - ORD/CESER From 89b442f0170f022617d03065b9aed7d4101f8df6 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 29 May 2020 20:26:36 -0400 Subject: [PATCH 141/266] Update build-and-test.yml Changing action trigger to develop branch --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 1e163b741..a73785376 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,9 +2,9 @@ name: Build and Test on: push: - branches: [ upstream-dev ] + branches: [ develop ] pull_request: - branches: [ upstream-dev ] + branches: [ develop ] jobs: win_build: From 8b21193de0dde11fb95e3948435815b5227bb6eb Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sat, 30 May 2020 16:51:52 -0400 Subject: [PATCH 142/266] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 8c897a78d..4e184cfc1 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ Stormwater-Management-Model ORD Stormwater Management Model (aka "SWMM") +## Build Status +![Build and Test](https://github.com/SWMM-Project/swmm-solver/workflows/Build%20and%20Test/badge.svg?branch=upstream-dev) + ## Introduction This is the official SWMM source code repository maintained by US EPA Office of Research and Development, Center For Environmental Solutions & Emergency Response, Water Infrastructure Division located in Cincinnati, Ohio. From e0c4b1fcd4d722f2485552cc41ea8f33791d17b4 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sat, 30 May 2020 16:53:52 -0400 Subject: [PATCH 143/266] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8c897a78d..09a0e1894 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Stormwater-Management-Model ORD Stormwater Management Model (aka "SWMM") +## Build Status +![Build and Test](https://github.com/USEPA/Stormwater-Management-Model/workflows/Build%20and%20Test/badge.svg) + + ## Introduction This is the official SWMM source code repository maintained by US EPA Office of Research and Development, Center For Environmental Solutions & Emergency Response, Water Infrastructure Division located in Cincinnati, Ohio. From e29d8a62c0145ce10b2a098f59f31afeb9b1da52 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sat, 30 May 2020 17:35:37 -0400 Subject: [PATCH 144/266] Work in progress Streamlines environment check --- .gitignore | 1 + tools/before-nrtest.cmd | 7 +++---- tools/run-nrtests.cmd | 15 +++------------ 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index d48ffa364..548e7a2f5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ src/solver/include/*_export.h build/ nrtests/ +upload/ diff --git a/tools/before-nrtest.cmd b/tools/before-nrtest.cmd index c49bfa119..7a216e1d2 100644 --- a/tools/before-nrtest.cmd +++ b/tools/before-nrtest.cmd @@ -56,10 +56,9 @@ if [%1] == [] (set "RELEASE_TAG=" :: check env variables and apply defaults -if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) -if not defined BUILD_HOME ( echo "ERROR: BUILD_HOME must be defined" & exit /B 1 ) -if not defined PLATFORM ( echo "ERROR: PLATFORM must be defined" & exit /B 1 ) - +for %%v in (PROJECT BUILD_HOME PLATFORM) do ( + if not defined %%v ( echo "ERROR: %%v must be defined" & exit /B 1 ) +) echo INFO: Staging files for regression testing diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index be7814f0f..195f9a0dc 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -30,13 +30,10 @@ setlocal EnableDelayedExpansion where 7z > nul if %ERRORLEVEL% neq 0 ( echo "ERROR: 7zip not installed" & exit /B 1 ) - :: Check that required environment variables are set -if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) -if not defined BUILD_HOME ( echo "ERROR: BUILD_HOME must be defined" & exit /B 1 ) -if not defined TEST_HOME ( echo "ERROR: TEST_HOME must be defined" & exit /B 1 ) -if not defined PLATFORM ( echo "ERROR: PLATFORM must be defined" & exit /B 1 ) -if not defined REF_BUILD_ID ( echo "ERROR: REF_BUILD_ID must be defined" & exit /B 1 ) +for %%v in (PROJECT BUILD_HOME TEST_HOME PLATFORM REF_BUILD_ID) do ( + if not defined %%v ( echo "ERROR: %%v must be defined" & exit /B 1 ) +) :: determine project directory @@ -129,12 +126,6 @@ if not exist %PROJ_DIR%\upload ( move /Y receipt.json %PROJ_DIR%\upload\receipt.json > nul move /Y benchmark-%PLATFORM%.zip %PROJ_DIR%\upload\benchmark-%PLATFORM%.zip > nul -:: echo INFO: Artifacts staged at %PROJ_DIR%\upload - - -:: GitHub Actions -echo ::set-env name=SUT_BUILD_ID::%SUT_BUILD_ID% - :: Return user to their current dir cd %CUR_DIR% From 465cbed391eba5d425b189039858858073b63b03 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sat, 30 May 2020 17:49:20 -0400 Subject: [PATCH 145/266] Update before-nrtest.cmd Streamlines dependency check --- tools/before-nrtest.cmd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/before-nrtest.cmd b/tools/before-nrtest.cmd index 7a216e1d2..78898e18f 100644 --- a/tools/before-nrtest.cmd +++ b/tools/before-nrtest.cmd @@ -40,10 +40,10 @@ setlocal :: check that dependencies are installed -where curl > nul -if %ERRORLEVEL% neq 0 ( echo "ERROR: curl not installed" & exit /B 1 ) -where 7z > nul -if %ERRORLEVEL% neq 0 ( echo "ERROR: 7zip not installed" & exit /B 1 ) +for %%d in (curl 7z) do ( + where %%d > nul + if %ERRORLEVEL% neq 0 ( echo "ERROR: %%d not installed" & exit /B 1 ) +) :: set URL to github repo with test files From 5d9b75bbb5702a6bf33887ae1e6050f33994f840 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sat, 30 May 2020 18:06:02 -0400 Subject: [PATCH 146/266] Update run-nrtests.cmd --- tools/run-nrtests.cmd | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index 195f9a0dc..c96a04dd5 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -64,12 +64,12 @@ if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( :: recursively build test list -:: set "TESTS=tests\examples" -set TESTS= -for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( - set FULL_PATH=%%T - set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! -) +set "TESTS=tests\examples" +:: set TESTS= +:: for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( +:: set FULL_PATH=%%T +:: set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! +:: ) :: determine location of python Scripts folder @@ -112,8 +112,12 @@ echo. echo INFO: Comparing SUT artifacts to REF %REF_BUILD_ID% set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --rtol %RTOL_VALUE% --atol %ATOL_VALUE% -o benchmark\receipt.json %NRTEST_COMMAND% +if %ERRORLEVEL% eq 0 ( + echo INFO: nrtest compare exited successfully +) else ( + echo ERROR: nrtest compare exited with errors +) -echo. :: create SUT benchmark archive echo INFO: Staging nrtest artifacts for upload From 3b5335c5088cc2dc46bfe4a53494d625cacf9aab Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sat, 30 May 2020 18:08:16 -0400 Subject: [PATCH 147/266] Update run-nrtests.cmd --- tools/run-nrtests.cmd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index c96a04dd5..2e6762d1e 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -112,10 +112,10 @@ echo. echo INFO: Comparing SUT artifacts to REF %REF_BUILD_ID% set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --rtol %RTOL_VALUE% --atol %ATOL_VALUE% -o benchmark\receipt.json %NRTEST_COMMAND% -if %ERRORLEVEL% eq 0 ( - echo INFO: nrtest compare exited successfully -) else ( +if %ERRORLEVEL% neq 0 ( echo ERROR: nrtest compare exited with errors +) else ( + echo INFO: nrtest compare exited successfully ) From 09504360e5d1a395d6eb8b59791bfa8b9ceacd4a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sat, 30 May 2020 18:17:09 -0400 Subject: [PATCH 148/266] Update run-nrtests.cmd --- tools/run-nrtests.cmd | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index 2e6762d1e..e42509b1c 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -62,6 +62,11 @@ if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( %PLATFORM% %SUT_BUILD_ID% %SUT_VERSION% > apps\%PROJECT%-%SUT_BUILD_ID%.json ) +:: prepare for artifact upload +if not exist %PROJ_DIR%\upload ( + mkdir %PROJ_DIR%\upload +) + :: recursively build test list set "TESTS=tests\examples" @@ -112,24 +117,18 @@ echo. echo INFO: Comparing SUT artifacts to REF %REF_BUILD_ID% set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --rtol %RTOL_VALUE% --atol %ATOL_VALUE% -o benchmark\receipt.json %NRTEST_COMMAND% -if %ERRORLEVEL% neq 0 ( - echo ERROR: nrtest compare exited with errors -) else ( - echo INFO: nrtest compare exited successfully -) +set RESULT=%ERRORLEVEL% - -:: create SUT benchmark archive -echo INFO: Staging nrtest artifacts for upload -cd .\benchmark -7z a benchmark-%PLATFORM%.zip .\%PROJECT%-%SUT_BUILD_ID% > nul - -if not exist %PROJ_DIR%\upload ( - mkdir %PROJ_DIR%\upload +if %RESULT% neq 0 ( + echo ERROR: nrtest compare exited with errors + cd .\benchmark + 7z a benchmark-%PLATFORM%.zip .\%PROJECT%-%SUT_BUILD_ID% > nul + move /Y benchmark-%PLATFORM%.zip %PROJ_DIR%\upload\benchmark-%PLATFORM%.zip > nul +) else ( + echo INFO: nrtest compare exited successfully + move /Y receipt.json %PROJ_DIR%\upload\receipt.json > nul ) -move /Y receipt.json %PROJ_DIR%\upload\receipt.json > nul -move /Y benchmark-%PLATFORM%.zip %PROJ_DIR%\upload\benchmark-%PLATFORM%.zip > nul - -:: Return user to their current dir +:: Return user to their current dir and exit cd %CUR_DIR% +exit /B %RESULT% From a349dc910470c98b5f5f9fdcf9b11c752282c8c9 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sat, 30 May 2020 18:18:55 -0400 Subject: [PATCH 149/266] Update run-nrtests.cmd --- tools/run-nrtests.cmd | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index e42509b1c..352794279 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -117,11 +117,13 @@ echo. echo INFO: Comparing SUT artifacts to REF %REF_BUILD_ID% set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --rtol %RTOL_VALUE% --atol %ATOL_VALUE% -o benchmark\receipt.json %NRTEST_COMMAND% + set RESULT=%ERRORLEVEL% +cd .\benchmark +:: stage artifacts for upload if %RESULT% neq 0 ( echo ERROR: nrtest compare exited with errors - cd .\benchmark 7z a benchmark-%PLATFORM%.zip .\%PROJECT%-%SUT_BUILD_ID% > nul move /Y benchmark-%PLATFORM%.zip %PROJ_DIR%\upload\benchmark-%PLATFORM%.zip > nul ) else ( @@ -129,6 +131,6 @@ if %RESULT% neq 0 ( move /Y receipt.json %PROJ_DIR%\upload\receipt.json > nul ) -:: Return user to their current dir and exit +:: return user to their current dir and exit cd %CUR_DIR% exit /B %RESULT% From 874e826d23bbb283d8c666a330715c03defe1e0c Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sat, 30 May 2020 18:23:00 -0400 Subject: [PATCH 150/266] Update run-nrtests.cmd Sets return value --- tools/run-nrtests.cmd | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index 352794279..ec2c25756 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -28,7 +28,7 @@ setlocal EnableDelayedExpansion :: check that dependencies are installed where 7z > nul -if %ERRORLEVEL% neq 0 ( echo "ERROR: 7zip not installed" & exit /B 1 ) +if %ERRORLEVEL% neq 0 ( echo "ERROR: 7z not installed" & exit /B 1 ) :: Check that required environment variables are set for %%v in (PROJECT BUILD_HOME TEST_HOME PLATFORM REF_BUILD_ID) do ( @@ -69,12 +69,12 @@ if not exist %PROJ_DIR%\upload ( :: recursively build test list -set "TESTS=tests\examples" -:: set TESTS= -:: for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( -:: set FULL_PATH=%%T -:: set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! -:: ) +:: set "TESTS=tests\examples" +set TESTS= +for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( + set FULL_PATH=%%T + set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! +) :: determine location of python Scripts folder From 6fa5874ee12eafb99b8576b1f327d7edbeb40449 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sat, 30 May 2020 18:33:47 -0400 Subject: [PATCH 151/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 1e163b741..369fdcf52 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -44,6 +44,7 @@ jobs: run: tools/run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Upload artifacts + if: ${{ always() }} uses: actions/upload-artifact@v2 with: name: nrtest-artifacts From c5841e84552286d3e07619088ceb8aa071c1b3b2 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sun, 31 May 2020 12:03:01 -0400 Subject: [PATCH 152/266] Update build-and-test.yml Creates header --- .github/workflows/build-and-test.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 369fdcf52..15ed09ed6 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -1,10 +1,20 @@ +# +# build-and-test.yml - GitHub Actions CI for swmm-solver +# +# Created: May 19, 2019 +# Updated: May 31, 2019 +# +# Author: Michael E. Tryby +# US EPA - ORD/CESER +# + name: Build and Test on: push: - branches: [ upstream-dev ] + branches: [ develop ] pull_request: - branches: [ upstream-dev ] + branches: [ develop ] jobs: win_build: From 22cdce20ed1cc1f1052133a20e99f8454be5532c Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 1 Jun 2020 10:15:09 -0400 Subject: [PATCH 153/266] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 9a81ff8f1..09a0e1894 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,6 @@ Stormwater-Management-Model ORD Stormwater Management Model (aka "SWMM") -## Build Status -![Build and Test](https://github.com/SWMM-Project/swmm-solver/workflows/Build%20and%20Test/badge.svg?branch=upstream-dev) - ## Build Status ![Build and Test](https://github.com/USEPA/Stormwater-Management-Model/workflows/Build%20and%20Test/badge.svg) From 04dad46b5e3883e0b1bb76aefc5a64bac7ecdf0d Mon Sep 17 00:00:00 2001 From: michaeltryby Date: Tue, 9 Jun 2020 14:33:58 -0400 Subject: [PATCH 154/266] updating --- .gitignore | 1 + tools/run-nrtests.cmd | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 827fd7b12..7e8e71376 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,6 @@ build/ nrtests/ +upload/ *_export.h diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd index 3e62705bd..8b07f0ee6 100644 --- a/tools/run-nrtests.cmd +++ b/tools/run-nrtests.cmd @@ -87,7 +87,7 @@ set TEST_OUTPUT_PATH=benchmark\%PROJECT%-%SUT_BUILD_ID% set NRTEST_COMPARE_CMD=python.exe %NRTEST_SCRIPT_PATH%\nrtest compare set REF_OUTPUT_PATH=benchmark\%PROJECT%-%REF_BUILD_ID% set RTOL_VALUE=0.01 -set ATOL_VALUE=0.0 +set ATOL_VALUE=1.0E-6 :: change current directory to test suite ::cd %TEST_HOME% From 01eed49c34ad932153de0acd4f05a6dc3a1bdcc2 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 24 Jun 2020 17:46:14 -0400 Subject: [PATCH 155/266] Rolling back dwflow.c to previous version --- src/solver/dwflow.c | 1219 +++++++++++++++++++------------------------ 1 file changed, 540 insertions(+), 679 deletions(-) diff --git a/src/solver/dwflow.c b/src/solver/dwflow.c index f04975235..de096ad38 100644 --- a/src/solver/dwflow.c +++ b/src/solver/dwflow.c @@ -3,7 +3,11 @@ // // Project: EPA SWMM5 // Version: 5.1 -// Date: 03/01/20 (Build 5.1.015) +// Date: 03/20/14 (Build 5.1.001) +// 03/19/15 (Build 5.1.008) +// 03/14/17 (Build 5.1.012) +// 05/10/18 (Build 5.1.013) +// 03/01/20 (Build 5.1.014) // Author: L. Rossman (EPA) // M. Tryby (EPA) // R. Dickinson (CDM) @@ -11,486 +15,143 @@ // Solves the momentum equation for flow in a conduit under dynamic wave // flow routing. // -// Completely refactored for release 5.1.015. +// Build 5.1.008: +// - Bug in finding if conduit was upstrm/dnstrm full was fixed. +// +// Build 5.1.012: +// - Modified uniform loss rate term of conduit momentum equation. +// +// Build 5.1.013: +// - Preissmann slot surcharge option implemented. +// - Changed sign of uniform loss rate term (dq6) in flow updating equation. +// +// Build 5.1.014: +// - Conduit evap. and seepage loss initialized to 0 in dwflow_findConduitFlow. +// - Most current flow (qLast) used instead of previous time period flow +// (qOld) in call to link_getLossRate. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE #include "headers.h" #include -// Flow cross-section variables: -// y = depth -// h = head -// a = area -// r = hydraulic radius -// w = top width -// Location notation: -// 1 = upstream -// 2 = downstream -// Mid = midstream - -// Intermediate conduit data -typedef struct -{ - double y1, y2; // upstream/downstream flow depth (ft) - double h1, h2; // upstream/downstream hydraulic head (ft) - double a1, a2; // upstream/downstream flow area (ft2) - double r1; // upstream hydraulic radius (ft) - double yMid, aMid, rMid; // mid-stream values of y, a, and r - double yFull; // conduit full depth (ft) - double yCrit; // critical flow depth (ft) - double fasnh; // fraction between normal & critical depth - double aWtd, rWtd; // upstream weighted area & hyd. radius - double flow; // estimated flow for current time step (cfs) - double aOld; // computed area from previous time step (ft2) - double velocity; // flow velocity (ft/sec) - double sigma; // inertial damping factor - double length; // effective conduit length (ft) - int isFull; // TRUE if conduit flows full - int linkIndex; // index of conduit's parent link -} ConduitData; - static const double MAXVELOCITY = 50.; // max. allowable velocity (ft/sec) -static void initConduitData(TLink *link, ConduitData *cd); -static void setFlowDepth(TNode *node, double hInvert, double yFull, - double *h, double *y); - -static void findFlowClass(TLink *link, ConduitData *cd); -static int getWetNegativeFlowClass(TLink *link, ConduitData *cd, double yOffset1); -static int getWetPositiveFlowClass(TLink *link, ConduitData *cd, double yOffset2); -static int getDryToWetFlowClass(TLink *link, ConduitData *cd, double yOffset1); -static int getWetToDryFlowClass(TLink *link, ConduitData *cd, double yOffset2); - -static void computeSurfaceArea(TLink *link, ConduitData *cd); -static void getSubCriticalArea(TLink *link, ConduitData *cd); -static void getUpCriticalArea(TLink *link, ConduitData *cd); -static void getDownCriticalArea(TLink *link, ConduitData *cd); -static void getUpDryArea(TLink *link, ConduitData *cd); -static void getDownDryArea(TLink *link, ConduitData *cd); - -static void computeFlowSectionGeometry(TLink *link, ConduitData *cd); -static int conduitIsDryOrClosed(TLink *link, ConduitData *cd, double timeStep); -static void applyInertialDamping(TLink *link, ConduitData *cd); -static double solveMomentumEqn(TLink *link, ConduitData *cd, double timeStep); -static double findLocalLosses(TLink *link, ConduitData *cd); - -static double checkForCulvertInletControl(TLink *link, ConduitData *cd, double flow); -static double checkForNormalFlowControl(TLink *link, ConduitData *cd, double flow); -static int hasSlopeBasedNormalFlow(ConduitData *cd, int hasOutfall); -static int hasFroudeBasedNormalFlow(ConduitData *cd, int hasOutfall, double flow); -static double checkNormalFlowValue(TLink *link, ConduitData *cd, double flow); - -static double applyUnderRelaxation(ConduitData *cd, double omega, double flow); -static double checkImposedFlowLimits(TLink *link, ConduitData *cd, double flow); -static void saveFlowResult(TLink *link, ConduitData *cd, double flow); - -static double getWidth(TXsect *xsect, double y); -static double getSlotWidth(TXsect *xsect, double y); -static double getArea(TXsect *xsect, double y, double wSlot); -static double getHydRad(TXsect *xsect, double y); - -//============================================================================= - -void dwflow_findConduitFlow(int linkIndex, int trials, double omega, double timeStep) -// -// Updates flow in a conduit link by solving finite difference form of combined -// St. Venant continuity and momentum equations. -{ - double flow; // new conduit flow estimate (per barrel) (cfs) - TLink *link; // conduit's parent link - ConduitData cd; // intermediate conduit data - - link = &Link[linkIndex]; - cd.linkIndex = linkIndex; - - initConduitData(link, &cd); - findFlowClass(link, &cd); - computeSurfaceArea(link, &cd); - computeFlowSectionGeometry(link, &cd); - if (conduitIsDryOrClosed(link, &cd, timeStep)) - return; - applyInertialDamping(link, &cd); - - flow = solveMomentumEqn(link, &cd, timeStep); - flow = checkForCulvertInletControl(link, &cd, flow); - flow = checkForNormalFlowControl(link, &cd, flow); - if (trials > 0) - flow = applyUnderRelaxation(&cd, omega, flow); - flow = checkImposedFlowLimits(link, &cd, flow); - saveFlowResult(link, &cd, flow); -} - -//============================================================================= - -void initConduitData(TLink *link, ConduitData *cd) -{ - int k; // conduit index - double hInvert; // conduit's invert elevation (ft) - TNode *node; // node object - - k = link->subIndex; - cd->flow = Conduit[k].q1; - cd->yFull = link->xsect.yFull; - cd->aOld = MAX(Conduit[k].a2, FUDGE); - cd->length = Conduit[k].modLength; - cd->fasnh = 1.0; - cd->yCrit = cd->yFull; - - node = &Node[link->node1]; - hInvert = node->invertElev + link->offset1; - setFlowDepth(node, hInvert, cd->yFull, &cd->h1, &cd->y1); - node = &Node[link->node2]; - hInvert = node->invertElev + link->offset2; - setFlowDepth(node, hInvert, cd->yFull, &cd->h2, &cd->y2); - - Conduit[k].evapLossRate = 0.0; - Conduit[k].seepLossRate = 0.0; -} - -//============================================================================= - -void setFlowDepth(TNode *node, double hInvert, double yFull, double *h, double *y) -// -// Set the head (h) and flow depth (y) of a conduit connected to a specified node. -{ - *h = node->newDepth + node->invertElev; - *h = MAX(*h, hInvert); - *y = *h - hInvert; - *y = MAX(*y, FUDGE); - if (SurchargeMethod != SLOT) *y = MIN(*y, yFull); -} - -//============================================================================= - -void findFlowClass(TLink *link, ConduitData *cd) -// -// Find the type of flow a conduit is experiencing. -{ - int n1 = link->node1, - n2 = link->node2; - double yOffset1, yOffset2; - - // --- get upstream & downstream conduit invert offsets - yOffset1 = link->offset1; - yOffset2 = link->offset2; - if (Node[n1].type == OUTFALL) - yOffset1 = MAX(0.0, (yOffset1 - Node[n1].newDepth)); - if (Node[n2].type == OUTFALL) - yOffset2 = MAX(0.0, (yOffset2 - Node[n2].newDepth)); - - // --- default class is SUBCRITICAL - link->flowClass = SUBCRITICAL; - cd->fasnh = 1.0; - - // -- conduit is full - if (cd->y1 >= cd->yFull && cd->y2 >= cd->yFull) - link->flowClass = SUBCRITICAL; - - // --- both ends of conduit are wet - else if (cd->y1 > FUDGE && cd->y2 > FUDGE) - { - if (cd->flow < 0.0) - link->flowClass = getWetNegativeFlowClass(link, cd, yOffset1); - else - link->flowClass = getWetPositiveFlowClass(link, cd, yOffset2); - } - - // --- no flow at either end of conduit - else if (cd->y1 <= FUDGE && cd->y2 <= FUDGE) - link->flowClass = DRY; - - // --- downstream end of conduit is wet, upstream dry - else if (cd->y2 > FUDGE) - link->flowClass = getDryToWetFlowClass(link, cd, yOffset1); - - // --- upstream end of conduit is wet, downstream dry - else - link->flowClass = getWetToDryFlowClass(link, cd, yOffset2); -} - -//============================================================================= - -int getWetNegativeFlowClass(TLink *link, ConduitData *cd, double yOffset1) -// -// Determine flow class for a fully wetted conduit with reverse flow. -{ - double flow = fabs(cd->flow); - int flowClass = SUBCRITICAL; - - // --- upstream end at critical depth if flow depth is - // below conduit's critical depth and an upstream - // conduit offset exists - if (yOffset1 > 0.0) - { - cd->yCrit = MIN(link_getYnorm(cd->linkIndex, flow), - link_getYcrit(cd->linkIndex, flow)); - if (cd->y1 < cd->yCrit) flowClass = UP_CRITICAL; - } - return flowClass; -} - -//============================================================================= - -int getWetPositiveFlowClass(TLink *link, ConduitData *cd, double yOffset2) -// -// Determine flow class for a fully wetted conduit with positive flow. -{ - double flow = fabs(cd->flow); - double yNorm, yCrit; - double ycMin, ycMax; - int flowClass = SUBCRITICAL; - - // --- conduit has a downstream offset - if (yOffset2 > 0.0) - { - yNorm = link_getYnorm(cd->linkIndex, flow); - yCrit = link_getYcrit(cd->linkIndex, flow); - ycMin = MIN(yNorm, yCrit); - ycMax = MAX(yNorm, yCrit); - - // --- if downstream depth < smaller critical depth - // then flow class is Downstream Critical - if (cd->y2 < ycMin) flowClass = DN_CRITICAL; - - // --- if downstream depth between critical & normal - // depth compute a weighting factor (fasnh) to - // apply to downstream surface area - else if (cd->y2 < ycMax) - { - if (ycMax - ycMin < FUDGE) - cd->fasnh = 0.0; - else - cd->fasnh = (ycMax - cd->y2) / (ycMax - ycMin); - } - cd->yCrit = ycMin; - } - return flowClass; -} - -//============================================================================= - -int getDryToWetFlowClass(TLink *link, ConduitData *cd, double yOffset1) -// -// Determine flow class for a conduit that is dry at upstream end and -// wet at downstream end. -{ - double flow = fabs(cd->flow); - int flowClass = SUBCRITICAL; - - // --- flow classification is UP_DRY if downstream head < - // invert of upstream end of conduit - if (cd->h2 < Node[link->node1].invertElev + link->offset1) - flowClass = UP_DRY; - - // --- otherwise, the downstream head will be >= upstream - // conduit invert creating a flow reversal and upstream end - // should be at critical depth, providing that an upstream - // offset exists (otherwise subcritical condition is maintained) - else if (yOffset1 > 0.0) - { - cd->yCrit = MIN(link_getYnorm(cd->linkIndex, flow), - link_getYcrit(cd->linkIndex, flow)); - flowClass = UP_CRITICAL; - } - return flowClass; -} +static int getFlowClass(int link, double q, double h1, double h2, + double y1, double y2, double* criticalDepth, double* normalDepth, + double* fasnh); +static void findSurfArea(int link, double q, double length, double* h1, + double* h2, double* y1, double* y2); +static double findLocalLosses(int link, double a1, double a2, double aMid, + double q); -//============================================================================= +static double getWidth(TXsect* xsect, double y); +static double getSlotWidth(TXsect* xsect, double y); //(5.1.013) +static double getArea(TXsect* xsect, double y, double wSlot); //(5.1.013) +static double getHydRad(TXsect* xsect, double y); -int getWetToDryFlowClass(TLink *link, ConduitData *cd, double yOffset2) -// -// Determine flow class for a conduit that is wet at upstream end and -// dry at downstream end. -{ - double flow = fabs(cd->flow); - int flowClass = SUBCRITICAL; - - // --- flow classification is DN_DRY if upstream head < - // invert of downstream end of conduit - if (cd->h1 < Node[link->node2].invertElev + link->offset2) - flowClass = DN_DRY; - - // --- otherwise flow at downstream end should be at critical depth - // providing that a downstream offset exists (otherwise - // subcritical condition is maintained) - else if (yOffset2 > 0.0) - { - cd->yCrit = MIN(link_getYnorm(cd->linkIndex, flow), - link_getYcrit(cd->linkIndex, flow)); - flowClass = DN_CRITICAL; - } - return flowClass; -} +static double checkNormalFlow(int j, double q, double y1, double y2, + double a1, double r1); //============================================================================= -void computeSurfaceArea(TLink *link, ConduitData *cd) -// -// Compute surface area that conduit contributes to its end nodes. -{ - switch (link->flowClass) +void dwflow_findConduitFlow(int j, int steps, double omega, double dt) +// +// Input: j = link index +// steps = number of iteration steps taken +// omega = under-relaxation parameter +// dt = time step (sec) +// Output: returns new flow value (cfs) +// Purpose: updates flow in conduit link by solving finite difference +// form of continuity and momentum equations. +// +{ + int k; // index of conduit + int n1, n2; // indexes of end nodes + double z1, z2; // upstream/downstream invert elev. (ft) + double h1, h2; // upstream/dounstream flow heads (ft) + double y1, y2; // upstream/downstream flow depths (ft) + double a1, a2; // upstream/downstream flow areas (ft2) + double r1; // upstream hyd. radius (ft) + double yMid, rMid, aMid; // mid-stream or avg. values of y, r, & a + double aWtd, rWtd; // upstream weighted area & hyd. radius + double qLast; // flow from previous iteration (cfs) + double qOld; // flow from previous time step (cfs) + double aOld; // area from previous time step (ft2) + double v; // velocity (ft/sec) + double rho; // upstream weighting factor + double sigma; // inertial damping factor + double length; // effective conduit length (ft) + double wSlot; // Preissmann slot width (ft) //(5.1.013) + double dq1, dq2, dq3, dq4, dq5, // terms in momentum eqn. + dq6; // term for evap and infil losses + double denom; // denominator of flow update formula + double q; // new flow value (cfs) + double barrels; // number of barrels in conduit + TXsect* xsect = &Link[j].xsect; // ptr. to conduit's cross section data + char isFull = FALSE; // TRUE if conduit flowing full + char isClosed = FALSE; // TRUE if conduit closed + + + + // --- adjust isClosed status by any control action + if ( Link[j].setting == 0 ) isClosed = TRUE; + + // --- get flow from last time step & previous iteration + k = Link[j].subIndex; + barrels = Conduit[k].barrels; + qOld = Link[j].oldFlow / barrels; + qLast = Conduit[k].q1; + Conduit[k].evapLossRate = 0.0; //(5.1.014) + Conduit[k].seepLossRate = 0.0; //(5.1.014) + + // --- get most current heads at upstream and downstream ends of conduit + n1 = Link[j].node1; + n2 = Link[j].node2; + z1 = Node[n1].invertElev + Link[j].offset1; + z2 = Node[n2].invertElev + Link[j].offset2; + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + h1 = MAX(h1, z1); + h2 = MAX(h2, z2); + + // --- get unadjusted upstream and downstream flow depths in conduit + // (flow depth = head in conduit - elev. of conduit invert) + y1 = h1 - z1; + y2 = h2 - z2; + y1 = MAX(y1, FUDGE); + y2 = MAX(y2, FUDGE); + + // --- flow depths can't exceed full depth of conduit if slot not used + if ( SurchargeMethod != SLOT ) //(5.1.013) { - case SUBCRITICAL: - getSubCriticalArea(link, cd); - break; - case UP_CRITICAL: - getUpCriticalArea(link, cd); - break; - case DN_CRITICAL: - getDownCriticalArea(link, cd); - break; - case UP_DRY: - getUpDryArea(link, cd); - break; - case DN_DRY: - getDownDryArea(link, cd); - break; - case DRY: - link->surfArea1 = FUDGE * cd->length / 2.0; - link->surfArea2 = link->surfArea1; - break; + y1 = MIN(y1, xsect->yFull); + y2 = MIN(y2, xsect->yFull); } -} -//============================================================================= + // -- get area from solution at previous time step + aOld = Conduit[k].a2; + aOld = MAX(aOld, FUDGE); -void getSubCriticalArea(TLink *link, ConduitData *cd) -// -// Conduit surface area when neither end is dry or has critical flow. -{ - double w1, w2, wMid; - TXsect *xsect = &link->xsect; - - cd->yMid = 0.5 * (cd->y1 + cd->y2); - if (cd->yMid < FUDGE) cd->yMid = FUDGE; - w1 = getWidth(xsect, cd->y1); - w2 = getWidth(xsect, cd->y2); - wMid = getWidth(xsect, cd->yMid); - - // --- assign each end the avg. area over its half of the conduit - link->surfArea1 = (w1 + wMid) / 2. * cd->length / 2.; - link->surfArea2 = (wMid + w2) / 2. * cd->length / 2. * (cd->fasnh); -} + // --- use Courant-modified length instead of conduit's actual length + length = Conduit[k].modLength; -//============================================================================= + // --- find surface area contributions to upstream and downstream nodes + // based on previous iteration's flow estimate + findSurfArea(j, qLast, length, &h1, &h2, &y1, &y2); -void getUpCriticalArea(TLink *link, ConduitData *cd) -// -// Conduit surface area when upstream end has critical flow. -{ - double w2, wMid; - TXsect *xsect = &link->xsect; - - cd->y1 = MAX(cd->yCrit, FUDGE); - cd->h1 = Node[link->node1].invertElev + link->offset1 + cd->y1; - cd->yMid = 0.5 * (cd->y1 + cd->y2); - if (cd->yMid < FUDGE) cd->yMid = FUDGE; - w2 = getWidth(xsect, cd->y2); - wMid = getWidth(xsect, cd->yMid); - - // --- assign downstream end avg. area over full length - link->surfArea2 = (wMid + w2) / 2. * cd->length; - link->surfArea1 = 0.0; -} - -//============================================================================= - -void getDownCriticalArea(TLink *link, ConduitData *cd) -// -// Conduit surface area when downstream end has critical flow. -{ - double w1, wMid; - TXsect *xsect = &link->xsect; - - cd->y2 = MAX(cd->yCrit, FUDGE); - cd->h2 = Node[link->node2].invertElev + link->offset2 + cd->y2; - w1 = getWidth(xsect, cd->y1); - cd->yMid = 0.5 * (cd->y1 + cd->y2); - if (cd->yMid < FUDGE) cd->yMid = FUDGE; - wMid = getWidth(xsect, cd->yMid); - - // --- assign upstream end avg. surface area over full length - link->surfArea1 = (w1 + wMid) / 2. * cd->length; - link->surfArea2 = 0.0; -} + // --- compute area at each end of conduit & hyd. radius at upstream end + wSlot = getSlotWidth(xsect, y1); //(5.1.013) + a1 = getArea(xsect, y1, wSlot); //(5.1.013) + r1 = getHydRad(xsect, y1); + wSlot = getSlotWidth(xsect, y2); //(5.1.013) + a2 = getArea(xsect, y2, wSlot); //(5.1.013) -//============================================================================= - -void getUpDryArea(TLink *link, ConduitData *cd) -// -// Conduit surface area when upstream end is dry. -{ - double w1, w2, wMid; - TXsect *xsect = &link->xsect; - - cd->y1 = FUDGE; - cd->yMid = 0.5 * (cd->y1 + cd->y2); - if (cd->yMid < FUDGE) cd->yMid = FUDGE; - w1 = getWidth(xsect, cd->y1); - w2 = getWidth(xsect, cd->y2); - wMid = getWidth(xsect, cd->yMid); - - // --- assign avg. surface area of downstream half of conduit - // to the downstream node - link->surfArea2 = (wMid + w2) / 2. * cd->length / 2.; - - // --- if there is no free-fall at upstream end, assign the - // upstream node the avg. surface area of the upstream half - if (link->offset1 <= 0.0) - link->surfArea1 = (w1 + wMid) / 2. * cd->length / 2.; - else - link->surfArea1 = 0.0; -} - -//============================================================================= - -void getDownDryArea(TLink *link, ConduitData *cd) -// -// Conduit surface area when downstream end is dry. -{ - double w1, w2, wMid; - TXsect *xsect = &link->xsect; - - cd->y2 = FUDGE; - cd->yMid = 0.5 * (cd->y1 + cd->y2); - if (cd->yMid < FUDGE) cd->yMid = FUDGE; - w1 = getWidth(xsect, cd->y1); - w2 = getWidth(xsect, cd->y2); - wMid = getWidth(xsect, cd->yMid); - - // --- assign avg. surface area of upstream half of conduit - // to the upstream node - link->surfArea1 = (wMid + w1) / 2. * cd->length / 2.; - - // --- if there is no free-fall at downstream end, assign the - // downstream node the avg. surface area of the downstream half - if (link->offset2 <= 0.0) - link->surfArea2 = (w2 + wMid) / 2. * cd->length / 2.; - else - link->surfArea2 = 0.0; -} - -//============================================================================= - -void computeFlowSectionGeometry(TLink *link, ConduitData *cd) -// -// Compute flow area and hydraulic radius for flow depths at each end -// and midpoint of a conduit. -{ - double wSlot; // Preissmann slot width (ft) - TXsect *xsect = &link->xsect; - - wSlot = getSlotWidth(xsect, cd->y1); - cd->a1 = getArea(xsect, cd->y1, wSlot); - cd->r1 = getHydRad(xsect, cd->y1); - wSlot = getSlotWidth(xsect, cd->y2); - cd->a2 = getArea(xsect, cd->y2, wSlot); - - cd->yMid = 0.5 * (cd->y1 + cd->y2); - wSlot = getSlotWidth(xsect, cd->yMid); - cd->aMid = getArea(xsect, cd->yMid, wSlot); - cd->rMid = getHydRad(xsect, cd->yMid); + // --- compute area & hyd. radius at midpoint + yMid = 0.5 * (y1 + y2); + wSlot = getSlotWidth(xsect, yMid); //(5.1.013) + aMid = getArea(xsect, yMid, wSlot); //(5.1.013) + rMid = getHydRad(xsect, yMid); // --- alternate approach not currently used, but might produce better // Bernoulli energy balance for steady flows @@ -498,315 +159,451 @@ void computeFlowSectionGeometry(TLink *link, ConduitData *cd) //rMid = (r1+getHydRad(xsect,y2))/2.0; // --- check if conduit is flowing full - cd->isFull = (cd->y1 >= cd->yFull && - cd->y2 >= cd->yFull); -} - -//============================================================================= - -int conduitIsDryOrClosed(TLink *link, ConduitData *cd, double timeStep) -// -// Set flow to 0 if conduit is dry or closed. -{ - int k = link->subIndex; - - if (link->flowClass == DRY || - link->flowClass == UP_DRY || - link->flowClass == DN_DRY || - link->setting == 0.0 || - cd->aMid <= FUDGE) + if ( y1 >= xsect->yFull && + y2 >= xsect->yFull) isFull = TRUE; + + // --- set new flow to zero if conduit is dry or if flap gate is closed + if ( Link[j].flowClass == DRY || + Link[j].flowClass == UP_DRY || + Link[j].flowClass == DN_DRY || + isClosed || + aMid <= FUDGE ) { - Conduit[k].a1 = 0.5 * (cd->a1 + cd->a2); + Conduit[k].a1 = 0.5 * (a1 + a2); Conduit[k].q1 = 0.0;; Conduit[k].q2 = 0.0; - Conduit[k].fullState = 0; - link->dqdh = GRAVITY * timeStep * cd->aMid / cd->length * - Conduit[k].barrels; - link->froude = 0.0; - link->newDepth = MIN(cd->yMid, cd->yFull); - link->newVolume = Conduit[k].a1 * link_getLength(cd->linkIndex) * - Conduit[k].barrels; - link->newFlow = 0.0; - return TRUE; + Link[j].dqdh = GRAVITY * dt * aMid / length * barrels; + Link[j].froude = 0.0; + Link[j].newDepth = MIN(yMid, Link[j].xsect.yFull); + Link[j].newVolume = Conduit[k].a1 * link_getLength(j) * barrels; + Link[j].newFlow = 0.0; + return; } - return FALSE; -} - -//============================================================================= - -void applyInertialDamping(TLink *link, ConduitData *cd) -// -// Apply inertial damping factor to weight conduit's -// average area and hydraulic radius with upstream values. -{ - double rho; - double Fr; // --- compute velocity from last flow estimate - cd->velocity = cd->flow / cd->aMid; - if ( fabs(cd->velocity) > MAXVELOCITY ) - cd->velocity = MAXVELOCITY * SGN(cd->flow); + v = qLast / aMid; + if ( fabs(v) > MAXVELOCITY ) v = MAXVELOCITY * SGN(qLast); // --- compute Froude No. - Fr = link_getFroude(cd->linkIndex, cd->velocity, cd->yMid); - if (link->flowClass == SUBCRITICAL && Fr > 1.0) - link->flowClass = SUPCRITICAL; + Link[j].froude = link_getFroude(j, v, yMid); + if ( Link[j].flowClass == SUBCRITICAL && + Link[j].froude > 1.0 ) Link[j].flowClass = SUPCRITICAL; // --- find inertial damping factor (sigma) - if ( Fr <= 0.5 ) - cd->sigma = 1.0; - else if ( Fr >= 1.0 ) - cd->sigma = 0.0; - else - cd->sigma = 2.0 * (1.0 - Fr); - link->froude = Fr; + if ( Link[j].froude <= 0.5 ) sigma = 1.0; + else if ( Link[j].froude >= 1.0 ) sigma = 0.0; + else sigma = 2.0 * (1.0 - Link[j].froude); // --- get upstream-weighted area & hyd. radius based on damping factor // (modified version of R. Dickinson's slope weighting) rho = 1.0; - if ( !cd->isFull && cd->flow > 0.0 && cd->h1 >= cd->h2 ) - rho = cd->sigma; - cd->aWtd = cd->a1 + (cd->aMid - cd->a1) * rho; - cd->rWtd = cd->r1 + (cd->rMid - cd->r1) * rho; + if ( !isFull && qLast > 0.0 && h1 >= h2 ) rho = sigma; + aWtd = a1 + (aMid - a1) * rho; + rWtd = r1 + (rMid - r1) * rho; // --- determine how much inertial damping to apply - if ( InertDamping == NO_DAMPING ) - cd->sigma = 1.0; - else if ( InertDamping == FULL_DAMPING ) - cd->sigma = 0.0; + if ( InertDamping == NO_DAMPING ) sigma = 1.0; + else if ( InertDamping == FULL_DAMPING ) sigma = 0.0; // --- use full inertial damping if closed conduit is surcharged - if (cd->isFull && !xsect_isOpen(link->xsect.type)) - cd->sigma = 0.0; -} - -//============================================================================= - -double solveMomentumEqn(TLink *link, ConduitData *cd, double timeStep) -// -// Solve St. Venant momentum equation for conduit flow over a time step. -{ - int k = link->subIndex; - int linkIndex = cd->linkIndex; - double flow; // new flow value (cfs) - double dq1, dq2, dq3, dq4, dq5, dq6; // terms in momentum eqn. - double denom; - TXsect* xsect = &link->xsect; + if ( isFull && !xsect_isOpen(xsect->type) ) sigma = 0.0; + // --- compute terms of momentum eqn.: // --- 1. friction slope term - if (xsect->type == FORCE_MAIN && cd->isFull) - dq1 = timeStep * - forcemain_getFricSlope(linkIndex, fabs(cd->velocity), cd->rMid); - else - dq1 = timeStep * Conduit[k].roughFactor / pow(cd->rWtd, 1.33333) * - fabs(cd->velocity); + if ( xsect->type == FORCE_MAIN && isFull ) + dq1 = dt * forcemain_getFricSlope(j, fabs(v), rMid); + else dq1 = dt * Conduit[k].roughFactor / pow(rWtd, 1.33333) * fabs(v); // --- 2. energy slope term - dq2 = timeStep * GRAVITY * cd->aWtd * (cd->h2 - cd->h1) / cd->length; + dq2 = dt * GRAVITY * aWtd * (h2 - h1) / length; // --- 3 & 4. inertial terms dq3 = 0.0; dq4 = 0.0; - if (cd->sigma > 0.0) + if ( sigma > 0.0 ) { - dq3 = 2.0 * cd->velocity * (cd->aMid - cd->aOld) * cd->sigma; - dq4 = timeStep * cd->velocity * cd->velocity * - (cd->a2 - cd->a1) / cd->length * cd->sigma; + dq3 = 2.0 * v * (aMid - aOld) * sigma; + dq4 = dt * v * v * (a2 - a1) / length * sigma; } // --- 5. local losses term dq5 = 0.0; - if (Conduit[k].hasLosses) - dq5 = findLocalLosses(link, cd) / 2.0 / cd->length * timeStep; + if ( Conduit[k].hasLosses ) + { + dq5 = findLocalLosses(j, a1, a2, aMid, qLast) / 2.0 / length * dt; + } // --- 6. term for evap and seepage losses per unit length - dq6 = link_getLossRate(linkIndex, cd->flow) * 2.5 * timeStep * - cd->velocity / link_getLength(linkIndex); + dq6 = link_getLossRate(j, qLast) * 2.5 * dt * v / link_getLength(j); //(5.1.014) // --- combine terms to find new conduit flow denom = 1.0 + dq1 + dq5; - flow = link->oldFlow / Conduit[k].barrels; - flow = (flow - dq2 + dq3 + dq4 + dq6) / denom; + q = (qOld - dq2 + dq3 + dq4 + dq6) / denom; //(5.1.013) // --- compute derivative of flow w.r.t. head - link->dqdh = 1.0 / denom * GRAVITY * timeStep * cd->aWtd / cd->length * - Conduit[k].barrels; - return flow; -} + Link[j].dqdh = 1.0 / denom * GRAVITY * dt * aWtd / length * barrels; -//============================================================================= + // --- check if any flow limitation applies + Link[j].inletControl = FALSE; + Link[j].normalFlow = FALSE; + if ( q > 0.0 ) + { + // --- check for inlet controlled culvert flow + if ( xsect->culvertCode > 0 && !isFull ) + q = culvert_getInflow(j, q, h1); -double findLocalLosses(TLink *link, ConduitData* cd) -// -// Compute local losses term of a conduit's momentum equation. -{ - double losses = 0.0; - double flow = fabs(cd->flow); + // --- check for normal flow limitation based on surface slope & Fr + else + if ( y1 < Link[j].xsect.yFull && + ( Link[j].flowClass == SUBCRITICAL || + Link[j].flowClass == SUPCRITICAL ) + ) q = checkNormalFlow(j, q, y1, y2, a1, r1); + } - if ( cd->a1 > FUDGE ) losses += link->cLossInlet * (flow/cd->a1); - if ( cd->a2 > FUDGE ) losses += link->cLossOutlet * (flow/cd->a2); - if ( cd->aMid > FUDGE ) losses += link->cLossAvg * (flow/cd->aMid); - return losses; -} + // --- apply under-relaxation weighting between new & old flows; + // --- do not allow change in flow direction without first being zero + if ( steps > 0 ) + { + q = (1.0 - omega) * qLast + omega * q; + if ( q * qLast < 0.0 ) q = 0.001 * SGN(q); + } -//============================================================================= + // --- check if user-supplied flow limit applies + if ( Link[j].qLimit > 0.0 ) + { + if ( fabs(q) > Link[j].qLimit ) q = SGN(q) * Link[j].qLimit; + } -double checkForCulvertInletControl(TLink *link, ConduitData *cd, double flow) -// -// Check if conduit flow is subject to culvert inlet control. -{ - link->inletControl = FALSE; - if (flow > 0.0 && link->xsect.culvertCode > 0 && !cd->isFull) - flow = culvert_getInflow(cd->linkIndex, flow, cd->h1); - return flow; + // --- check for reverse flow with closed flap gate + if ( link_setFlapGate(j, n1, n2, q) ) q = 0.0; + + // --- do not allow flow out of a dry node + // (as suggested by R. Dickinson) + if( q > FUDGE && Node[n1].newDepth <= FUDGE ) q = FUDGE; + if( q < -FUDGE && Node[n2].newDepth <= FUDGE ) q = -FUDGE; + + // --- save new values of area, flow, depth, & volume + Conduit[k].a1 = aMid; + Conduit[k].q1 = q; + Conduit[k].q2 = q; + Link[j].newDepth = MIN(yMid, xsect->yFull); + aMid = (a1 + a2) / 2.0; +// aMid = MIN(aMid, xsect->aFull); //Slot can have aMid > aFull //(5.1.013) + Conduit[k].fullState = link_getFullState(a1, a2, xsect->aFull); + Link[j].newVolume = aMid * link_getLength(j) * barrels; + Link[j].newFlow = q * barrels; } //============================================================================= -double checkForNormalFlowControl(TLink *link, ConduitData *cd, double flow) +int getFlowClass(int j, double q, double h1, double h2, double y1, double y2, + double *yC, double *yN, double* fasnh) +// +// Input: j = conduit link index +// q = current conduit flow (cfs) +// h1 = head at upstream end of conduit (ft) +// h2 = head at downstream end of conduit (ft) +// y1 = upstream flow depth in conduit (ft) +// y2 = downstream flow depth in conduit (ft) +// yC = critical flow depth (ft) +// yN = normal flow depth (ft) +// fasnh = fraction between norm. & crit. depth +// Output: returns flow classification code +// Purpose: determines flow class for a conduit based on depths at each end. // -// Check if conduit flow should be reduced to normal Manning flow value. { - int hasOutfall; + int n1, n2; // indexes of upstrm/downstrm nodes + int flowClass; // flow classification code + double ycMin, ycMax; // min/max critical depths (ft) + double z1, z2; // offsets of conduit inverts (ft) - link->normalFlow = FALSE; - if (link->inletControl || cd->isFull) - return flow; - if (link->flowClass == SUBCRITICAL || link->flowClass == SUPCRITICAL) - { - hasOutfall = (Node[link->node1].type == OUTFALL || - Node[link->node2].type == OUTFALL); - if (hasSlopeBasedNormalFlow(cd, hasOutfall) || - hasFroudeBasedNormalFlow(cd, hasOutfall, flow)) - flow = checkNormalFlowValue(link, cd, flow); - } - return flow; -} + // --- get upstream & downstream node indexes + n1 = Link[j].node1; + n2 = Link[j].node2; -//============================================================================= + // --- get upstream & downstream conduit invert offsets + z1 = Link[j].offset1; + z2 = Link[j].offset2; -int hasSlopeBasedNormalFlow(ConduitData *cd, int hasOutfall) -// -// Check if upstream flow depth is lower than downstream depth. -{ - if (NormalFlowLtd == SLOPE || NormalFlowLtd == BOTH || hasOutfall) - return (cd->y1 < cd->y2); - return FALSE; -} + // --- base offset of an outfall conduit on outfall's depth + if ( Node[n1].type == OUTFALL ) z1 = MAX(0.0, (z1 - Node[n1].newDepth)); + if ( Node[n2].type == OUTFALL ) z2 = MAX(0.0, (z2 - Node[n2].newDepth)); -//============================================================================= + // --- default class is SUBCRITICAL + flowClass = SUBCRITICAL; + *fasnh = 1.0; -int hasFroudeBasedNormalFlow(ConduitData *cd, int hasOutfall, double flow) -// -// Check if Froude number at upstream end of conduit is >= 1. -{ - if ( (NormalFlowLtd == FROUDE || NormalFlowLtd == BOTH) && !hasOutfall) + // --- case where both ends of conduit are wet + if ( y1 > FUDGE && y2 > FUDGE ) { - if (link_getFroude(cd->linkIndex, flow / cd->a1, cd->y1) >= 1.0) - return TRUE; + if ( q < 0.0 ) + { + // --- upstream end at critical depth if flow depth is + // below conduit's critical depth and an upstream + // conduit offset exists + if ( z1 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + ycMin = MIN(*yN, *yC); + if ( y1 < ycMin ) flowClass = UP_CRITICAL; + } + } + + // --- case of normal direction flow + else + { + // --- downstream end at smaller of critical and normal depth + // if downstream flow depth below this and a downstream + // conduit offset exists + if ( z2 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + ycMin = MIN(*yN, *yC); + ycMax = MAX(*yN, *yC); + if ( y2 < ycMin ) flowClass = DN_CRITICAL; + else if ( y2 < ycMax ) + { + if ( ycMax - ycMin < FUDGE ) *fasnh = 0.0; + else *fasnh = (ycMax - y2) / (ycMax - ycMin); + } + } + } } - return FALSE; -} -//============================================================================= + // --- case where no flow at either end of conduit + else if ( y1 <= FUDGE && y2 <= FUDGE ) flowClass = DRY; -double checkNormalFlowValue(TLink *link, ConduitData *cd, double flow) -// -// Return smaller of normal Manning flow and current dynamic wave flow. -{ - int k = link->subIndex; - double normalFlow = Conduit[k].beta * cd->a1 * pow(cd->r1, 2./3.); + // --- case where downstream end of pipe is wet, upstream dry + else if ( y2 > FUDGE ) + { + // --- flow classification is UP_DRY if downstream head < + // invert of upstream end of conduit + if ( h2 < Node[n1].invertElev + Link[j].offset1 ) flowClass = UP_DRY; + + // --- otherwise, the downstream head will be >= upstream + // conduit invert creating a flow reversal and upstream end + // should be at critical depth, providing that an upstream + // offset exists (otherwise subcritical condition is maintained) + else if ( z1 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + flowClass = UP_CRITICAL; + } + } - if ( normalFlow < flow ) + // --- case where upstream end of pipe is wet, downstream dry + else { - link->normalFlow = TRUE; - flow = normalFlow; + // --- flow classification is DN_DRY if upstream head < + // invert of downstream end of conduit + if ( h1 < Node[n2].invertElev + Link[j].offset2 ) flowClass = DN_DRY; + + // --- otherwise flow at downstream end should be at critical depth + // providing that a downstream offset exists (otherwise + // subcritical condition is maintained) + else if ( z2 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + flowClass = DN_CRITICAL; + } } - return flow; + return flowClass; } //============================================================================= -double applyUnderRelaxation(ConduitData *cd, double omega, double flow) -// -// Weight current flow estimate with previous estimate. -{ - flow = (1.0 - omega) * cd->flow + omega * flow; +void findSurfArea(int j, double q, double length, double* h1, double* h2, + double* y1, double* y2) +// +// Input: j = conduit link index +// q = current conduit flow (cfs) +// length = conduit length (ft) +// h1 = head at upstream end of conduit (ft) +// h2 = head at downstream end of conduit (ft) +// y1 = upstream flow depth (ft) +// y2 = downstream flow depth (ft) +// Output: updated values of h1, h2, y1, & y2; +// Purpose: assigns surface area of conduit to its up and downstream nodes. +// +{ + int n1, n2; // indexes of upstrm/downstrm nodes + double flowDepth1; // flow depth at upstrm end (ft) + double flowDepth2; // flow depth at downstrm end (ft) + double flowDepthMid; // flow depth at midpt. (ft) + double width1; // top width at upstrm end (ft) + double width2; // top width at downstrm end (ft) + double widthMid; // top width at midpt. (ft) + double surfArea1 = 0.0; // surface area at upstream node (ft2) + double surfArea2 = 0.0; // surface area st downstrm node (ft2) + double criticalDepth; // critical flow depth (ft) + double normalDepth; // normal flow depth (ft) + double fullDepth; // full depth (ft) //(5.1.013) + double fasnh = 1.0; // fraction between norm. & crit. depth //(5.1.013) + TXsect* xsect = &Link[j].xsect; // pointer to cross-section data + + // --- get node indexes & current flow depths + n1 = Link[j].node1; + n2 = Link[j].node2; + flowDepth1 = *y1; + flowDepth2 = *y2; + + normalDepth = (flowDepth1 + flowDepth2) / 2.0; + criticalDepth = normalDepth; + +//// Following code segment modified for release 5.1.013. //// //(5.1.013) + // --- find conduit's flow classification + fullDepth = xsect->yFull; + if (flowDepth1 >= fullDepth && flowDepth2 >= fullDepth) + { + Link[j].flowClass = SUBCRITICAL; + } + else Link[j].flowClass = getFlowClass(j, q, *h1, *h2, *y1, *y2, + &criticalDepth, &normalDepth, &fasnh); +/////////////////////////////////////////////////////////////// - // --- flow can't switch sign without first being close to 0 - if (flow * cd->flow < 0.0) flow = 0.001 * SGN(flow); - return flow; -} + // --- add conduit's surface area to its end nodes depending on flow class + switch ( Link[j].flowClass ) + { + case SUBCRITICAL: + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width1 = getWidth(xsect, flowDepth1); + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + surfArea1 = (width1 + widthMid) * length / 4.; + surfArea2 = (widthMid + width2) * length / 4. * fasnh; + break; -//============================================================================= + case UP_CRITICAL: + flowDepth1 = criticalDepth; + if ( normalDepth < criticalDepth ) flowDepth1 = normalDepth; + flowDepth1 = MAX(flowDepth1, FUDGE); + *h1 = Node[n1].invertElev + Link[j].offset1 + flowDepth1; + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + surfArea2 = (widthMid + width2) * length * 0.5; + break; -double checkImposedFlowLimits(TLink *link, ConduitData *cd, double flow) -// -// Perform additional checks that limit a conduit's flow. -{ - int n1 = link->node1; - int n2 = link->node2; + case DN_CRITICAL: + flowDepth2 = criticalDepth; + if ( normalDepth < criticalDepth ) flowDepth2 = normalDepth; + flowDepth2 = MAX(flowDepth2, FUDGE); + *h2 = Node[n2].invertElev + Link[j].offset2 + flowDepth2; + width1 = getWidth(xsect, flowDepth1); + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + widthMid = getWidth(xsect, flowDepthMid); + surfArea1 = (width1 + widthMid) * length * 0.5; + break; - // --- check if user-supplied flow limit applies - if (link->qLimit > 0.0) - { - if (fabs(flow) > link->qLimit) flow = SGN(flow) * link->qLimit; - } + case UP_DRY: + flowDepth1 = FUDGE; + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width1 = getWidth(xsect, flowDepth1); + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + + // --- assign avg. surface area of downstream half of conduit + // to the downstream node + surfArea2 = (widthMid + width2) * length / 4.; + + // --- if there is no free-fall at upstream end, assign the + // upstream node the avg. surface area of the upstream half + if ( Link[j].offset1 <= 0.0 ) + { + surfArea1 = (width1 + widthMid) * length / 4.; + } + break; - // --- check for reverse flow with closed flap gate - if (link_setFlapGate(cd->linkIndex, n1, n2, flow)) flow = 0.0; + case DN_DRY: + flowDepth2 = FUDGE; + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width1 = getWidth(xsect, flowDepth1); + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + + // --- assign avg. surface area of upstream half of conduit + // to the upstream node + surfArea1 = (widthMid + width1) * length / 4.; + + // --- if there is no free-fall at downstream end, assign the + // downstream node the avg. surface area of the downstream half + if ( Link[j].offset2 <= 0.0 ) + { + surfArea2 = (width2 + widthMid) * length / 4.; + } + break; - // --- do not allow flow out of a dry node - // (as suggested by R. Dickinson) - if (flow > FUDGE && Node[n1].newDepth <= FUDGE) - flow = FUDGE; - if (flow < -FUDGE && Node[n2].newDepth <= FUDGE) - flow = -FUDGE; - return flow; + case DRY: + surfArea1 = FUDGE * length / 2.0; + surfArea2 = surfArea1; + break; + } + Link[j].surfArea1 = surfArea1; + Link[j].surfArea2 = surfArea2; + *y1 = flowDepth1; + *y2 = flowDepth2; } //============================================================================= -void saveFlowResult(TLink *link, ConduitData *cd, double flow) +double findLocalLosses(int j, double a1, double a2, double aMid, double q) +// +// Input: j = link index +// a1 = upstream area (ft2) +// a2 = downstream area (ft2) +// aMid = midpoint area (ft2) +// q = flow rate (cfs) +// Output: returns local losses (ft/sec) +// Purpose: computes local losses term of momentum equation. +// { - int k = link->subIndex; - double aFull = link->xsect.aFull; - double aAvg = (cd->a1 + cd->a2) / 2.0; - - Conduit[k].a1 = cd->aMid; - Conduit[k].q1 = flow; - Conduit[k].q2 = flow; - Conduit[k].fullState = link_getFullState(cd->a1, cd->a2, aFull); - link->newDepth = MIN(cd->yMid, cd->yFull); - link->newVolume = aAvg * link_getLength(cd->linkIndex) * Conduit[k].barrels; - link->newFlow = flow * Conduit[k].barrels; + double losses = 0.0; + q = fabs(q); + if ( a1 > FUDGE ) losses += Link[j].cLossInlet * (q/a1); + if ( a2 > FUDGE ) losses += Link[j].cLossOutlet * (q/a2); + if ( aMid > FUDGE ) losses += Link[j].cLossAvg * (q/aMid); + return losses; } //============================================================================= +//// New function added to release 5.1.013. //// //(5.1.013) + double getSlotWidth(TXsect* xsect, double y) -// -// Compute width of Preissmann slot atop a conduit at surcharged depth y. { double yNorm = y / xsect->yFull; - // --- check if slot is needed + // --- return 0.0 if slot surcharge method not used if (SurchargeMethod != SLOT || xsect_isOpen(xsect->type) || - yNorm < CrownCutoff) - return 0.0; + yNorm < CrownCutoff) return 0.0; // --- for depth > 1.78 * pipe depth, slot width = 1% of max. width - if (yNorm > 1.78) return xsect->wMax * 0.01; + if (yNorm > 1.78) return 0.01 * xsect->wMax; // --- otherwise use the Sjoberg formula - return xsect->wMax * exp(-pow(yNorm, 2.4)) * 0.5423; + return xsect->wMax * 0.5423 * exp(-pow(yNorm, 2.4)); } //============================================================================= +//// This function was re-written for release 5.1.013. //// //(5.1.013) + double getWidth(TXsect* xsect, double y) // -// Compute top width of conduit cross section (xsect) at flow depth y. +// Input: xsect = ptr. to conduit cross section +// y = flow depth (ft) +// Output: returns top width (ft) +// Purpose: computes top width of flow surface in conduit. +// { double wSlot = getSlotWidth(xsect, y); if (wSlot > 0.0) return wSlot; @@ -817,21 +614,85 @@ double getWidth(TXsect* xsect, double y) //============================================================================= +//// This function was re-written for release 5.1.013. //// //(5.1.013) + double getArea(TXsect* xsect, double y, double wSlot) // -// Compute area of conduit cross-section (xsect) at flow depth y. +// Input: xsect = ptr. to conduit cross section +// y = flow depth (ft) +// Output: returns flow area (ft2) +// Purpose: computes area of flow cross-section in a conduit. +// { - if ( y >= xsect->yFull ) - return xsect->aFull + (y - xsect->yFull) * wSlot; + if ( y >= xsect->yFull ) return xsect->aFull + (y - xsect->yFull) * wSlot; return xsect_getAofY(xsect, y); } //============================================================================= +//// This function was re-written for release 5.1.013. //// //(5.1.013) + double getHydRad(TXsect* xsect, double y) // -// Compute hydraulic radius of conduit cross-section (xsect) at flow depth y. +// Input: xsect = ptr. to conduit cross section +// y = flow depth (ft) +// Output: returns hydraulic radius (ft) +// Purpose: computes hydraulic radius of flow cross-section in a conduit. +// { if (y >= xsect->yFull) return xsect->rFull; return xsect_getRofY(xsect, y); } + +//============================================================================= + +double checkNormalFlow(int j, double q, double y1, double y2, double a1, + double r1) +// +// Input: j = link index +// q = link flow found from dynamic wave equations (cfs) +// y1 = flow depth at upstream end (ft) +// y2 = flow depth at downstream end (ft) +// a1 = flow area at upstream end (ft2) +// r1 = hyd. radius at upstream end (ft) +// Output: returns modifed flow in link (cfs) +// Purpose: checks if flow in link should be replaced by normal flow. +// +{ + int check = FALSE; + int k = Link[j].subIndex; + int n1 = Link[j].node1; + int n2 = Link[j].node2; + int hasOutfall = (Node[n1].type == OUTFALL || Node[n2].type == OUTFALL); + double qNorm; + double f1; + + // --- check if water surface slope < conduit slope + if ( NormalFlowLtd == SLOPE || NormalFlowLtd == BOTH || hasOutfall ) + { + if ( y1 < y2 ) check = TRUE; + } + + // --- check if Fr >= 1.0 at upstream end of conduit + if ( !check && (NormalFlowLtd == FROUDE || NormalFlowLtd == BOTH) && + !hasOutfall ) + { + if ( y1 > FUDGE && y2 > FUDGE ) + { + f1 = link_getFroude(j, q/a1, y1); + if ( f1 >= 1.0 ) check = TRUE; + } + } + + // --- check if normal flow < dynamic flow + if ( check ) + { + qNorm = Conduit[k].beta * a1 * pow(r1, 2./3.); + if ( qNorm < q ) + { + Link[j].normalFlow = TRUE; + return qNorm; + } + } + return q; +} From 9b3066287ff6244276403f44edc20387f5ca8953 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Tue, 7 Jul 2020 10:21:50 -0400 Subject: [PATCH 156/266] Avoid compiler warning --- src/solver/swmm5.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/swmm5.c b/src/solver/swmm5.c index ab4ae2c1e..1ddf9f77c 100644 --- a/src/solver/swmm5.c +++ b/src/solver/swmm5.c @@ -820,7 +820,7 @@ void writecon(char *s) // Purpose: writes string of characters to the console. // { - fprintf(stdout,s); + fprintf(stdout,"%s",s); fflush(stdout); } From 2229d536b4ae60940a750f285039fc66fc433a18 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Wed, 8 Jul 2020 08:46:57 -0400 Subject: [PATCH 157/266] Avoid compiler warning GCC warns about using strcmp without including string.h. --- src/run/main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/run/main.c b/src/run/main.c index b4af1f112..15f272b9e 100644 --- a/src/run/main.c +++ b/src/run/main.c @@ -9,6 +9,7 @@ // Main stub for the command line version of EPA SWMM 5.1 // to be run with swmm5.dll. +#include #include #include #include "swmm5.h" @@ -100,4 +101,4 @@ int main(int argc, char *argv[]) */ return 0; -} \ No newline at end of file +} From 3caecaa2b603e11483bf18840c7aa083cf909035 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 9 Jul 2020 17:38:26 -0400 Subject: [PATCH 158/266] Adds include guards to headers --- src/solver/consts.h | 13 ++++++++--- src/solver/datetime.h | 7 ++++++ src/solver/enums.h | 27 ++++++++++++++-------- src/solver/error.h | 15 ++++++++---- src/solver/exfil.h | 4 +++- src/solver/findroot.h | 8 +++++++ src/solver/funcs.h | 24 ++++++++++++------- src/solver/globals.h | 9 +++++++- src/solver/hash.h | 7 ++++++ src/solver/headers.h | 1 + src/solver/include/swmm5.h | 12 ++++++---- src/solver/infil.h | 4 +++- src/solver/keywords.h | 7 ++++++ src/solver/lid.h | 16 +++++++------ src/solver/macros.h | 7 ++++++ src/solver/mathexpr.h | 7 ++++++ src/solver/mempool.h | 7 ++++++ src/solver/objects.h | 47 ++++++++++++++++++++++---------------- src/solver/odesolve.h | 7 ++++++ src/solver/text.h | 9 +++++++- 20 files changed, 177 insertions(+), 61 deletions(-) diff --git a/src/solver/consts.h b/src/solver/consts.h index 6217bb4bd..829b089fd 100644 --- a/src/solver/consts.h +++ b/src/solver/consts.h @@ -13,11 +13,15 @@ // Various Constants //----------------------------------------------------------------------------- +#ifndef CONSTS_H +#define CONSTS_H + + //------------------ // General Constants //------------------ -#define VERSION 51015 +#define VERSION 51015 #define MAGICNUMBER 516114522 #define EOFMARK 0x1A // Use 0x04 for UNIX systems #define MAXTITLE 3 // Max. # title lines @@ -94,5 +98,8 @@ //--------------------------- // Token separator characters -//--------------------------- -#define SEPSTR " \t\n\r" +//--------------------------- +#define SEPSTR " \t\n\r" + + +#endif //CONSTS_H diff --git a/src/solver/datetime.h b/src/solver/datetime.h index 397f3180c..8c84b3b79 100644 --- a/src/solver/datetime.h +++ b/src/solver/datetime.h @@ -18,6 +18,10 @@ // - New getTimeStamp function added. //----------------------------------------------------------------------------- +#ifndef DATETIME_H +#define DATETIME_H + + typedef double DateTime; #define Y_M_D 0 @@ -61,3 +65,6 @@ void datetime_setDateFormat(int fmt); DateTime datetime_addSeconds(DateTime date1, double seconds); DateTime datetime_addDays(DateTime date1, DateTime date2); long datetime_timeDiff(DateTime date1, DateTime date2); + + +#endif //DATETIME_H diff --git a/src/solver/enums.h b/src/solver/enums.h index e76f27519..4bc50e3f8 100644 --- a/src/solver/enums.h +++ b/src/solver/enums.h @@ -35,10 +35,14 @@ // // Build 5.1.013: // - SURCHARGE_METHOD and RULE_STEP options added. -// - WEIR_CURVE added as a curve type. +// - WEIR_CURVE added as a curve type. // //----------------------------------------------------------------------------- +#ifndef ENUMS_H +#define ENUMS_H + + //------------------------------------- // Names of major object types //------------------------------------- @@ -113,18 +117,18 @@ // Cross section shape types //------------------------------------- enum XsectType { - DUMMY, // 0 + DUMMY, // 0 CIRCULAR, // 1 closed FILLED_CIRCULAR, // 2 closed RECT_CLOSED, // 3 closed - RECT_OPEN, // 4 - TRAPEZOIDAL, // 5 - TRIANGULAR, // 6 + RECT_OPEN, // 4 + TRAPEZOIDAL, // 5 + TRIANGULAR, // 6 PARABOLIC, // 7 - POWERFUNC, // 8 - RECT_TRIANG, // 9 + POWERFUNC, // 8 + RECT_TRIANG, // 9 RECT_ROUND, // 10 - MOD_BASKET, // 11 + MOD_BASKET, // 11 HORIZ_ELLIPSE, // 12 closed VERT_ELLIPSE, // 13 closed ARCH, // 14 closed @@ -193,7 +197,7 @@ //------------------------------------- // Computed node quantities //------------------------------------- - #define MAX_NODE_RESULTS 7 + #define MAX_NODE_RESULTS 7 enum NodeResultType { NODE_DEPTH, // water depth above invert NODE_HEAD, // hydraulic head @@ -410,7 +414,7 @@ enum CompatibilityType { enum PumpCurveType { TYPE1_PUMP, // flow varies stepwise with wet well volume - TYPE2_PUMP, // flow varies stepwise with inlet depth + TYPE2_PUMP, // flow varies stepwise with inlet depth TYPE3_PUMP, // flow varies with head delivered TYPE4_PUMP, // flow varies with inlet depth IDEAL_PUMP}; // outflow equals inflow @@ -479,3 +483,6 @@ enum NoneAllType { NONE, ALL, SOME}; + + +#endif //ENUMS_H diff --git a/src/solver/error.h b/src/solver/error.h index 8ce6cc07d..20737960c 100644 --- a/src/solver/error.h +++ b/src/solver/error.h @@ -11,6 +11,10 @@ // //----------------------------------------------------------------------------- +#ifndef ERROR_H +#define ERROR_H + + enum ErrorType { //... Runtime Errors @@ -117,7 +121,7 @@ enum ErrorType { ERR_RAIN_FILE_SCRATCH, //313 72 ERR_RAIN_FILE_OPEN, //315 73 ERR_RAIN_FILE_DATA, //317 74 - ERR_RAIN_FILE_SEQUENCE, //318 75 + ERR_RAIN_FILE_SEQUENCE, //318 75 ERR_RAIN_FILE_FORMAT, //319 76 ERR_RAIN_IFACE_FORMAT, //320 77 ERR_RAIN_FILE_GAGE, //321 78 @@ -144,7 +148,7 @@ enum ErrorType { ERR_RDII_FILE_SCRATCH, //341 91 ERR_RDII_FILE_OPEN, //343 92 ERR_RDII_FILE_FORMAT, //345 93 - + //... Routing File Errors ERR_ROUTING_FILE_OPEN, //351 94 ERR_ROUTING_FILE_FORMAT, //353 95 @@ -166,7 +170,7 @@ enum ErrorType { ERR_API_INPUTNOTOPEN, //502 105 ERR_API_SIM_NRUNNING, //503 106 ERR_API_WRONG_TYPE, //504 107 - ERR_API_OBJECT_INDEX, //505 108 + ERR_API_OBJECT_INDEX, //505 108 ERR_API_POLLUT_INDEX, //506 109 ERR_API_INFLOWTYPE, //507 110 ERR_API_TSERIES_INDEX, //508 111 @@ -175,7 +179,10 @@ enum ErrorType { //... Additional Errors ERR_STORAGE_VOLUME, //140 113 //(5.1.015) MAXERRMSG}; - + char* error_getMsg(int i); int error_getCode(int i); int error_setInpError(int errcode, char* s); + + +#endif //ERROR_H diff --git a/src/solver/exfil.h b/src/solver/exfil.h index 6958735cc..8315c08c4 100644 --- a/src/solver/exfil.h +++ b/src/solver/exfil.h @@ -12,6 +12,7 @@ #ifndef EXFIL_H #define EXFIL_H + //---------------------------- // EXFILTRATION OBJECT //---------------------------- @@ -32,4 +33,5 @@ int exfil_readStorageParams(int k, char* tok[], int ntoks, int n); void exfil_initState(int k); double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area); -#endif + +#endif //EXFIL_H diff --git a/src/solver/findroot.h b/src/solver/findroot.h index 253a4ba4e..c0ae10dff 100644 --- a/src/solver/findroot.h +++ b/src/solver/findroot.h @@ -5,8 +5,16 @@ // // Last modified on 11/19/13. //----------------------------------------------------------------------------- + +#ifndef FINDROOT_H +#define FINDROOT_H + + int findroot_Newton(double x1, double x2, double* rts, double xacc, void (*func) (double x, double* f, double* df, void* p), void* p); double findroot_Ridder(double x1, double x2, double xacc, double (*func)(double, void* p), void* p); + + +#endif //FINDROOT_H diff --git a/src/solver/funcs.h b/src/solver/funcs.h index bdc2a4fe0..b9da07048 100644 --- a/src/solver/funcs.h +++ b/src/solver/funcs.h @@ -32,6 +32,11 @@ // - Arguments to link_getLossRate function changed. // //----------------------------------------------------------------------------- + +#ifndef FUNCS_H +#define FUNCS_H + + void project_open(char *f1, char *f2, char *f3); void project_close(void); @@ -86,7 +91,7 @@ void report_writeSysStats(TSysStats* sysStats); void report_writeErrorMsg(int code, char* msg); void report_writeErrorCode(void); void report_writeInputErrorMsg(int k, int sect, char* line, long lineCount); -void report_writeWarningMsg(char* msg, char* id); +void report_writeWarningMsg(char* msg, char* id); void report_writeTseriesErrorMsg(int code, TTable *tseries); void inputrpt_writeInput(void); @@ -275,7 +280,7 @@ void stats_report(void); void stats_updateCriticalTimeCount(int node, int link); void stats_updateFlowStats(double tStep, DateTime aDate, int stepCount, int steadyState); -void stats_updateSubcatchStats(int subcatch, double rainVol, +void stats_updateSubcatchStats(int subcatch, double rainVol, double runonVol, double evapVol, double infilVol, double impervVol, double pervVol, double runoffVol, double runoff); //(5.1.013) void stats_updateGwaterStats(int j, double infil, double evap, @@ -356,12 +361,12 @@ void node_getResults(int node, double wt, float x[]); int inflow_readExtInflow(char* tok[], int ntoks); int inflow_readDwfInflow(char* tok[], int ntoks); int inflow_readDwfPattern(char* tok[], int ntoks); -int inflow_setExtInflow(int j, int param, int type, - int tSeries, int basePat, double cf, +int inflow_setExtInflow(int j, int param, int type, + int tSeries, int basePat, double cf, double baseline, double sf); -int inflow_validate(int param, int type, int tSeries, - int basePat, double *cf); - +int inflow_validate(int param, int type, int tSeries, + int basePat, double *cf); + void inflow_initDwfInflow(TDwfInflow* inflow); void inflow_initDwfPattern(int pattern); @@ -471,7 +476,7 @@ int shape_validate(TShape *shape, TTable *curve); int controls_create(int n); void controls_delete(void); int controls_addRuleClause(int rule, int keyword, char* Tok[], int nTokens); -int controls_evaluate(DateTime currentTime, DateTime elapsedTime, +int controls_evaluate(DateTime currentTime, DateTime elapsedTime, double tStep); //----------------------------------------------------------------------------- @@ -518,3 +523,6 @@ void writecon(char *s); // writes string to console DateTime getDateTime(double elapsedMsec); // convert elapsed time to date void getElapsedTime(DateTime aDate, // convert elapsed date int* days, int* hrs, int* mins); + + +#endif //FUNCS_H diff --git a/src/solver/globals.h b/src/solver/globals.h index b9f110cdb..400f35b9b 100644 --- a/src/solver/globals.h +++ b/src/solver/globals.h @@ -27,7 +27,7 @@ // // Build 5.1.011: // - Changed WarningCode to Warnings (# warnings issued) -// - Added error message text as a variable. +// - Added error message text as a variable. // - Added elapsed simulation time (in decimal days) variable. // - Added variables associated with detailed routing events. // @@ -41,6 +41,10 @@ // - Fixes bug in summary statistics when Report Start date > Start Date. //----------------------------------------------------------------------------- +#ifndef GLOBALS_H +#define GLOBALS_H + + EXTERN TFile Finp, // Input file Fout, // Output file @@ -171,3 +175,6 @@ EXTERN TTable* Tseries; // Array of time series tables EXTERN TTransect* Transect; // Array of transect data EXTERN TShape* Shape; // Array of custom conduit shapes EXTERN TEvent* Event; // Array of routing events + + +#endif //GLOBALS_H diff --git a/src/solver/hash.h b/src/solver/hash.h index 26f1684fa..90ac1ca22 100644 --- a/src/solver/hash.h +++ b/src/solver/hash.h @@ -4,6 +4,10 @@ // Header file for Hash Table module hash.c. //----------------------------------------------------------------------------- +#ifndef HASH_H +#define HASH_H + + #define HTMAXSIZE 1999 #define NOTFOUND -1 @@ -21,3 +25,6 @@ int HTinsert(HTtable *, char *, int); int HTfind(HTtable *, char *); char *HTfindKey(HTtable *, char *); void HTfree(HTtable *); + + +#endif //HASH_H diff --git a/src/solver/headers.h b/src/solver/headers.h index 5f76a8a6e..eafda8a2c 100644 --- a/src/solver/headers.h +++ b/src/solver/headers.h @@ -10,6 +10,7 @@ // // DO NOT CHANGE THE ORDER OF THE #INCLUDE STATEMENTS //----------------------------------------------------------------------------- + #include #include "consts.h" #include "macros.h" diff --git a/src/solver/include/swmm5.h b/src/solver/include/swmm5.h index f2eaf9872..2d717bbc2 100644 --- a/src/solver/include/swmm5.h +++ b/src/solver/include/swmm5.h @@ -14,6 +14,7 @@ #ifndef SWMM5_H #define SWMM5_H + // --- define WINDOWS #undef WINDOWS @@ -35,8 +36,8 @@ // --- use "C" linkage for C++ programs #ifdef __cplusplus -extern "C" { -#endif +extern "C" { +#endif int DLLEXPORT swmm_run(char* f1, char* f2, char* f3); int DLLEXPORT swmm_open(char* f1, char* f2, char* f3); @@ -51,8 +52,9 @@ int DLLEXPORT swmm_getVersion(void); int DLLEXPORT swmm_getError(char* errMsg, int msgLen); int DLLEXPORT swmm_getWarnings(void); -#ifdef __cplusplus -} // matches the linkage specification from above */ +#ifdef __cplusplus +} // matches the linkage specification from above */ #endif -#endif + +#endif //SWMM5_H diff --git a/src/solver/infil.h b/src/solver/infil.h index 3aff6f2f4..c4923e88d 100644 --- a/src/solver/infil.h +++ b/src/solver/infil.h @@ -25,6 +25,7 @@ #ifndef INFIL_H #define INFIL_H + //--------------------- // Enumerated Constants //--------------------- @@ -113,4 +114,5 @@ void grnampt_initState(TGrnAmpt *infil); double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, double depth, int modelType); -#endif + +#endif //INFIL_H diff --git a/src/solver/keywords.h b/src/solver/keywords.h index 3724a0c89..af89e0d58 100644 --- a/src/solver/keywords.h +++ b/src/solver/keywords.h @@ -17,6 +17,10 @@ // - New keyword array defined for surcharge method. //----------------------------------------------------------------------------- +#ifndef KEYWORDS_H +#define KEYWORDS_H + + extern char* BuildupTypeWords[]; extern char* CurveTypeWords[]; extern char* DividerTypeWords[]; @@ -65,3 +69,6 @@ extern char* VolUnitsWords2[]; extern char* WashoffTypeWords[]; extern char* WeirTypeWords[]; extern char* XsectTypeWords[]; + + +#endif //KEYWORDS_H diff --git a/src/solver/lid.h b/src/solver/lid.h index 93ee2b779..146b58e2a 100644 --- a/src/solver/lid.h +++ b/src/solver/lid.h @@ -18,7 +18,7 @@ // - Detailed LID reporting modified. // // Build 5.1.011: -// - Water depth replaces moisture content for LID's pavement layer. +// - Water depth replaces moisture content for LID's pavement layer. // - Arguments for lidproc_saveResults() modified. // // Build 5.1.012: @@ -41,6 +41,7 @@ #ifndef LID_H #define LID_H + #include #include #include @@ -51,8 +52,8 @@ //----------------------------------------------------------------------------- enum LidTypes { BIO_CELL, // bio-retention cell - RAIN_GARDEN, // rain garden - GREEN_ROOF, // green roof + RAIN_GARDEN, // rain garden + GREEN_ROOF, // green roof INFIL_TRENCH, // infiltration trench POROUS_PAVEMENT, // porous pavement RAIN_BARREL, // rain barrel @@ -73,7 +74,7 @@ typedef struct { double thickness; // depression storage or berm ht. (ft) double voidFrac; // available fraction of storage volume - double roughness; // surface Mannings n + double roughness; // surface Mannings n double surfSlope; // land surface slope (fraction) double sideSlope; // swale side slope (run/rise) double alpha; // slope/roughness term in Manning eqn. @@ -184,7 +185,7 @@ typedef struct int drainNode; // node receiving drain flow TLidRptFile* rptFile; // pointer to detailed report file - TGrnAmpt soilInfil; // infil. object for biocell soil layer + TGrnAmpt soilInfil; // infil. object for biocell soil layer double surfaceDepth; // depth of ponded water on surface layer (ft) double paveDepth; // depth of water in porous pavement layer double soilMoisture; // moisture content of biocell soil layer @@ -192,7 +193,7 @@ typedef struct // net inflow - outflow from previous time step for each LID layer (ft/s) double oldFluxRates[MAX_LAYERS]; - + double dryTime; // time since last rainfall (sec) double oldDrainFlow; // previous drain flow (cfs) double newDrainFlow; // current drain flow (cfs) @@ -236,4 +237,5 @@ double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, double ucfRainDepth); -#endif + +#endif //LID_H diff --git a/src/solver/macros.h b/src/solver/macros.h index 998e92ac0..6f10ae464 100644 --- a/src/solver/macros.h +++ b/src/solver/macros.h @@ -7,6 +7,10 @@ // Author: L. Rossman //----------------------------------------------------------------------------- +#ifndef MACROS_H +#define MACROS_H + + //-------------------------------------------------- // Macro to test for successful allocation of memory //-------------------------------------------------- @@ -36,3 +40,6 @@ // Macro to evaluate function x with error checking //------------------------------------------------- #define CALL(x) (ErrorCode = ((ErrorCode>0) ? (ErrorCode) : (x))) + + +#endif //MACROS_H diff --git a/src/solver/mathexpr.h b/src/solver/mathexpr.h index 34d81eafd..ab56b7fba 100644 --- a/src/solver/mathexpr.h +++ b/src/solver/mathexpr.h @@ -8,6 +8,10 @@ ** LAST UPDATE: 03/20/14 ******************************************************************************/ +#ifndef MATHEXPR_H +#define MATHEXPR_H + + // Node in a tokenized math expression list struct ExprNode { @@ -27,3 +31,6 @@ double mathexpr_eval(MathExpr* expr, double (*getVal) (int)); // Deletes a tokenized math expression void mathexpr_delete(MathExpr* expr); + + +#endif //MATHEXPR_H diff --git a/src/solver/mempool.h b/src/solver/mempool.h index cc7b45cc1..864d2d6c3 100644 --- a/src/solver/mempool.h +++ b/src/solver/mempool.h @@ -7,6 +7,10 @@ // alloc pool - only the alloc routines know its structure. //----------------------------------------------------------------------------- +#ifndef MEMPOOL_H +#define MEMPOOL_H + + typedef struct { long dummy; @@ -17,3 +21,6 @@ char *Alloc(long); alloc_handle_t *AllocSetPool(alloc_handle_t *); void AllocReset(void); void AllocFreePool(void); + + +#endif //MEMPOOL_H diff --git a/src/solver/objects.h b/src/solver/objects.h index 8815a5efc..87716b86a 100644 --- a/src/solver/objects.h +++ b/src/solver/objects.h @@ -61,6 +61,10 @@ // - Support added for grouped freqency table of routing time steps. //----------------------------------------------------------------------------- +#ifndef OBJECTS_H +#define OBJECTS_H + + #include "mathexpr.h" #include "infil.h" #include "exfil.h" @@ -111,7 +115,7 @@ typedef struct typedef struct { char* ID; // raingage name - int dataSource; // data from time series or file + int dataSource; // data from time series or file int tSeries; // rainfall data time series index char fname[MAXFNAME+1]; // name of rainfall data file char staID[MAXMSG+1]; // station number @@ -135,7 +139,7 @@ typedef struct double reportRainfall; // rainfall value used for reported results int coGage; // index of gage with same rain timeseries int isUsed; // TRUE if gage used by any subcatchment - int isCurrent; // TRUE if gage's rainfall is current + int isCurrent; // TRUE if gage's rainfall is current } TGage; //------------------- @@ -143,7 +147,7 @@ typedef struct //------------------- typedef struct { - int dataSource; // data from time series or file + int dataSource; // data from time series or file int tSeries; // temperature data time series index DateTime fileStartDate; // starting date of data read from file double elev; // elev. of study area (ft) @@ -191,11 +195,11 @@ typedef struct int tSeries; // time series index double monthlyEvap[12]; // monthly evaporation values double panCoeff[12]; // monthly pan coeff. values - int recoveryPattern; // soil recovery factor pattern + int recoveryPattern; // soil recovery factor pattern int dryOnly; // true if evaporation only in dry periods //---------------------------- double rate; // current evaporation rate (ft/sec) - double recoveryFactor; // current soil recovery factor + double recoveryFactor; // current soil recovery factor } TEvap; //------------------- @@ -263,7 +267,7 @@ typedef struct //------------------------ typedef struct { - int aquifer; // index of associated gw aquifer + int aquifer; // index of associated gw aquifer int node; // index of node receiving gw flow double surfElev; // elevation of ground surface (ft) double a1, b1; // ground water outflow coeff. & exponent @@ -392,7 +396,7 @@ typedef struct double lidArea; // area devoted to LIDs (ft2) double rainfall; // current rainfall (ft/sec) double evapLoss; // current evap losses (ft/sec) - double infilLoss; // current infil losses (ft/sec) + double infilLoss; // current infil losses (ft/sec) double runon; // runon from other subcatchments (cfs) double oldRunoff; // previous runoff (cfs) double newRunoff; // current runoff (cfs) @@ -544,7 +548,7 @@ typedef struct TExfil* exfil; // ptr. to exfiltration object //----------------------------- double hrt; // hydraulic residence time (sec) - double evapLoss; // evaporation loss (ft3) + double evapLoss; // evaporation loss (ft3) double exfilLoss; // exfiltration loss (ft3) } TStorage; @@ -596,10 +600,10 @@ typedef struct double aFull; // area when full (ft2) double rFull; // hyd. radius when full (ft) double wMax; // width at widest point (ft) - double ywMax; // depth at max width (ft) + double ywMax; // depth at max width (ft) double sMax; // section factor at max. flow (ft^4/3) double aMax; // area at max. flow (ft2) - double lengthFactor; // floodplain / channel length + double lengthFactor; // floodplain / channel length //-------------------------------------- double roughness; // Manning's n double areaTbl[N_TRANSECT_TBL]; // table of area v. depth @@ -616,7 +620,7 @@ typedef struct { int curve; // index of shape curve int nTbl; // size of geometry tables - double aFull; // area when full + double aFull; // area when full double rFull; // hyd. radius when full double wMax; // max. width double sMax; // max. section factor @@ -704,10 +708,10 @@ typedef struct { int type; // pump type int pumpCurve; // pump curve table index - double initSetting; // initial speed setting + double initSetting; // initial speed setting double yOn; // startup depth (ft) double yOff; // shutoff depth (ft) - double xMin; // minimum pt. on pump curve + double xMin; // minimum pt. on pump curve double xMax; // maximum pt. on pump curve } TPump; @@ -838,7 +842,7 @@ typedef struct //------------------------------- typedef struct { // All volume totals are in ft3. - double rainfall; // rainfall volume + double rainfall; // rainfall volume double evap; // evaporation loss double infil; // infiltration loss double runoff; // runoff volume @@ -940,7 +944,7 @@ typedef struct double evap; double infil; double runoff; - double maxFlow; + double maxFlow; double impervRunoff; //(5.1.013) double pervRunoff; // } TSubcatchStats; @@ -988,11 +992,11 @@ typedef struct { double avgFlow; double maxFlow; - double* totalLoad; + double* totalLoad; int totalPeriods; } TOutfallStats; -//---------------- +//---------------- // PUMP STATISTICS //---------------- typedef struct @@ -1039,15 +1043,18 @@ typedef struct int objType; // either NODE or LINK int index; // node or link index double value; // value of node or link statistic -} TMaxStats; +} TMaxStats; //------------------ // REPORT FIELD INFO //------------------ -typedef struct +typedef struct { - char Name[80]; // name of reported variable + char Name[80]; // name of reported variable char Units[80]; // units of reported variable char Enabled; // TRUE if appears in report table int Precision; // number of decimal places when reported } TRptField; + + +#endif //OBJECTS_H diff --git a/src/solver/odesolve.h b/src/solver/odesolve.h index 86fc86e67..77316452f 100644 --- a/src/solver/odesolve.h +++ b/src/solver/odesolve.h @@ -5,8 +5,15 @@ // //----------------------------------------------------------------------------- +#ifndef ODESOLVE_H +#define ODESOLVE_H + + // functions that open, close, and use the ODE solver int odesolve_open(int n); void odesolve_close(void); int odesolve_integrate(double ystart[], int n, double x1, double x2, double eps, double h1, void (*derivs)(double, double*, double*)); + + +#endif //ODESOLVE_H diff --git a/src/solver/text.h b/src/solver/text.h index 00e274058..204f6d518 100644 --- a/src/solver/text.h +++ b/src/solver/text.h @@ -17,12 +17,16 @@ // 05/10/18 (Build 5.1.013) // 03/01/20 (Build 5.1.014) // 04/01/20 (Build 5.1.015) -// +// // Author: L. Rossman // // Text strings //----------------------------------------------------------------------------- +#ifndef TEXT_H +#define TEXT_H + + #define FMT01 \ "\tswmm5 \n" @@ -446,3 +450,6 @@ #define ws_GWF "[GWF" #define ws_ADJUST "[ADJUSTMENT" #define ws_EVENT "[EVENT" + + +#endif //TEXT_H From 2d9990494c953e24c896e76bb5e5cc8a4f365fd9 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Fri, 10 Jul 2020 10:17:25 -0400 Subject: [PATCH 159/266] Update dynwave.c Rolls back to v5.1.013 how the inflow/outflow to nodes between a conduit are adjusted for evaporation and seepage losses. It fixes a large WQ mass balance error in v5.1.014/15 as compared to 5.1.013. It also shows the flow loss directly in the conduit where it occurs rather than as just a reduction in the inflow to the conduit's downstream node. --- src/solver/dynwave.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/solver/dynwave.c b/src/solver/dynwave.c index df630c36d..8c901057e 100644 --- a/src/solver/dynwave.c +++ b/src/solver/dynwave.c @@ -10,6 +10,7 @@ // 08/01/16 (5.1.011) // 05/10/18 (5.1.013) // 03/01/20 (5.1.014) +// 07/10/20 (5.1.015) // Author: L. Rossman (EPA) // M. Tryby (EPA) // R. Dickinson (CDM) @@ -50,6 +51,9 @@ // - updateNodeFlows() modified to subtract conduit evap. and seepage losses // from downstream node inflow instead of upstream node outflow. // +// Build 5.1.015: +// - Roll back the 5.1.014 change for conduit losses in updateNodeFlows(). +// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -552,13 +556,13 @@ void updateNodeFlows(int i) // --- update total inflow & outflow at upstream/downstream nodes if ( q >= 0.0 ) { - Node[n1].outflow += q; //(5.1.014) - Node[n2].inflow += q - uniformLossRate; //(5.1.014) + Node[n1].outflow += q + uniformLossRate; //(5.1.015) + Node[n2].inflow += q; //(5.1.015) } else { - Node[n1].inflow -= q + uniformLossRate; //(5.1.014) - Node[n2].outflow -= q; //(5.1.014) + Node[n1].inflow -= q; //(5.1.015) + Node[n2].outflow -= q - uniformLossRate; //(5.1.015) } // --- add surf. area contributions to upstream/downstream nodes From 47aeabf792b892fcb47c33d2f330d4e79a8abcd2 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 10 Jul 2020 11:46:37 -0400 Subject: [PATCH 160/266] Test Appveyor --- appveyor.yml | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..ccb27e665 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,87 @@ +# https://ci.appveyor.com/project/OpenWaterAnalytics/stormwater-management-model/ + +version: 2.0.{build} + +platform: + - x64 + +matrix: + allow_failures: + #Group: (EXPERIMENTAL / SUPPORTED) + #EXPERIMENTAL is allowed to fail under the build matrix + - GROUP: "EXPERIMENTAL" + +environment: + matrix: + # Python 3.4 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + GENERATOR: "Visual Studio 15 2017" + GROUP: "SUPPORTED" + BOOST_ROOT: "C:/Libraries/boost_1_67_0" + PLATFORM: "win32" + REF_BUILD_ID: "323_1" + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + GENERATOR: "Visual Studio 15 2017 Win64" + GROUP: "EXPERIMENTAL" + BOOST_ROOT: "C:/Libraries/boost_1_67_0" + PLATFORM: "win64" + REF_BUILD_ID: "323_2" + +init: + - set SUT_BUILD_ID=%APPVEYOR_BUILD_NUMBER%_%APPVEYOR_JOB_NUMBER% + - set SWMM_HOME=%APPVEYOR_BUILD_FOLDER% + - set BUILD_HOME=buildprod + - set TEST_HOME=nrtestsuite + # See set values + - echo %APPVEYOR_BUILD_WORKER_IMAGE% + - echo %BUILD_HOME% + - echo %GENERATOR% + - echo %BOOST_ROOT% + + # Repo is cloned after init + +install: + # Install nrtest and dependencies for regression tests + - python -m pip install -r tools\requirements-appveyor.txt + +before_build: + - mkdir %BUILD_HOME% + - cd %BUILD_HOME% + - cmake -G "%GENERATOR%" + -DBUILD_TESTS=1 + -DBOOST_ROOT="%BOOST_ROOT%" + -DBoost_USE_STATIC_LIBS="ON" .. + +build_script: + - cmake --build . --config Release + +before_test: + - cd %SWMM_HOME% + - tools\before-test.cmd %PLATFORM% %REF_BUILD_ID% %SUT_BUILD_ID% %APPVEYOR_REPO_COMMIT% + +test_script: + # run unit tests + - cd %BUILD_HOME%\tests + - ctest -C Release + # Run regression tests + - cd %SWMM_HOME% + - tools\run-nrtest.cmd %REF_BUILD_ID% %SUT_BUILD_ID% + +on_success: + - cd %TEST_HOME%\benchmark + - appveyor PushArtifact receipt.json + - cd %SWMM_HOME% + - cd %BUILD_HOME%\bin\Release + - appveyor PushArtifact run-swmm.exe + - appveyor PushArtifact swmm5.dll + - appveyor PushArtifact swmm-output.dll + +on_failure: + - cd %TEST_HOME%\benchmark + # zip up the SUT benchmarks + - 7z a benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip .\swmm-%SUT_BUILD_ID% + - appveyor PushArtifact benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip + +#cache: +# - C:\ProgramData\chocolatey\bin -> appveyor.yml +# - C:\ProgramData\chocolatey\lib -> appveyor.yml From 843e2c7446c7df5687cd6b8836e9ff01e09b8e28 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Mon, 13 Jul 2020 15:30:03 -0400 Subject: [PATCH 161/266] Update stats.c Fixes failure to initialize GW final upper zone moisture content and final water table values producing strange summary results for 100% impervious subcatchments with GW. --- src/solver/stats.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/solver/stats.c b/src/solver/stats.c index 13dbbc60f..11c4ace09 100644 --- a/src/solver/stats.c +++ b/src/solver/stats.c @@ -40,6 +40,7 @@ // // Build 5.1.015: // - Fixes bug in summary statistics when Report Start date > Start Date. +// - Fixes failure to initialize all subcatchment groundwater statistics. // - Support added for grouped freqency table of routing time steps. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -155,6 +156,8 @@ int stats_open() Subcatch[j].groundwater->stats.deepFlow = 0.0; Subcatch[j].groundwater->stats.evap = 0.0; Subcatch[j].groundwater->stats.maxFlow = 0.0; + Subcatch[j].groundwater->stats.finalUpperMoist = 0.0; //(5.1.015) + Subcatch[j].groundwater->stats.finalWaterTable = 0.0; // } } From 7ae9f1625b374f9559ebaea6974a240803518705 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 17 Jul 2020 11:45:12 -0400 Subject: [PATCH 162/266] Update build-and-test.yml Adds OMP_NUM_THREADS as env variable --- .github/workflows/build-and-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 15ed09ed6..3be305834 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -23,6 +23,7 @@ jobs: run: shell: cmd env: + OMP_NUM_THREADS: 1 PROJECT: swmm BUILD_HOME: build TEST_HOME: nrtests From 1dba5ed653606c529204ddbc168ed41e4152e17c Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Sat, 18 Jul 2020 11:52:37 -0400 Subject: [PATCH 163/266] Truncates numerical noise in "Time of Max Occurrence" determinations --- src/solver/stats.c | 60 +++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/solver/stats.c b/src/solver/stats.c index 11c4ace09..6eade9377 100644 --- a/src/solver/stats.c +++ b/src/solver/stats.c @@ -267,10 +267,10 @@ int stats_open() } // --- allocate memory & initialize pumping statistics - if ( Nlinks[PUMP] > 0 ) - { + if ( Nlinks[PUMP] > 0 ) + { PumpStats = (TPumpStats *) calloc(Nlinks[PUMP], sizeof(TPumpStats)); - if ( !PumpStats ) + if ( !PumpStats ) { report_writeErrorMsg(ERR_MEMORY, ""); return ErrorCode; @@ -280,14 +280,14 @@ int stats_open() PumpStats[j].utilized = 0.0; PumpStats[j].minFlow = 0.0; PumpStats[j].avgFlow = 0.0; - PumpStats[j].maxFlow = 0.0; + PumpStats[j].maxFlow = 0.0; PumpStats[j].volume = 0.0; PumpStats[j].energy = 0.0; PumpStats[j].startUps = 0; - PumpStats[j].offCurveLow = 0.0; + PumpStats[j].offCurveLow = 0.0; PumpStats[j].offCurveHigh = 0.0; - } - } + } + } // --- initialize system stats MaxRunoffFlow = 0.0; @@ -319,7 +319,7 @@ int stats_open() void stats_close() // // Input: none -// Output: +// Output: // Purpose: closes the simulation statistics system. // { @@ -328,7 +328,7 @@ void stats_close() FREE(SubcatchStats); FREE(NodeStats); FREE(LinkStats); - FREE(StorageStats); + FREE(StorageStats); if ( OutfallStats ) { for ( j=0; j NodeStats[j].maxDepth ) + if ( (newDepth - NodeStats[j].maxDepth) > ZERO ) { NodeStats[j].maxDepth = newDepth; NodeStats[j].maxDepthDate = aDate; } - + // --- update flooding, ponding, and surcharge statistics if ( Node[j].type != OUTFALL ) { @@ -570,13 +570,13 @@ void stats_updateNodeStats(int j, double tStep, DateTime aDate) { k = Node[j].subIndex; StorageStats[k].avgVol += newVolume; - StorageStats[k].evapLosses += - Storage[Node[j].subIndex].evapLoss; + StorageStats[k].evapLosses += + Storage[Node[j].subIndex].evapLoss; StorageStats[k].exfilLosses += - Storage[Node[j].subIndex].exfilLoss; + Storage[Node[j].subIndex].exfilLoss; newVolume = MIN(newVolume, Node[j].fullVolume); - if ( newVolume > StorageStats[k].maxVol ) + if ( (newVolume - StorageStats[k].maxVol) > ZERO ) { StorageStats[k].maxVol = newVolume; StorageStats[k].maxVolDate = aDate; @@ -585,7 +585,7 @@ void stats_updateNodeStats(int j, double tStep, DateTime aDate) } // --- update outfall statistics - if ( Node[j].type == OUTFALL ) + if ( Node[j].type == OUTFALL ) { k = Node[j].subIndex; if ( Node[j].inflow >= MIN_RUNOFF_FLOW ) @@ -596,25 +596,25 @@ void stats_updateNodeStats(int j, double tStep, DateTime aDate) } for (p=0; p fabs(NodeStats[j].maxLatFlow) ) NodeStats[j].maxLatFlow = Node[j].newLatFlow; - if ( Node[j].inflow > NodeStats[j].maxInflow ) + if ( (Node[j].inflow - NodeStats[j].maxInflow) > ZERO ) { NodeStats[j].maxInflow = Node[j].inflow; NodeStats[j].maxInflowDate = aDate; } // --- update overflow statistics - if ( Node[j].overflow > NodeStats[j].maxOverflow ) + if ( (Node[j].overflow - NodeStats[j].maxOverflow) > ZERO ) { NodeStats[j].maxOverflow = Node[j].overflow; NodeStats[j].maxOverflowDate = aDate; @@ -639,7 +639,7 @@ void stats_updateLinkStats(int j, double tStep, DateTime aDate) // --- update max. flow dq = Link[j].newFlow - Link[j].oldFlow; q = fabs(Link[j].newFlow); - if ( q > LinkStats[j].maxFlow ) + if ( (q - LinkStats[j].maxFlow) > ZERO ) { LinkStats[j].maxFlow = q; LinkStats[j].maxFlowDate = aDate; @@ -653,7 +653,7 @@ void stats_updateLinkStats(int j, double tStep, DateTime aDate) } // --- update max. depth - if ( Link[j].newDepth > LinkStats[j].maxDepth ) + if ( (Link[j].newDepth - LinkStats[j].maxDepth) > ZERO ) { LinkStats[j].maxDepth = Link[j].newDepth; } @@ -686,10 +686,10 @@ void stats_updateLinkStats(int j, double tStep, DateTime aDate) else if ( Link[j].type == CONDUIT ) { - // --- update time under normal flow & inlet control + // --- update time under normal flow & inlet control if ( Link[j].normalFlow ) LinkStats[j].timeNormalFlow += tStep; if ( Link[j].inletControl ) LinkStats[j].timeInletControl += tStep; - + // --- update flow classification distribution k = Link[j].flowClass; if ( k >= 0 && k < MAX_FLOW_CLASSES ) @@ -700,7 +700,7 @@ void stats_updateLinkStats(int j, double tStep, DateTime aDate) // --- update time conduit is full k = Link[j].subIndex; if ( q >= Link[j].qFull * (double)Conduit[k].barrels ) - LinkStats[j].timeFullFlow += tStep; + LinkStats[j].timeFullFlow += tStep; if ( Conduit[k].capacityLimited ) LinkStats[j].timeCapacityLimited += tStep; @@ -748,11 +748,11 @@ void stats_findMaxStats() MaxMassBalErrs[j].value = -1.0; MaxCourantCrit[j].index = -1; MaxCourantCrit[j].value = -1.0; - MaxFlowTurns[j].index = -1; + MaxFlowTurns[j].index = -1; MaxFlowTurns[j].value = -1.0; } - // --- find links with most flow turns + // --- find links with most flow turns if ( stepCount > 2 ) //(5.1.015) { for (j=0; j Date: Sat, 18 Jul 2020 12:01:20 -0400 Subject: [PATCH 164/266] Upating Actions workflow --- .github/workflows/build-and-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index d55d1099d..d8294028c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -14,6 +14,7 @@ jobs: shell: cmd working-directory: tools env: + OMP_NUM_THREADS: 1 PROJECT: swmm BUILD_HOME: build TEST_HOME: nrtests From 1692c25f8f21058b62f8333a283ae68efa27d2b6 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 21 Jul 2020 10:42:24 -0400 Subject: [PATCH 165/266] Update swmm_output.c --- src/outfile/swmm_output.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/outfile/swmm_output.c b/src/outfile/swmm_output.c index 82bbbcf6c..84ae6594c 100644 --- a/src/outfile/swmm_output.c +++ b/src/outfile/swmm_output.c @@ -10,6 +10,8 @@ */ +#define _CRT_SECURE_NO_DEPRECATE + #include #include #include From e2546d2ddfa8d1a994f506e099893dd2ad6fb86c Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Wed, 29 Jul 2020 22:43:03 -0400 Subject: [PATCH 166/266] Update project.c stdlib.h was included twice. --- src/solver/project.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/solver/project.c b/src/solver/project.c index db9d116c6..1c8ed78a0 100644 --- a/src/solver/project.c +++ b/src/solver/project.c @@ -63,7 +63,6 @@ #include #include -#include #include #if defined(_OPENMP) //(5.1.013) From 437f773eb2dd0b98b8190a077b4c4bb9b9031478 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 19 Aug 2020 15:56:27 -0400 Subject: [PATCH 167/266] Work in progress Updating header --- .github/workflows/build-and-test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 3be305834..dc516c6dc 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -1,8 +1,8 @@ # # build-and-test.yml - GitHub Actions CI for swmm-solver # -# Created: May 19, 2019 -# Updated: May 31, 2019 +# Created: May 19, 2020 +# Updated: May 31, 2020 # # Author: Michael E. Tryby # US EPA - ORD/CESER @@ -52,7 +52,7 @@ jobs: run: tools/before-nrtest.cmd - name: Run reg test - run: tools/run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% + run: tools/run-nrtests.cmd %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Upload artifacts if: ${{ always() }} From b58aa910a0adf3794a59df210de6b3d94f970e11 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 19 Aug 2020 16:46:59 -0400 Subject: [PATCH 168/266] Minor formatting --- CMakeLists.txt | 24 +++--------------------- src/outfile/CMakeLists.txt | 37 ++++++++++++++++++++++++++----------- src/outfile/errormanager.h | 2 ++ src/outfile/messages.h | 2 ++ src/run/CMakeLists.txt | 12 +++++++----- src/solver/CMakeLists.txt | 29 +++++++++++++++++++---------- 6 files changed, 59 insertions(+), 47 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5918cfe51..95aae44bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,9 +17,9 @@ endif() project(swmm-solver - VERSION 5.1.14 + VERSION 5.1.15 LANGUAGES C CXX - ) +) # Append local dir to module search path list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) @@ -40,6 +40,7 @@ set(LIBRARY_DIST "lib") set(CONFIG_DIST "cmake") +# Define build options option(BUILD_TESTS "Builds component tests (requires Boost)" OFF) option(BUILD_DEF "Builds library with def file interface" OFF) @@ -55,25 +56,6 @@ if(BUILD_TESTS) endif() -# Create target import scripts so other cmake projects can use swmm libraries -install( - EXPORT - swmm5Targets - DESTINATION - "${CONFIG_DIST}" - FILE - swmm5-config.cmake - ) - -install( - EXPORT - swmm-outputTargets - DESTINATION - "${CONFIG_DIST}" - FILE - swmm-output-config.cmake - ) - # Create install rules for vcruntime.dll, msvcp.dll, vcomp.dll etc. set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) include(InstallRequiredSystemLibraries) diff --git a/src/outfile/CMakeLists.txt b/src/outfile/CMakeLists.txt index 5c5d2ed96..8c0950520 100644 --- a/src/outfile/CMakeLists.txt +++ b/src/outfile/CMakeLists.txt @@ -12,7 +12,7 @@ set(SWMM_OUT_PUBLIC_HEADERS include/swmm_output.h include/swmm_output_enums.h include/swmm_output_export.h - ) +) # the binary output file API @@ -20,13 +20,13 @@ add_library(swmm-output SHARED swmm_output.c errormanager.c - ) +) target_include_directories(swmm-output PUBLIC $ $ - ) +) include(GenerateExportHeader) generate_export_header(swmm-output @@ -34,19 +34,34 @@ generate_export_header(swmm-output EXPORT_MACRO_NAME EXPORT_OUT_API EXPORT_FILE_NAME swmm_output_export.h STATIC_DEFINE SHARED_EXPORTS_BUILT_AS_STATIC - ) +) file(COPY ${CMAKE_CURRENT_BINARY_DIR}/swmm_output_export.h DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/include - ) +) -install(TARGETS swmm-output EXPORT swmm-outputTargets - RUNTIME DESTINATION "${TOOL_DIST}" - LIBRARY DESTINATION "${TOOL_DIST}" - ARCHIVE DESTINATION "${LIBRARY_DIST}" - FRAMEWORK DESTINATION "${TOOL_DIST}" - ) +install(TARGETS swmm-output + EXPORT + swmm-outputTargets + RUNTIME + DESTINATION "${TOOL_DIST}" + LIBRARY + DESTINATION "${TOOL_DIST}" + ARCHIVE + DESTINATION "${LIBRARY_DIST}" + FRAMEWORK + DESTINATION "${TOOL_DIST}" +) + +install( + EXPORT + swmm-outputTargets + DESTINATION + "${CONFIG_DIST}" + FILE + swmm-output-config.cmake +) install(FILES ${SWMM_OUT_PUBLIC_HEADERS} DESTINATION "${INCLUDE_DIST}") diff --git a/src/outfile/errormanager.h b/src/outfile/errormanager.h index cfa97ba70..b61334200 100644 --- a/src/outfile/errormanager.h +++ b/src/outfile/errormanager.h @@ -10,6 +10,7 @@ #ifndef ERRORMANAGER_H_ #define ERRORMANAGER_H_ + #define ERR_MAXMSG 256 typedef struct error_s { @@ -24,4 +25,5 @@ int set_error(error_handle_t* error_handle, int errorcode); char* check_error(error_handle_t* error_handle); void clear_error(error_handle_t* error_handle); + #endif /* ERRORMANAGER_H_ */ diff --git a/src/outfile/messages.h b/src/outfile/messages.h index 9fa691df3..54af706ff 100644 --- a/src/outfile/messages.h +++ b/src/outfile/messages.h @@ -10,6 +10,7 @@ #ifndef SRC_MESSAGES_H_ #define SRC_MESSAGES_H_ + #define MAXMSG 56 /*------------------- Error Messages --------------------*/ @@ -28,4 +29,5 @@ #define ERR440 "ERROR 440: an unspecified error has occurred" + #endif /* SRC_MESSAGES_H_ */ diff --git a/src/run/CMakeLists.txt b/src/run/CMakeLists.txt index a0937db10..adcb11129 100644 --- a/src/run/CMakeLists.txt +++ b/src/run/CMakeLists.txt @@ -11,20 +11,22 @@ # Creates the EPANET command line executable add_executable(runswmm main.c - ) +) + target_include_directories(runswmm PUBLIC include - ) +) + target_link_libraries(runswmm LINK_PUBLIC swmm5 - ) +) install(TARGETS runswmm DESTINATION "${TOOL_DIST}" - ) +) # copy runswmm to build tree for testing @@ -32,4 +34,4 @@ add_custom_command(TARGET runswmm POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_BINARY_DIR}/bin/$/$ - ) +) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index f109b5f49..fd5a00434 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -15,12 +15,12 @@ find_package(OpenMP) # configure file groups set(SWMM_PUBLIC_HEADERS include/swmm5.h - ) +) file(GLOB SWMM_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c *.h - ) +) if(BUILD_DEF) # Build library with def file interface for backward compatibility @@ -32,14 +32,14 @@ if(BUILD_DEF) SHARED ${SWMM_SOURCES} ${PROJECT_SOURCE_DIR/bindings/swmm5.def} - ) + ) else() add_library(swmm5 SHARED ${SWMM_SOURCES} - ) + ) endif() @@ -50,33 +50,42 @@ target_compile_options(swmm5 "$<$:/fp:fast>" "$<$:/Zi>" ">" - ) +) target_link_options(swmm5 PUBLIC "$<$:" "$<$:/LTCG:incremental>" ">" - ) +) target_link_libraries(swmm5 PUBLIC $<$>>:m> $<$:OpenMP::OpenMP_C> - ) +) target_include_directories(swmm5 PUBLIC $ $ - ) +) install(TARGETS swmm5 EXPORT swmm5Targets RUNTIME DESTINATION "${TOOL_DIST}" LIBRARY DESTINATION "${TOOL_DIST}" ARCHIVE DESTINATION "${LIBRARY_DIST}" FRAMEWORK DESTINATION "${TOOL_DIST}" - ) +) + +install( + EXPORT + swmm5Targets + DESTINATION + "${CONFIG_DIST}" + FILE + swmm5-config.cmake +) install(FILES ${SWMM_PUBLIC_HEADERS} DESTINATION "${INCLUDE_DIST}") @@ -86,4 +95,4 @@ add_custom_command(TARGET swmm5 POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_BINARY_DIR}/bin/$/$ - ) +) From 56d1aeeb3045c910546110106f09e387dfa06056 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 19 Aug 2020 17:00:58 -0400 Subject: [PATCH 169/266] Derecating tools folder --- tools/Reg_Testing.md | 50 ---------------- tools/app-config.cmd | 58 ------------------ tools/before-nrtest.cmd | 119 ------------------------------------- tools/make.cmd | 110 ---------------------------------- tools/requirements-win.txt | 22 ------- tools/run-nrtests.cmd | 118 ------------------------------------ 6 files changed, 477 deletions(-) delete mode 100644 tools/Reg_Testing.md delete mode 100644 tools/app-config.cmd delete mode 100644 tools/before-nrtest.cmd delete mode 100644 tools/make.cmd delete mode 100644 tools/requirements-win.txt delete mode 100644 tools/run-nrtests.cmd diff --git a/tools/Reg_Testing.md b/tools/Reg_Testing.md deleted file mode 100644 index b6d31afec..000000000 --- a/tools/Reg_Testing.md +++ /dev/null @@ -1,50 +0,0 @@ - - -## Regression Testing SWMM locally on Windows - - -### Dependencies - -Before the project can be built and tested the required dependencies must be installed. - -**Summary of Build Dependencies: Windows** - - - Build - - Build Tools for Visual Studio 2017 - - CMake 3.13 - - - Regression Test - - Python 3.6 64 bit - - curl - - git - - 7z - -Once Python is present, the following command installs the required packages for regression testing. -``` -\> cd swmm -\swmm>pip install -r tools\requirements-win.txt -``` - - -### Build - -EPANET can be built with one simple command. -``` -\swmm>tools\make.cmd -``` - - -### Regression Test - -This command runs regression tests for the local build and compares them to the latest benchmark. -``` -\swmm>tools\before-nrtest.cmd && tools\run-nrtest.cmd -``` diff --git a/tools/app-config.cmd b/tools/app-config.cmd deleted file mode 100644 index 09ce04daf..000000000 --- a/tools/app-config.cmd +++ /dev/null @@ -1,58 +0,0 @@ -:: -:: app-config.cmd - Generates nrtest app configuration file for SUT executable -:: -:: Date Created: 10/16/2019 -:: Date Modified: 10/17/2019 -:: -:: Author: Michael E. Tryby -:: US EPA - ORD/CESER -:: -:: Requires: -:: git -:: -:: Environment Variables: -:: PROJECT -:: -:: Arguments: -:: 1 - absolute path to test executable (valid path seperator for nrtest is "/") -:: 2 - (platform) -:: 3 - (build identifier for SUT) -:: - -@echo off -setlocal - - -:: check requirements -where git > nul -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: git not installed" & exit /B 1 ) - -:: check environment -if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) - - -:: swmm target created by the cmake build script -set TEST_CMD=run%PROJECT%.exe - -:: remove quotes from path and convert backward to forward slash -set ABS_BUILD_PATH=%~1 -set ABS_BUILD_PATH=%ABS_BUILD_PATH:\=/% - -if [%2]==[] ( set "PLATFORM=unknown" -) else ( set "PLATFORM=%~2" ) - -if [%3]==[] ( set "BUILD_ID=unknown" -) else ( set "BUILD_ID=%~3" ) - -:: determine version -for /F "tokens=1" %%v in ( 'git rev-parse --short HEAD' ) do ( set "VERSION=%%v" ) -if not defined VERSION ( echo "ERROR: VERSION could not be determined" & exit /B 1 ) - - -echo { -echo "name" : "%PROJECT%", -echo "version" : "%VERSION%", -echo "description" : "%PLATFORM% %BUILD_ID%", -echo "setup_script" : "", -echo "exe" : "%ABS_BUILD_PATH%/%TEST_CMD%" -echo } diff --git a/tools/before-nrtest.cmd b/tools/before-nrtest.cmd deleted file mode 100644 index dd558fbde..000000000 --- a/tools/before-nrtest.cmd +++ /dev/null @@ -1,119 +0,0 @@ -:: -:: before-test.cmd - Stages test and benchmark files for nrtest -:: -:: Date Created: 10/16/2019 -:: Date Updated: -:: -:: Author: Michael E. Tryby -:: US EPA - ORD/CESER -:: -:: Dependencies: -:: curl -:: 7z -:: -:: Environment Variables: -:: PROJECT -:: BUILD_HOME - defaults to "build" -:: PLATFORM -:: -:: Arguments: -:: 1 - (RELEASE_TAG) release tag for benchmark version (defaults to latest tag) -:: -:: Note: -:: Tests and benchmark files are stored in the project-nrtests repo. -:: This script retrieves them using a stable URL associated with a GitHub -:: release, stages the files, and sets up the environment for nrtest to run. -:: - -::@echo off - -:: set global default -set "TEST_HOME=nrtests" - -:: determine project directory -set "CUR_DIR=%CD%" -set "SCRIPT_HOME=%~dp0" -cd %SCRIPT_HOME% -cd .. - -setlocal - - -:: check that dependencies are installed -where curl > nul -if %ERRORLEVEL% neq 0 ( echo "ERROR: curl not installed" & exit /B 1 ) -where 7z > nul -if %ERRORLEVEL% neq 0 ( echo "ERROR: 7zip not installed" & exit /B 1 ) - - -:: set URL to github repo with test files -set "NRTESTS_URL=https://github.com/SWMM-Project/%PROJECT%-nrtestsuite" - - -:: if release tag isn't provided latest tag will be retrieved -if [%1] == [] (set "RELEASE_TAG=" -) else (set "RELEASE_TAG=%~1") - - -:: check env variables and apply defaults -if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) -if not defined BUILD_HOME ( echo "ERROR: BUILD_HOME must be defined" & exit /B 1 ) -if not defined PLATFORM ( echo "ERROR: PLATFORM must be defined" & exit /B 1 ) - - -echo INFO: Staging files for regression testing - - -:: determine latest tag in the tests repo -if [%RELEASE_TAG%] == [] ( - for /F delims^=^"^ tokens^=2 %%g in ('curl --silent %NRTESTS_URL%/releases/latest') do ( - set "RELEASE_TAG=%%~nxg" - ) -) - -if defined RELEASE_TAG ( - set "TESTFILES_URL=%NRTESTS_URL%/archive/%RELEASE_TAG%.zip" - set "BENCHFILES_URL=%NRTESTS_URL%/releases/download/%RELEASE_TAG%/benchmark-%PLATFORM%.zip" -) else ( echo ERROR: tag %RELEASE_TAG% is invalid & exit /B 1 ) - - -:: create a clean directory for staging regression tests -if exist %TEST_HOME% ( - rmdir /s /q %TEST_HOME% -) -mkdir %TEST_HOME% -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to make %TEST_HOME% dir" & exit /B 1 ) -cd %TEST_HOME% -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to cd %TEST_HOME% dir" & exit /B 1 ) - - -:: retrieve nrtest cases and benchmark results for regression testing -curl -fsSL -o nrtestfiles.zip %TESTFILES_URL% -curl -fsSL -o benchmark.zip %BENCHFILES_URL% - - -:: extract tests, scripts, benchmarks, and manifest -7z x nrtestfiles.zip * > nul -7z x benchmark.zip -obenchmark\ > nul -7z e benchmark.zip -o. manifest.json -r > nul - - -:: set up symlinks for tests directory -mklink /D .\tests .\%PROJECT%-nrtestsuite-%RELEASE_TAG:~1%\public > nul - - -endlocal - - -:: determine REF_BUILD_ID from manifest file -for /F delims^=^"^ tokens^=4 %%d in ( 'findstr %PLATFORM% %TEST_HOME%\manifest.json' ) do ( - for /F "tokens=2" %%r in ( 'echo %%d' ) do ( set "REF_BUILD_ID=%%r" ) -) -if not defined REF_BUILD_ID ( echo "ERROR: REF_BUILD_ID could not be determined" & exit /B 1 ) - -:: GitHub Actions -echo ::set-env name=REF_BUILD_ID::%REF_BUILD_ID% - - -:: return to users current directory -cd %CUR_DIR% diff --git a/tools/make.cmd b/tools/make.cmd deleted file mode 100644 index 93561bce9..000000000 --- a/tools/make.cmd +++ /dev/null @@ -1,110 +0,0 @@ -:: -:: make.cmd - builds project -:: -:: Date Created: 10/15/2019 -:: Date Updated: -:: -:: Author: Michael E. Tryby -:: US EPA - ORD/CESER -:: -:: Requires: -:: Build Tools for Visual Studio download: -:: https://visualstudio.microsoft.com/downloads/ -:: -:: CMake download: -:: https://cmake.org/download/ -:: -:: Optional Arguments: -:: /g ("GENERATOR") defaults to "Visual Studio 15 2017" -:: /t builds and runs unit tests (requires Boost) -:: - - -::echo off - - -:: set global defaults -set "PROJECT=swmm" -set "BUILD_HOME=build" -set "PLATFORM=win32" -set "CONFIG=Release" - - -:: determine project directory -set "CUR_DIR=%CD%" -set "SCRIPT_HOME=%~dp0" -cd %SCRIPT_HOME% -cd .. - -:: check for requirements -where cmake > nul -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: cmake not installed" & exit /B 1 ) - - -setlocal EnableDelayedExpansion - - -echo INFO: Building %PROJECT% ... - - -:: set local defaults -set "GENERATOR=Visual Studio 15 2017" -set "TESTING=0" - -:: process arguments -:loop -if NOT [%1]==[] ( - if "%1"=="/g" ( - set "GENERATOR=%~2" - shift - ) - if "%1"=="/t" ( - set "TESTING=1" - ) - shift - goto :loop -) - - -:: if generator has changed delete the build folder -if exist %BUILD_HOME% ( - for /F "tokens=*" %%f in ( 'findstr CMAKE_GENERATOR:INTERNAL %BUILD_HOME%\CmakeCache.txt' ) do ( - for /F "delims=:= tokens=3" %%m in ( 'echo %%f' ) do ( - set CACHE_GEN=%%m - if not "!CACHE_GEN!" == "!GENERATOR!" ( rmdir /s /q %BUILD_HOME% ) - ) - ) -) - -:: perform the build -cmake -E make_directory %BUILD_HOME% - - -if %TESTING% EQU 1 ( - cmake -E chdir ./%BUILD_HOME% cmake -G"%GENERATOR%" -DBUILD_TESTS=ON ..^ - && cmake --build ./%BUILD_HOME% --config Debug^ - & echo. && cmake -E chdir ./%BUILD_HOME% ctest -C Debug --output-on-failure -) else ( - cmake -E chdir ./%BUILD_HOME% cmake -G"%GENERATOR%" -DBUILD_TESTS=OFF ..^ - && cmake --build ./%BUILD_HOME% --config Release --target package -) - - -endlocal - - -:: determine platform from CmakeCache.txt file -for /F "tokens=*" %%f in ( 'findstr CMAKE_SHARED_LINKER_FLAGS:STRING %BUILD_HOME%\CmakeCache.txt' ) do ( - for /F "delims=: tokens=3" %%m in ( 'echo %%f' ) do ( - if "%%m" == "X86" ( set "PLATFORM=win32" ) else if "%%m" == "x64" ( set "PLATFORM=win64" ) - ) -) -if not defined PLATFORM ( echo "ERROR: PLATFORM could not be determined" & exit /B 1 ) - - -:: GitHub Actions -echo ::set-env name=PLATFORM::%PLATFORM% - - -:: return to users current dir -cd %CUR_DIR% diff --git a/tools/requirements-win.txt b/tools/requirements-win.txt deleted file mode 100644 index 45d66b5b3..000000000 --- a/tools/requirements-win.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# requirements-win.txt - Python requirements for running nrtest on Win32/Win64 -# -# Date Created: Oct 17, 2019 -# Date Updated: May 21, 2020 -# -# Author: Michael E. Tryby -# US EPA ORD/CESER -# -# Useful for configuring a python environment to run nrtests on swmm. -# -# usage: -# pip install -r tools/requirements-win.txt -# - -nrtest - --f https://github.com/SWMM-Project/swmm-python/releases/download/v0.6.0-rc.2/swmm_toolkit-0.5.0-cp37-cp37m-win_amd64.whl -swmm-toolkit - --f https://github.com/SWMM-Project/swmm-python/releases/download/v0.6.0-rc.1/nrtest_swmm-0.6.0-py3-none-any.whl -nrtest-swmm diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd deleted file mode 100644 index 8b07f0ee6..000000000 --- a/tools/run-nrtests.cmd +++ /dev/null @@ -1,118 +0,0 @@ -:: -:: run_nrtest.cmd - Runs numerical regression test -:: -:: Date Created: 10/16/2019 -:: Date Updated: -:: -:: Author: Michael E. Tryby -:: US EPA - ORD/CESER -:: -:: Dependencies: -:: python -m pip install -r requirements.txt -:: -:: Environment Variables: -:: PROJECT -:: BUILD_HOME - relative path -:: TEST_HOME - relative path -:: PLATFORM -:: REF_BUILD_ID -:: -:: Arguments: -:: 1 - (SUT_VERSION) - optional argument -:: 2 - (SUT_BUILD_ID) - optional argument -:: - -::@echo off -setlocal EnableDelayedExpansion - - -:: Check that required environment variables are set -if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) -if not defined BUILD_HOME ( echo "ERROR: BUILD_HOME must be defined" & exit /B 1 ) -if not defined TEST_HOME ( echo "ERROR: TEST_HOME must be defined" & exit /B 1 ) -if not defined PLATFORM ( echo "ERROR: PLATFORM must be defined" & exit /B 1 ) -if not defined REF_BUILD_ID ( echo "ERROR: REF_BUILD_ID must be defined" & exit /B 1 ) - - -:: determine project directory -set "CUR_DIR=%CD%" -set "SCRIPT_HOME=%~dp0" -cd %SCRIPT_HOME% -pushd .. -set PROJ_DIR=%CD% -popd - - -cd %PROJ_DIR%\%TEST_HOME% - -:: Process optional arguments -if [%1]==[] (set "SUT_VERSION=unknown" -) else ( set "SUT_VERSION=%~1" ) - -if [%2]==[] ( set "SUT_BUILD_ID=local" -) else ( set "SUT_BUILD_ID=%~2" ) - - -:: check if app config file exists -if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( - mkdir apps - call %SCRIPT_HOME%\app-config.cmd %PROJ_DIR%\%BUILD_HOME%\bin\Release^ - %PLATFORM% %SUT_BUILD_ID% %SUT_VERSION% > apps\%PROJECT%-%SUT_BUILD_ID%.json -) - - -:: recursively build test list -set "TESTS=" - for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( - set FULL_PATH=%%T - set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! -) - - -:: determine location of python Scripts folder -for /F "tokens=*" %%G in ('where python.exe') do ( - set PYTHON_DIR=%%~dpG - goto break_loop_1 -) -:break_loop_1 -set "NRTEST_SCRIPT_PATH=%PYTHON_DIR%Scripts" - - -:: build nrtest execute command -set NRTEST_EXECUTE_CMD=python.exe %NRTEST_SCRIPT_PATH%\nrtest execute -set TEST_APP_PATH=apps\%PROJECT%-%SUT_BUILD_ID%.json -set TEST_OUTPUT_PATH=benchmark\%PROJECT%-%SUT_BUILD_ID% - -:: build nrtest compare command -set NRTEST_COMPARE_CMD=python.exe %NRTEST_SCRIPT_PATH%\nrtest compare -set REF_OUTPUT_PATH=benchmark\%PROJECT%-%REF_BUILD_ID% -set RTOL_VALUE=0.01 -set ATOL_VALUE=1.0E-6 - -:: change current directory to test suite -::cd %TEST_HOME% - -:: if present clean test benchmark results -if exist %TEST_OUTPUT_PATH% ( - rmdir /s /q %TEST_OUTPUT_PATH% -) - -:: perform nrtest execute -echo INFO: Creating SUT %SUT_BUILD_ID% artifacts -set NRTEST_COMMAND=%NRTEST_EXECUTE_CMD% %TEST_APP_PATH% %TESTS% -o %TEST_OUTPUT_PATH% -:: if there is an error exit the script with error value 1 -%NRTEST_COMMAND% || exit /B 1 - -echo. - -:: perform nrtest compare -echo INFO: Comparing SUT artifacts to REF %REF_BUILD_ID% -set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --rtol %RTOL_VALUE% --atol %ATOL_VALUE% -o benchmark\receipt.json -%NRTEST_COMMAND% - - -:: GitHub Actions -echo ::set-env name=SUT_BUILD_ID::%SUT_BUILD_ID% - -:: Return user to their current dir -cd %CUR_DIR% From 2273372f63168e1e2c07d5caeb0c0ab74f66874c Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 19 Aug 2020 17:15:03 -0400 Subject: [PATCH 170/266] Updating Actions configuration --- .github/workflows/build-and-test.yml | 30 +++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index d8294028c..ca1e519a6 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -12,29 +12,49 @@ jobs: defaults: run: shell: cmd - working-directory: tools + working-directory: ci-tools/windows + env: OMP_NUM_THREADS: 1 PROJECT: swmm BUILD_HOME: build TEST_HOME: nrtests + steps: - - uses: actions/checkout@v2 + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Checkout submodule + uses: actions/checkout@v2 + with: + repository: michaeltryby/ci-tools + path: ci-tools + - name: Setup python uses: actions/setup-python@v2 with: python-version: '3.7' + - name: Install requirements run: | python -m pip install --upgrade pip python -m pip install -r requirements-win.txt + - name: Build and unit test run: make.cmd /t + - name: Build run: make.cmd + - name: Before test - run: | - before-nrtest.cmd - dir ..\nrtests\benchmark + run: before-nrtest.cmd + - name: Reg test run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% + + - name: Upload artifacts + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: build-test-artifacts + path: upload/*.* From 5f161fe01c7bf32d00bcffaf28404b7b83ccc8b0 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 19 Aug 2020 17:18:44 -0400 Subject: [PATCH 171/266] Fixing requirements --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ca1e519a6..c65509ea7 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -38,7 +38,7 @@ jobs: - name: Install requirements run: | python -m pip install --upgrade pip - python -m pip install -r requirements-win.txt + python -m pip install -r requirements-swmm.txt - name: Build and unit test run: make.cmd /t From 0650f71c3dbf924ec558eb4ea1301eb66faf7b62 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Thu, 20 Aug 2020 12:51:14 -0400 Subject: [PATCH 172/266] Delete appveyor.yml --- appveyor.yml | 87 ---------------------------------------------------- 1 file changed, 87 deletions(-) delete mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index ccb27e665..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,87 +0,0 @@ -# https://ci.appveyor.com/project/OpenWaterAnalytics/stormwater-management-model/ - -version: 2.0.{build} - -platform: - - x64 - -matrix: - allow_failures: - #Group: (EXPERIMENTAL / SUPPORTED) - #EXPERIMENTAL is allowed to fail under the build matrix - - GROUP: "EXPERIMENTAL" - -environment: - matrix: - # Python 3.4 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - GENERATOR: "Visual Studio 15 2017" - GROUP: "SUPPORTED" - BOOST_ROOT: "C:/Libraries/boost_1_67_0" - PLATFORM: "win32" - REF_BUILD_ID: "323_1" - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - GENERATOR: "Visual Studio 15 2017 Win64" - GROUP: "EXPERIMENTAL" - BOOST_ROOT: "C:/Libraries/boost_1_67_0" - PLATFORM: "win64" - REF_BUILD_ID: "323_2" - -init: - - set SUT_BUILD_ID=%APPVEYOR_BUILD_NUMBER%_%APPVEYOR_JOB_NUMBER% - - set SWMM_HOME=%APPVEYOR_BUILD_FOLDER% - - set BUILD_HOME=buildprod - - set TEST_HOME=nrtestsuite - # See set values - - echo %APPVEYOR_BUILD_WORKER_IMAGE% - - echo %BUILD_HOME% - - echo %GENERATOR% - - echo %BOOST_ROOT% - - # Repo is cloned after init - -install: - # Install nrtest and dependencies for regression tests - - python -m pip install -r tools\requirements-appveyor.txt - -before_build: - - mkdir %BUILD_HOME% - - cd %BUILD_HOME% - - cmake -G "%GENERATOR%" - -DBUILD_TESTS=1 - -DBOOST_ROOT="%BOOST_ROOT%" - -DBoost_USE_STATIC_LIBS="ON" .. - -build_script: - - cmake --build . --config Release - -before_test: - - cd %SWMM_HOME% - - tools\before-test.cmd %PLATFORM% %REF_BUILD_ID% %SUT_BUILD_ID% %APPVEYOR_REPO_COMMIT% - -test_script: - # run unit tests - - cd %BUILD_HOME%\tests - - ctest -C Release - # Run regression tests - - cd %SWMM_HOME% - - tools\run-nrtest.cmd %REF_BUILD_ID% %SUT_BUILD_ID% - -on_success: - - cd %TEST_HOME%\benchmark - - appveyor PushArtifact receipt.json - - cd %SWMM_HOME% - - cd %BUILD_HOME%\bin\Release - - appveyor PushArtifact run-swmm.exe - - appveyor PushArtifact swmm5.dll - - appveyor PushArtifact swmm-output.dll - -on_failure: - - cd %TEST_HOME%\benchmark - # zip up the SUT benchmarks - - 7z a benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip .\swmm-%SUT_BUILD_ID% - - appveyor PushArtifact benchmark-%PLATFORM%-%SUT_BUILD_ID%.zip - -#cache: -# - C:\ProgramData\chocolatey\bin -> appveyor.yml -# - C:\ProgramData\chocolatey\lib -> appveyor.yml From 139529d06f77c587b95dad22bd7f94de85c22cd8 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Tue, 25 Aug 2020 11:04:42 -0400 Subject: [PATCH 173/266] Fix uniform loss rate in dynwave.c --- src/solver/dynwave.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/solver/dynwave.c b/src/solver/dynwave.c index df630c36d..2fc3e2c38 100644 --- a/src/solver/dynwave.c +++ b/src/solver/dynwave.c @@ -10,6 +10,7 @@ // 08/01/16 (5.1.011) // 05/10/18 (5.1.013) // 03/01/20 (5.1.014) +// 07/10/20 (5.1.015) // Author: L. Rossman (EPA) // M. Tryby (EPA) // R. Dickinson (CDM) @@ -50,6 +51,9 @@ // - updateNodeFlows() modified to subtract conduit evap. and seepage losses // from downstream node inflow instead of upstream node outflow. // +// Build 5.1.015: +// - The 5.1.014 change regarding conduit losses has been retracted. +// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -552,13 +556,13 @@ void updateNodeFlows(int i) // --- update total inflow & outflow at upstream/downstream nodes if ( q >= 0.0 ) { - Node[n1].outflow += q; //(5.1.014) - Node[n2].inflow += q - uniformLossRate; //(5.1.014) + Node[n1].outflow += q + uniformLossRate; //(5.1.015) + Node[n2].inflow += q; //(5.1.015) } else { - Node[n1].inflow -= q + uniformLossRate; //(5.1.014) - Node[n2].outflow -= q; //(5.1.014) + Node[n1].inflow -= q; //(5.1.015) + Node[n2].outflow -= q - uniformLossRate; //(5.1.015) } // --- add surf. area contributions to upstream/downstream nodes From 8869f1115c87d40a62e1d67a68d60066dc142290 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Tue, 25 Aug 2020 12:19:32 -0400 Subject: [PATCH 174/266] Insure space separates flow values in summary tables Resolves issue #57 . --- src/solver/statsrpt.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/solver/statsrpt.c b/src/solver/statsrpt.c index b08851abf..313a712cb 100644 --- a/src/solver/statsrpt.c +++ b/src/solver/statsrpt.c @@ -29,7 +29,8 @@ // - Pervious and impervious runoff added to Subcatchment Runoff Summary. // // Build 5.1.015: -// - Fixes bug in summary statistics when Report Start date > Start Date. +// - Fixes bug in summary statistics when Report Start Date > Start Date. +// - Insures that flow values listed in tables are separated by a space. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -87,8 +88,8 @@ void statsrpt_writeReport() // { // --- set number of decimal places for reporting flow values - if ( FlowUnits == MGD || FlowUnits == CMS ) strcpy(FlowFmt, "%9.3f"); - else strcpy(FlowFmt, "%9.2f"); + if ( FlowUnits == MGD || FlowUnits == CMS ) strcpy(FlowFmt, " %8.3f"); //(5.1.015) + else strcpy(FlowFmt, " %8.2f"); //(5.1.015) // --- volume conversion factor from ft3 to Mgal or Mliters if (UnitSystem == US) Vcf = 7.48 / 1.0e6; From c25b830ccde1c37370bc5ee82908bb12bca9926b Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Tue, 25 Aug 2020 12:44:30 -0400 Subject: [PATCH 175/266] Fix conversion of runon to depth for CN infiltration Resolves issue #25. --- src/solver/infil.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/solver/infil.c b/src/solver/infil.c index 68808a33d..92af52d2e 100644 --- a/src/solver/infil.c +++ b/src/solver/infil.c @@ -37,6 +37,7 @@ // // Build 5.1.015: // - Support added for multiple infiltration methods within a project. +// - Conversion of runon to ponded depth fixed for Curve Number infiltration. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -319,7 +320,7 @@ double infil_getInfil(int j, double tstep, double rainfall, Subcatch[j].infilModel); case CURVE_NUMBER: - depth += runon / tstep; + depth += runon * tstep; //(5.1.015) return curvenum_getInfil(&Infil[j].curveNum, tstep, rainfall, depth); default: From 9bb4125784ec9ba969aa8a5b26bd6fb2dbc91a46 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Tue, 25 Aug 2020 13:52:15 -0400 Subject: [PATCH 176/266] Modifies format of Link Flow Summary table Resolves issue #36. --- src/solver/statsrpt.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/solver/statsrpt.c b/src/solver/statsrpt.c index 313a712cb..c358afc77 100644 --- a/src/solver/statsrpt.c +++ b/src/solver/statsrpt.c @@ -31,6 +31,7 @@ // Build 5.1.015: // - Fixes bug in summary statistics when Report Start Date > Start Date. // - Insures that flow values listed in tables are separated by a space. +// - Adds a '-' entry to blank columns in the Link Flow Summary table. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -740,7 +741,7 @@ void writeLinkFlows() // --- print max flow / flow capacity for pumps if (Link[j].type == PUMP && Link[j].qFull > 0.0) { - fprintf(Frpt.file, " "); + fprintf(Frpt.file, " -"); fprintf(Frpt.file, " %6.2f", LinkStats[j].maxFlow / Link[j].qFull); continue; @@ -761,7 +762,6 @@ void writeLinkFlows() fprintf(Frpt.file, " %6.2f", LinkStats[j].maxFlow / Link[j].qFull / (double)Conduit[k].barrels); } - else fprintf(Frpt.file, " "); // --- print max/full depth fullDepth = Link[j].xsect.yFull; @@ -769,9 +769,10 @@ void writeLinkFlows() Orifice[k].type == BOTTOM_ORIFICE) fullDepth = 0.0; if (fullDepth > 0.0) { + if (Link[j].type != CONDUIT) + fprintf(Frpt.file, " - -"); fprintf(Frpt.file, " %6.2f", LinkStats[j].maxDepth / fullDepth); } - else fprintf(Frpt.file, " "); } WRITE(""); } From 87867bf5892d8ffd56c86b332ac9a6000a402128 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Wed, 26 Aug 2020 10:04:30 -0400 Subject: [PATCH 177/266] Revert "Modifies format of Link Flow Summary table" This reverts commit 9bb4125784ec9ba969aa8a5b26bd6fb2dbc91a46. --- src/solver/statsrpt.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/solver/statsrpt.c b/src/solver/statsrpt.c index c358afc77..313a712cb 100644 --- a/src/solver/statsrpt.c +++ b/src/solver/statsrpt.c @@ -31,7 +31,6 @@ // Build 5.1.015: // - Fixes bug in summary statistics when Report Start Date > Start Date. // - Insures that flow values listed in tables are separated by a space. -// - Adds a '-' entry to blank columns in the Link Flow Summary table. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -741,7 +740,7 @@ void writeLinkFlows() // --- print max flow / flow capacity for pumps if (Link[j].type == PUMP && Link[j].qFull > 0.0) { - fprintf(Frpt.file, " -"); + fprintf(Frpt.file, " "); fprintf(Frpt.file, " %6.2f", LinkStats[j].maxFlow / Link[j].qFull); continue; @@ -762,6 +761,7 @@ void writeLinkFlows() fprintf(Frpt.file, " %6.2f", LinkStats[j].maxFlow / Link[j].qFull / (double)Conduit[k].barrels); } + else fprintf(Frpt.file, " "); // --- print max/full depth fullDepth = Link[j].xsect.yFull; @@ -769,10 +769,9 @@ void writeLinkFlows() Orifice[k].type == BOTTOM_ORIFICE) fullDepth = 0.0; if (fullDepth > 0.0) { - if (Link[j].type != CONDUIT) - fprintf(Frpt.file, " - -"); fprintf(Frpt.file, " %6.2f", LinkStats[j].maxDepth / fullDepth); } + else fprintf(Frpt.file, " "); } WRITE(""); } From 7e5b1d663d6e6ce1a8112908ac8af874425bbaa5 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Wed, 26 Aug 2020 10:23:44 -0400 Subject: [PATCH 178/266] Fix possible race condition in dynwave.c Resolves issue #58 . --- src/solver/dynwave.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/solver/dynwave.c b/src/solver/dynwave.c index 8c901057e..b8f141f77 100644 --- a/src/solver/dynwave.c +++ b/src/solver/dynwave.c @@ -584,10 +584,15 @@ void updateNodeFlows(int i) //============================================================================= +//// --- modified for release 5.1.015 ---- //// //(5.1.015) int findNodeDepths(double dt) +// +// Input: dt = time step (sec) +// Output: returns TRUE if no change in depth at all nodes, FALSE otherwise +// Purpose: finds new depth at all nodes and checks if convergence achieved. +// { int i; - int converged; // convergence flag double yOld; // previous node depth (ft) // --- compute outfall depths based on flow in connecting link @@ -595,24 +600,25 @@ int findNodeDepths(double dt) // --- compute new depth for all non-outfall nodes and determine if // depth change from previous iteration is below tolerance - converged = TRUE; #pragma omp parallel num_threads(NumThreads) { #pragma omp for private(yOld) for ( i = 0; i < Nobjects[NODE]; i++ ) { + Xnode[i].converged = TRUE; if ( Node[i].type == OUTFALL ) continue; yOld = Node[i].newDepth; setNodeDepth(i, dt); - Xnode[i].converged = TRUE; if ( fabs(yOld - Node[i].newDepth) > HeadTol ) { - converged = FALSE; Xnode[i].converged = FALSE; } } } - return converged; + // --- return FALSE if any node failed to converge + for (i = 0; i < Nobjects[NODE]; i++) + if (Xnode[i].converged == FALSE) return FALSE; + return TRUE; } //============================================================================= From daefcd4dab8191a73a87630634af8a75e0260c9f Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Wed, 26 Aug 2020 10:41:55 -0400 Subject: [PATCH 179/266] Remove threading from findNodeDepths in dynwave.c --- src/solver/dynwave.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/solver/dynwave.c b/src/solver/dynwave.c index b8f141f77..cfa8bf0ae 100644 --- a/src/solver/dynwave.c +++ b/src/solver/dynwave.c @@ -600,9 +600,9 @@ int findNodeDepths(double dt) // --- compute new depth for all non-outfall nodes and determine if // depth change from previous iteration is below tolerance -#pragma omp parallel num_threads(NumThreads) -{ - #pragma omp for private(yOld) +//#pragma omp parallel num_threads(NumThreads) +//{ +// #pragma omp for private(yOld) for ( i = 0; i < Nobjects[NODE]; i++ ) { Xnode[i].converged = TRUE; @@ -614,7 +614,7 @@ int findNodeDepths(double dt) Xnode[i].converged = FALSE; } } -} +//} // --- return FALSE if any node failed to converge for (i = 0; i < Nobjects[NODE]; i++) if (Xnode[i].converged == FALSE) return FALSE; From b8bd0d207da04fecbd11e03e078c2f4a43c9a1d3 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Wed, 26 Aug 2020 10:57:44 -0400 Subject: [PATCH 180/266] Revert "Remove threading from findNodeDepths in dynwave.c" This reverts commit daefcd4dab8191a73a87630634af8a75e0260c9f. --- src/solver/dynwave.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/solver/dynwave.c b/src/solver/dynwave.c index cfa8bf0ae..b8f141f77 100644 --- a/src/solver/dynwave.c +++ b/src/solver/dynwave.c @@ -600,9 +600,9 @@ int findNodeDepths(double dt) // --- compute new depth for all non-outfall nodes and determine if // depth change from previous iteration is below tolerance -//#pragma omp parallel num_threads(NumThreads) -//{ -// #pragma omp for private(yOld) +#pragma omp parallel num_threads(NumThreads) +{ + #pragma omp for private(yOld) for ( i = 0; i < Nobjects[NODE]; i++ ) { Xnode[i].converged = TRUE; @@ -614,7 +614,7 @@ int findNodeDepths(double dt) Xnode[i].converged = FALSE; } } -//} +} // --- return FALSE if any node failed to converge for (i = 0; i < Nobjects[NODE]; i++) if (Xnode[i].converged == FALSE) return FALSE; From 46757aaa82ac38197bcd1902deb8a8706921af0b Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Wed, 26 Aug 2020 11:29:50 -0400 Subject: [PATCH 181/266] Revert "Revert "Modifies format of Link Flow Summary table"" This reverts commit 87867bf5892d8ffd56c86b332ac9a6000a402128. --- src/solver/statsrpt.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/solver/statsrpt.c b/src/solver/statsrpt.c index 313a712cb..c358afc77 100644 --- a/src/solver/statsrpt.c +++ b/src/solver/statsrpt.c @@ -31,6 +31,7 @@ // Build 5.1.015: // - Fixes bug in summary statistics when Report Start Date > Start Date. // - Insures that flow values listed in tables are separated by a space. +// - Adds a '-' entry to blank columns in the Link Flow Summary table. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -740,7 +741,7 @@ void writeLinkFlows() // --- print max flow / flow capacity for pumps if (Link[j].type == PUMP && Link[j].qFull > 0.0) { - fprintf(Frpt.file, " "); + fprintf(Frpt.file, " -"); fprintf(Frpt.file, " %6.2f", LinkStats[j].maxFlow / Link[j].qFull); continue; @@ -761,7 +762,6 @@ void writeLinkFlows() fprintf(Frpt.file, " %6.2f", LinkStats[j].maxFlow / Link[j].qFull / (double)Conduit[k].barrels); } - else fprintf(Frpt.file, " "); // --- print max/full depth fullDepth = Link[j].xsect.yFull; @@ -769,9 +769,10 @@ void writeLinkFlows() Orifice[k].type == BOTTOM_ORIFICE) fullDepth = 0.0; if (fullDepth > 0.0) { + if (Link[j].type != CONDUIT) + fprintf(Frpt.file, " - -"); fprintf(Frpt.file, " %6.2f", LinkStats[j].maxDepth / fullDepth); } - else fprintf(Frpt.file, " "); } WRITE(""); } From 3b3e3964852ccc83c202b661cfb378d1b32f53df Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Thu, 27 Aug 2020 12:12:31 -0400 Subject: [PATCH 182/266] Another fix for possible race condition in dynwave.c Resolves issue #58 . Statsrpt.c was modified so that this commit can pass the reg tests but it no longer fixes issue #36 . --- src/solver/dynwave.c | 9 ++++++--- src/solver/statsrpt.c | 5 +++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/solver/dynwave.c b/src/solver/dynwave.c index b8f141f77..464282d86 100644 --- a/src/solver/dynwave.c +++ b/src/solver/dynwave.c @@ -605,19 +605,22 @@ int findNodeDepths(double dt) #pragma omp for private(yOld) for ( i = 0; i < Nobjects[NODE]; i++ ) { - Xnode[i].converged = TRUE; if ( Node[i].type == OUTFALL ) continue; yOld = Node[i].newDepth; setNodeDepth(i, dt); + Xnode[i].converged = TRUE; if ( fabs(yOld - Node[i].newDepth) > HeadTol ) { Xnode[i].converged = FALSE; } } } - // --- return FALSE if any node failed to converge + // --- return FALSE if any non-Outfall node failed to converge for (i = 0; i < Nobjects[NODE]; i++) - if (Xnode[i].converged == FALSE) return FALSE; + { + if (Node[i].type == OUTFALL) continue; + if (Xnode[i].converged == FALSE) return FALSE; + } return TRUE; } diff --git a/src/solver/statsrpt.c b/src/solver/statsrpt.c index c358afc77..b64014179 100644 --- a/src/solver/statsrpt.c +++ b/src/solver/statsrpt.c @@ -701,6 +701,7 @@ void writeLinkFlows() { int j, k, days, hrs, mins; double v, fullDepth; + char nullValue = ' '; if (Nobjects[LINK] == 0) return; WRITE(""); @@ -741,7 +742,7 @@ void writeLinkFlows() // --- print max flow / flow capacity for pumps if (Link[j].type == PUMP && Link[j].qFull > 0.0) { - fprintf(Frpt.file, " -"); + fprintf(Frpt.file, " %c", nullValue); fprintf(Frpt.file, " %6.2f", LinkStats[j].maxFlow / Link[j].qFull); continue; @@ -770,7 +771,7 @@ void writeLinkFlows() if (fullDepth > 0.0) { if (Link[j].type != CONDUIT) - fprintf(Frpt.file, " - -"); + fprintf(Frpt.file, " %c %c", nullValue, nullValue); fprintf(Frpt.file, " %6.2f", LinkStats[j].maxDepth / fullDepth); } } From 281760958034ae6af7512a13fe8ce6a8142a9af3 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Thu, 27 Aug 2020 12:51:03 -0400 Subject: [PATCH 183/266] Update statsrpt.c --- src/solver/statsrpt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/solver/statsrpt.c b/src/solver/statsrpt.c index b64014179..7b4ba8b40 100644 --- a/src/solver/statsrpt.c +++ b/src/solver/statsrpt.c @@ -763,6 +763,7 @@ void writeLinkFlows() fprintf(Frpt.file, " %6.2f", LinkStats[j].maxFlow / Link[j].qFull / (double)Conduit[k].barrels); } + else fprintf(Frpt.file, " %c %c", nullValue, nullValue); // --- print max/full depth fullDepth = Link[j].xsect.yFull; @@ -770,10 +771,9 @@ void writeLinkFlows() Orifice[k].type == BOTTOM_ORIFICE) fullDepth = 0.0; if (fullDepth > 0.0) { - if (Link[j].type != CONDUIT) - fprintf(Frpt.file, " %c %c", nullValue, nullValue); fprintf(Frpt.file, " %6.2f", LinkStats[j].maxDepth / fullDepth); } + else fprintf(Frpt.file, " "); } WRITE(""); } From f2df1d2648f6fc4acb56360b04683059e07bb05b Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Sat, 29 Aug 2020 10:36:27 -0400 Subject: [PATCH 184/266] Fix max. infil. feature for modified Horton method Resolves issue #59 . --- src/solver/infil.c | 25 ++++++++++++++++++------- src/solver/infil.h | 2 ++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/solver/infil.c b/src/solver/infil.c index 92af52d2e..80a84c3bd 100644 --- a/src/solver/infil.c +++ b/src/solver/infil.c @@ -38,6 +38,7 @@ // Build 5.1.015: // - Support added for multiple infiltration methods within a project. // - Conversion of runon to ponded depth fixed for Curve Number infiltration. +// - Fixed max. infiltration feature for modified Horton method. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -371,6 +372,7 @@ void horton_initState(THorton *infil) { infil->tp = 0.0; infil->Fe = 0.0; + infil->Fmh = 0.0; //(5.1.015) } //============================================================================= @@ -501,6 +503,7 @@ double horton_getInfil(THorton *infil, double tstep, double irate, double depth) //============================================================================= +//// -------- Modified for release 5.1.015 -------- //// //(5.1.015) double modHorton_getInfil(THorton *infil, double tstep, double irate, double depth) // @@ -516,8 +519,8 @@ double modHorton_getInfil(THorton *infil, double tstep, double irate, // --- assign local variables double f = 0.0; double fp, fa; - double f0 = infil->f0 * InfilFactor; //(5.1.013) - double fmin = infil->fmin * InfilFactor; //(5.1.013) + double f0 = infil->f0 * InfilFactor; + double fmin = infil->fmin * InfilFactor; double df = f0 - fmin; double kd = infil->decay; double kr = infil->regen * Evap.recoveryFactor; @@ -538,9 +541,6 @@ double modHorton_getInfil(THorton *infil, double tstep, double irate, // --- case where there is water to infiltrate if ( fa > ZERO ) { - // --- saturated condition - if ( infil->Fmax > 0.0 && infil->Fe >= infil->Fmax ) return 0.0; - // --- potential infiltration fp = f0 - kd * infil->Fe; fp = MAX(fp, fmin); @@ -548,16 +548,27 @@ double modHorton_getInfil(THorton *infil, double tstep, double irate, // --- actual infiltration f = MIN(fa, fp); + // --- limit cumulative infiltration to Fmax + if (infil->Fmax > 0.0) + { + if (infil->Fmh + f * tstep > infil->Fmax) + f = (infil->Fmax - infil->Fmh) / tstep; + f = MAX(f, 0.0); + infil->Fmh += f * tstep; + } + // --- new cumulative infiltration minus seepage infil->Fe += MAX((f - fmin), 0.0) * tstep; - if ( infil->Fmax > 0.0 ) infil->Fe = MAX(infil->Fe, infil->Fmax); } // --- reduce cumulative infiltration for dry condition else if (kr > 0.0) { - infil->Fe *= exp(-kr * tstep); + df = exp(-kr * tstep); + infil->Fe *= df; infil->Fe = MAX(infil->Fe, 0.0); + infil->Fmh *= df; + infil->Fmh = MAX(infil->Fmh, 0.0); } return f; } diff --git a/src/solver/infil.h b/src/solver/infil.h index c4923e88d..c7d6fb4e4 100644 --- a/src/solver/infil.h +++ b/src/solver/infil.h @@ -20,6 +20,7 @@ // // Build 5.1.015: // - Support added for multiple infiltration methods within a project. +// - Fixed max. infiltration feature for modified Horton method. //----------------------------------------------------------------------------- #ifndef INFIL_H @@ -49,6 +50,7 @@ typedef struct //----------------------------- double tp; // present time on infiltration curve (sec) double Fe; // cumulative infiltration (ft) + double Fmh; // mod Horton cumulative infiltration (ft) //(5.1.015) } THorton; From d5085436c7e9d339cf9359ce4010dda84661873a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 31 Aug 2020 17:32:52 -0400 Subject: [PATCH 185/266] Configuring build Fixs bug in def file build Format cmake list files --- src/outfile/CMakeLists.txt | 8 +++++++- src/solver/CMakeLists.txt | 21 ++++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/outfile/CMakeLists.txt b/src/outfile/CMakeLists.txt index 8c0950520..524a5f329 100644 --- a/src/outfile/CMakeLists.txt +++ b/src/outfile/CMakeLists.txt @@ -52,6 +52,7 @@ install(TARGETS swmm-output DESTINATION "${LIBRARY_DIST}" FRAMEWORK DESTINATION "${TOOL_DIST}" + ) install( @@ -63,7 +64,12 @@ install( swmm-output-config.cmake ) -install(FILES ${SWMM_OUT_PUBLIC_HEADERS} DESTINATION "${INCLUDE_DIST}") +install( + FILES + ${SWMM_OUT_PUBLIC_HEADERS} + DESTINATION + "${INCLUDE_DIST}" +) # copy epanet-output to build tree for testing diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index fd5a00434..773243789 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -31,7 +31,7 @@ if(BUILD_DEF) add_library(swmm5 SHARED ${SWMM_SOURCES} - ${PROJECT_SOURCE_DIR/bindings/swmm5.def} + ${PROJECT_SOURCE_DIR}/bindings/swmm5.def ) else() @@ -71,11 +71,16 @@ target_include_directories(swmm5 $ ) -install(TARGETS swmm5 EXPORT swmm5Targets - RUNTIME DESTINATION "${TOOL_DIST}" - LIBRARY DESTINATION "${TOOL_DIST}" - ARCHIVE DESTINATION "${LIBRARY_DIST}" - FRAMEWORK DESTINATION "${TOOL_DIST}" +install(TARGETS swmm5 + EXPORT swmm5Targets + RUNTIME + DESTINATION "${TOOL_DIST}" + LIBRARY + DESTINATION "${TOOL_DIST}" + ARCHIVE + DESTINATION "${LIBRARY_DIST}" + FRAMEWORK + DESTINATION "${TOOL_DIST}" ) install( @@ -87,7 +92,9 @@ install( swmm5-config.cmake ) -install(FILES ${SWMM_PUBLIC_HEADERS} DESTINATION "${INCLUDE_DIST}") +install(FILES ${SWMM_PUBLIC_HEADERS} + DESTINATION "${INCLUDE_DIST}" +) # copy swmm5 to build tree for testing From 2e7814cf496ae8b49dca1914ad1be90a5781baef Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 31 Aug 2020 17:35:58 -0400 Subject: [PATCH 186/266] Update statsrpt.c Bug fix for link flow summary --- src/solver/statsrpt.c | 46 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/solver/statsrpt.c b/src/solver/statsrpt.c index 7b4ba8b40..8ea6002a5 100644 --- a/src/solver/statsrpt.c +++ b/src/solver/statsrpt.c @@ -96,7 +96,7 @@ void statsrpt_writeReport() if (UnitSystem == US) Vcf = 7.48 / 1.0e6; else Vcf = 28.317 / 1.0e6; - // --- report summary results for subcatchment runoff + // --- report summary results for subcatchment runoff if ( Nobjects[SUBCATCH] > 0 ) { if ( !IgnoreRainfall || @@ -165,11 +165,11 @@ void writeSubcatchRunoff() fprintf(Frpt.file, "\n %-20s", Subcatch[j].ID); x = SubcatchStats[j].precip * UCF(RAINDEPTH); fprintf(Frpt.file, " %10.2f", x/a); - x = SubcatchStats[j].runon * UCF(RAINDEPTH); + x = SubcatchStats[j].runon * UCF(RAINDEPTH); fprintf(Frpt.file, " %10.2f", x/a); x = SubcatchStats[j].evap * UCF(RAINDEPTH); fprintf(Frpt.file, " %10.2f", x/a); - x = SubcatchStats[j].infil * UCF(RAINDEPTH); + x = SubcatchStats[j].infil * UCF(RAINDEPTH); fprintf(Frpt.file, " %10.2f", x/a); x = SubcatchStats[j].impervRunoff * UCF(RAINDEPTH); //(5.1.013) fprintf(Frpt.file, " %10.2f", x/a); // @@ -249,7 +249,7 @@ void writeSubcatchLoads() { int i, j, p; double x; - double* totals; + double* totals; char units[15]; char subcatchLine[] = "--------------------"; char pollutLine[] = "--------------"; @@ -258,7 +258,7 @@ void writeSubcatchLoads() totals = (double *) calloc(Nobjects[POLLUT], sizeof(double)); if ( totals ) { - // --- print the table headings + // --- print the table headings WRITE(""); WRITE("****************************"); WRITE("Subcatchment Washoff Summary"); @@ -289,7 +289,7 @@ void writeSubcatchLoads() x = Subcatch[j].totalLoad[p]; totals[p] += x; if ( Pollut[p].units == COUNT ) x = LOG10(x); - fprintf(Frpt.file, "%14.3f", x); + fprintf(Frpt.file, "%14.3f", x); } } @@ -301,7 +301,7 @@ void writeSubcatchLoads() { x = totals[p]; if ( Pollut[p].units == COUNT ) x = LOG10(x); - fprintf(Frpt.file, "%14.3f", x); + fprintf(Frpt.file, "%14.3f", x); } free(totals); WRITE(""); @@ -396,7 +396,7 @@ void writeNodeFlows() VolUnitsWords2[UnitSystem]); else fprintf(Frpt.file, "%12.3f", (NodeInflow[j]-NodeOutflow[j]) / - NodeOutflow[j]*100.); + NodeOutflow[j]*100.); } WRITE(""); } @@ -422,7 +422,7 @@ void writeNodeSurcharge() if ( n == 0 ) { WRITE("Surcharging occurs when water rises above the top of the highest conduit."); - fprintf(Frpt.file, + fprintf(Frpt.file, "\n ---------------------------------------------------------------------" "\n Max. Height Min. Depth" "\n Hours Above Crown Below Rim"); @@ -470,14 +470,14 @@ void writeNodeFlooding() if ( n == 0 ) { WRITE("Flooding refers to all water that overflows a node, whether it ponds or not."); - fprintf(Frpt.file, + fprintf(Frpt.file, "\n --------------------------------------------------------------------------" "\n Total Maximum" "\n Maximum Time of Max Flood Ponded" "\n Hours Rate Occurrence Volume"); if ( RouteModel == DW ) fprintf(Frpt.file, " Depth"); else fprintf(Frpt.file, " Volume"); - fprintf(Frpt.file, + fprintf(Frpt.file, "\n Node Flooded %3s days hr:min %8s", FlowUnitWords[FlowUnits], VolUnitsWords[UnitSystem]); if ( RouteModel == DW ) fprintf(Frpt.file, " %6s", @@ -608,7 +608,7 @@ void writeOutfallLoads() // --- print table column headers fprintf(Frpt.file, - "\n -----------------------------------------------------------"); + "\n -----------------------------------------------------------"); for (p = 0; p < Nobjects[POLLUT]; p++) fprintf(Frpt.file, "--------------"); fprintf(Frpt.file, "\n Flow Avg Max Total"); @@ -662,14 +662,14 @@ void writeOutfallLoads() x = OutfallStats[k].totalLoad[p] * LperFT3 * Pollut[p].mcf; totals[p] += x; if ( Pollut[p].units == COUNT ) x = LOG10(x); - fprintf(Frpt.file, "%14.3f", x); + fprintf(Frpt.file, "%14.3f", x); } } // --- print total outfall loads outfallCount = Nnodes[OUTFALL]; fprintf(Frpt.file, - "\n -----------------------------------------------------------"); + "\n -----------------------------------------------------------"); for (p = 0; p < Nobjects[POLLUT]; p++) fprintf(Frpt.file, "--------------"); fprintf(Frpt.file, "\n System %7.2f ", @@ -683,11 +683,11 @@ void writeOutfallLoads() { x = totals[p]; if ( Pollut[p].units == COUNT ) x = LOG10(x); - fprintf(Frpt.file, "%14.3f", x); + fprintf(Frpt.file, "%14.3f", x); } WRITE(""); free(totals); - } + } } //============================================================================= @@ -701,7 +701,7 @@ void writeLinkFlows() { int j, k, days, hrs, mins; double v, fullDepth; - char nullValue = ' '; + char nullValue = '-'; if (Nobjects[LINK] == 0) return; WRITE(""); @@ -816,7 +816,7 @@ void writeFlowClass() fprintf(Frpt.file, " %4.2f", LinkStats[j].timeNormalFlow / (NewRoutingTime/1000.0)); fprintf(Frpt.file, " %4.2f", LinkStats[j].timeInletControl / - (NewRoutingTime/1000.0)); + (NewRoutingTime/1000.0)); } WRITE(""); } @@ -836,7 +836,7 @@ void writeLinkSurcharge() for ( j = 0; j < Nobjects[LINK]; j++ ) { if ( Link[j].type != CONDUIT || - Link[j].xsect.type == DUMMY ) continue; + Link[j].xsect.type == DUMMY ) continue; t[0] = LinkStats[j].timeSurcharged / 3600.0; t[1] = LinkStats[j].timeFullUpstream / 3600.0; t[2] = LinkStats[j].timeFullDnstream / 3600.0; @@ -846,7 +846,7 @@ void writeLinkSurcharge() for (i=0; i<5; i++) t[i] = MAX(0.01, t[i]); if (n == 0) { - fprintf(Frpt.file, + fprintf(Frpt.file, "\n ----------------------------------------------------------------------------" "\n Hours Hours " "\n --------- Hours Full -------- Above Full Capacity" @@ -902,7 +902,7 @@ void writePumpFlows() avgFlow /= PumpStats[k].totalPeriods; fprintf(Frpt.file, " %8.2f %10d %9.2f %9.2f %9.2f %9.3f %9.2f", pctUtilized, PumpStats[k].startUps, PumpStats[k].minFlow*UCF(FLOW), - avgFlow*UCF(FLOW), PumpStats[k].maxFlow*UCF(FLOW), + avgFlow*UCF(FLOW), PumpStats[k].maxFlow*UCF(FLOW), PumpStats[k].volume*Vcf, PumpStats[k].energy); pctOffCurve1 = PumpStats[k].offCurveLow; pctOffCurve2 = PumpStats[k].offCurveHigh; @@ -911,7 +911,7 @@ void writePumpFlows() pctOffCurve1 = pctOffCurve1 / PumpStats[k].utilized * 100.0; pctOffCurve2 = pctOffCurve2 / PumpStats[k].utilized * 100.0; } - fprintf(Frpt.file, " %6.1f %6.1f", pctOffCurve1, pctOffCurve2); + fprintf(Frpt.file, " %6.1f %6.1f", pctOffCurve1, pctOffCurve2); } WRITE(""); } @@ -926,7 +926,7 @@ void writeLinkLoads() char linkLine[] = "--------------------"; char pollutLine[] = "--------------"; - // --- print the table headings + // --- print the table headings WRITE(""); WRITE("***************************"); WRITE("Link Pollutant Load Summary"); From 066bf241caf47f7bb6c8fa2d072ea16d37c8717a Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 2 Sep 2020 16:42:25 -0400 Subject: [PATCH 187/266] Update CMakeLists.txt Fixes bug in def build --- src/solver/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index f109b5f49..18a958bc6 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -31,7 +31,7 @@ if(BUILD_DEF) add_library(swmm5 SHARED ${SWMM_SOURCES} - ${PROJECT_SOURCE_DIR/bindings/swmm5.def} + ${PROJECT_SOURCE_DIR}/bindings/swmm5.def ) else() From 8488ea2c078b0759d2f08ab96622452357a38238 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 2 Sep 2020 16:55:00 -0400 Subject: [PATCH 188/266] Update cmake lists --- CMakeLists.txt | 20 -------------------- src/outfile/CMakeLists.txt | 9 +++++++++ src/solver/CMakeLists.txt | 10 ++++++++++ 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 90694faf9..d584110c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,26 +54,6 @@ if(BUILD_TESTS) endif() -# Create target import scripts so other cmake projects can use swmm libraries -install( - EXPORT - swmm5Targets - DESTINATION - "${CONFIG_DIST}" - FILE - swmm5-config.cmake - ) - -install( - EXPORT - swmm-outputTargets - DESTINATION - "${CONFIG_DIST}" - FILE - swmm-output-config.cmake - ) - - # Create install rules for vcruntime.dll, msvcp.dll, vcomp.dll etc. set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) include(InstallRequiredSystemLibraries) diff --git a/src/outfile/CMakeLists.txt b/src/outfile/CMakeLists.txt index 5c5d2ed96..288c5d36d 100644 --- a/src/outfile/CMakeLists.txt +++ b/src/outfile/CMakeLists.txt @@ -48,6 +48,15 @@ install(TARGETS swmm-output EXPORT swmm-outputTargets FRAMEWORK DESTINATION "${TOOL_DIST}" ) +install( + EXPORT + swmm-outputTargets + DESTINATION + "${CONFIG_DIST}" + FILE + swmm-output-config.cmake + ) + install(FILES ${SWMM_OUT_PUBLIC_HEADERS} DESTINATION "${INCLUDE_DIST}") diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index 18a958bc6..428deb891 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -78,6 +78,16 @@ install(TARGETS swmm5 EXPORT swmm5Targets FRAMEWORK DESTINATION "${TOOL_DIST}" ) +# Create target import scripts so other cmake projects can use swmm libraries +install( + EXPORT + swmm5Targets + DESTINATION + "${CONFIG_DIST}" + FILE + swmm5-config.cmake + ) + install(FILES ${SWMM_PUBLIC_HEADERS} DESTINATION "${INCLUDE_DIST}") From afbb5181e19fe05e67d075b9d95968f8f6474e63 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Fri, 4 Sep 2020 14:18:07 -0400 Subject: [PATCH 189/266] Processing update swmm v5.1.15 (#61) In addition to improvements described in ReleaseNotes this PR includes: - Improvements to cmake build system - Testing scripts previously found in /tools have migrated to their own repo - Support for GitHub Actions has been added --- .github/workflows/build-and-test.yml | 71 ++ .gitignore | 15 +- Build.md | 4 +- CMakeLists.txt | 28 +- README.md | 16 +- ReleaseNotes.txt | 1307 ++++++++++++++++++++++++++ extern/boost.cmake | 35 + src/outfile/CMakeLists.txt | 9 + src/outfile/swmm_output.c | 4 +- src/run/main.c | 8 +- src/solver/CMakeLists.txt | 12 +- src/solver/consts.h | 14 +- src/solver/datetime.h | 7 + src/solver/dynwave.c | 15 +- src/solver/enums.h | 27 +- src/solver/error.c | 9 +- src/solver/error.h | 19 +- src/solver/exfil.h | 4 +- src/solver/findroot.h | 8 + src/solver/flowrout.c | 6 +- src/solver/funcs.h | 24 +- src/solver/globals.h | 16 +- src/solver/hash.h | 7 + src/solver/headers.h | 1 + src/solver/hotstart.c | 7 +- src/solver/include/swmm5.h | 12 +- src/solver/infil.c | 138 +-- src/solver/infil.h | 25 +- src/solver/inflow.c | 174 ++-- src/solver/input.c | 5 +- src/solver/keywords.h | 7 + src/solver/lid.c | 33 +- src/solver/lid.h | 16 +- src/solver/macros.h | 7 + src/solver/mathexpr.h | 7 + src/solver/mempool.h | 7 + src/solver/node.c | 15 +- src/solver/objects.h | 56 +- src/solver/odesolve.h | 7 + src/solver/project.c | 5 +- src/solver/qualrout.c | 7 +- src/solver/report.c | 42 +- src/solver/stats.c | 46 +- src/solver/statsrpt.c | 12 +- src/solver/subcatch.c | 47 +- src/solver/swmm5.c | 18 +- src/solver/text.h | 12 +- tests/CMakeLists.txt | 18 +- tests/Unit_Testing.md | 4 +- tools/Reg_Testing.md | 50 - tools/app-config.cmd | 58 -- tools/before-nrtest.cmd | 116 --- tools/make.cmd | 108 --- tools/requirements-win.txt | 22 - tools/run-nrtests.cmd | 114 --- 55 files changed, 2048 insertions(+), 813 deletions(-) create mode 100644 .github/workflows/build-and-test.yml create mode 100644 ReleaseNotes.txt create mode 100644 extern/boost.cmake delete mode 100644 tools/Reg_Testing.md delete mode 100644 tools/app-config.cmd delete mode 100644 tools/before-nrtest.cmd delete mode 100644 tools/make.cmd delete mode 100644 tools/requirements-win.txt delete mode 100644 tools/run-nrtests.cmd diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 000000000..b92ab7048 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,71 @@ +# +# build-and-test.yml - GitHub Actions CI for swmm-solver +# +# Created: May 19, 2020 +# Updated: May 31, 2020 +# +# Author: Michael E. Tryby +# US EPA - ORD/CESER +# + +name: Build and Test + +on: + push: + branches: [ master, develop, release, codedrop ] + pull_request: + branches: [ master, develop, release, codedrop ] + +jobs: + win_build: + runs-on: windows-2016 + defaults: + run: + shell: cmd + working-directory: ci-tools/windows + + env: + OMP_NUM_THREADS: 1 + PROJECT: swmm + BUILD_HOME: build + TEST_HOME: nrtests + NRTESTS_URL: https://github.com/SWMM-Project/swmm-nrtestsuite + + steps: + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Checkout submodule + uses: actions/checkout@v2 + with: + repository: michaeltryby/ci-tools + path: ci-tools + + - name: Setup python + uses: actions/setup-python@v2 + with: + python-version: '3.7' + + - name: Install requirements + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements-swmm.txt + + - name: Build and unit test + run: make.cmd /t + + - name: Build for reg test + run: make.cmd + + - name: Before reg test + run: before-nrtest.cmd + + - name: Run reg test + run: run-nrtests.cmd %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% + + - name: Upload artifacts + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: nrtest-artifacts + path: upload/ diff --git a/.gitignore b/.gitignore index a441c732a..548e7a2f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,16 @@ -# Eclipse Stuff -.metadata/ -.settings/ + + +.DS_Store + +build*/ +nrtest*/ + +*_export.h + +src/outfile/include/*_export.h +src/solver/include/*_export.h build/ nrtests/ +upload/ diff --git a/Build.md b/Build.md index b9352e45d..b0383fe78 100644 --- a/Build.md +++ b/Build.md @@ -21,12 +21,12 @@ Before the project can be built the required dependencies must be installed. - Build - Build Tools for Visual Studio 2017 - CMake 3.13 - + - ci-tools repository ### Build SWMM can be built with one simple command. ``` \> cd swmm -\swmm>tools\make.cmd +\swmm>ci-tools\windows\make.cmd ``` diff --git a/CMakeLists.txt b/CMakeLists.txt index 5918cfe51..4e4d65ae7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ # CMakeLists.txt - CMake configuration file for swmm-solver # # Created: July 11, 2019 -# Modified: Nov 25, 2019 +# Updated: May 29, 2020 # # Author: Michael E. Tryby # US EPA ORD/CESER @@ -17,12 +17,12 @@ endif() project(swmm-solver - VERSION 5.1.14 + VERSION 5.1.15 LANGUAGES C CXX ) # Append local dir to module search path -list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +# list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) # Sets the position independent code property for all targets set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -40,8 +40,7 @@ set(LIBRARY_DIST "lib") set(CONFIG_DIST "cmake") -option(BUILD_TESTS "Builds component tests (requires Boost)" OFF) -option(BUILD_DEF "Builds library with def file interface" OFF) +option(BUILD_TESTS "Build component tests (requires Boost)" OFF) # Add project subdirectories @@ -55,25 +54,6 @@ if(BUILD_TESTS) endif() -# Create target import scripts so other cmake projects can use swmm libraries -install( - EXPORT - swmm5Targets - DESTINATION - "${CONFIG_DIST}" - FILE - swmm5-config.cmake - ) - -install( - EXPORT - swmm-outputTargets - DESTINATION - "${CONFIG_DIST}" - FILE - swmm-output-config.cmake - ) - # Create install rules for vcruntime.dll, msvcp.dll, vcomp.dll etc. set(CMAKE_INSTALL_OPENMP_LIBRARIES TRUE) include(InstallRequiredSystemLibraries) diff --git a/README.md b/README.md index b84872ef1..09a0e1894 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,16 @@ Stormwater-Management-Model ORD Stormwater Management Model (aka "SWMM") -Introduction ------------- -This is the official SWMM source code repository maintained by US EPA ORD, NRMRL, Water Supply and Water Resources Division located in Cincinnati, Ohio. + +## Build Status +![Build and Test](https://github.com/USEPA/Stormwater-Management-Model/workflows/Build%20and%20Test/badge.svg) + + +## Introduction +This is the official SWMM source code repository maintained by US EPA Office of Research and Development, Center For Environmental Solutions & Emergency Response, Water Infrastructure Division located in Cincinnati, Ohio. SWMM is a dynamic hydrology-hydraulic water quality simulation model. It is used for single event or long-term (continuous) simulation of runoff quantity and quality from primarily urban areas. SWMM source code is written in the C Programming Language and released in the Public Domain. -Find Out More -------------- -The source code distributed here is identical to the code found at the official [SWMM Website](http://www2.epa.gov/water-research/storm-water-management-model-swmm). + +## Find Out More +The source code distributed here is identical to the code found at the official [SWMM Website](http://www2.epa.gov/water-research/storm-water-management-model-swmm). diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt new file mode 100644 index 000000000..eb74d01bb --- /dev/null +++ b/ReleaseNotes.txt @@ -0,0 +1,1307 @@ +SWMM 5.1 Update History +======================= + +------------------------- +Build 5.1.015 (July 2020) +------------------------- + +Engine Updates: + +1. A mix of infiltration methods can now be used within a project. + +2. Monthly adjustments to depression storage are now applied only to + the pervious area of subcatchments instead of to both pervious and + impervious areas. + +3. The Status Report now includes a grouped frequency table of the + variable routing time steps used during a simulation. + +4. An error in the average summary statistics reported for projects + with a reporting start date later than the simulation start date + was fixed. + +5. A fatal error is now issued if a storage node's area curve produces + a negative volume when extrapolated to the node's full depth. + +6. A pollutant mass balance error occurring when very shallow storage + units lost all inflow to flooding was fixed. + +7. Conduit evaporation and seepage losses are now applied directly to + the conduit's flow rate instead of just to the downstream node's + inflow to reduce water quality mass balance errors. + +8. Spurious values appearing for Final Water Table and Upper Mositure + Content in the Groundwater Summary Results Table for completely + impervious subcatchments were corrected. + +GUI Updates: + +1. The mouse wheel can now be used to zoom in or out of the Study Area + Map and when pressed be used to pan the map. + +2. The Subcatchment Infiltration Dialog was modified to accept a choice + of infiltration method. + +3. The Group Editor dialog was modified to allow a choice of infiltration + method and its parameters be assigned to a group of subcatchments. + +4. The Control Curve assigned to an LID unit's drain layer was being + ignored by the input file reader. + +5. Some problems with the Graph Options dialog for the Statistics Report + plots were fixed. + +6. Better support for 4K ultra HD monitors was added. + +7. The latter update required that the three toolbars on the + main window be combined into one to avoid resizing issues. + +------------------------ +Build 5.1.014 (03/01/20) +------------------------ + +Engine Updates: + +1. Fixed a refactoring bug that produced incorrect rainfall when the same + time series was used by one rain gage assigned to a RDII Unit Hydrograph + and also by another gage assigned to a subcatchment. + +2. Fixed skipping the first rain gage in a project when checking + if two gages have the same station ID but use different data files. + +3. Fixed a program crash when running projects with LID units but no + subcatchments. + +4. Fixed having LID underdrain pollutant loads incorrectly added to the + mass balance totals. + +5. Fixed the program from hanging when an LID unit sent its outflow back + onto the pervious area of its own subcatchment. + +6. Fixed a failure to re-initialize layer volumes as each LID unit is + evaluated (which could cause incorrect results for certain + combinations of LID units). + +7. Fixed street sweeping being ignored when the sweeping period + began with a higher day of the year than the end of the period. + +8. Fixed incorrect adjustments being made for conduit evaporation and + seepage losses under dynamic wave flow routing. + +9. Fixed having soil moisture deficit recovery be ignored for Green-Ampt + exfiltration from storage units. + +10. Fixed having node/link ID names be mistaken for option keywords + in the [REPORT] section of an input file. + +11. Fixed a possible program crash when using the option to report average + values within each reporting time interval instead of point values. + + +------------------------ +Build 5.1.013 (05/10/18) +------------------------ + +Engine Updates: + +1. A subcatchment's depression storage depth, its pervious surface roughness + (Mannings n) and its hydraulic conductivity can now be adjusted on a + monthly basis by assigning monthly time patterns to these properties. + For conductivity, a subcatchment-specific adjustment pattern will + override any project-wide set of climate adjustment factors. + +2. LID controls can now treat a designated portion of a subcatchment’s + pervious area runoff (previously they could only treat impervious area + runoff). + +3. Permeable pavement LID units subjected to clogging over time can now + have their permeability only partly restored at periodic time + intervals + +4. The following options were added to control flow out of LID units + through their underdrains: + - A storage layer water depth above which a closed drain automatically + opens. + - A storage layer water depth below which an open drain automatically + closes. + - A control curve that specifies how the nominal drain flow rate is + adjusted as a function of the head seen by the drain. + +5. Pollutant removal percentages can now be assigned to LID processes that + have underdrains. The removals apply to flow leaving the unit through the + drain and not to any surface overflow from the unit. + +6. The Subcatchment Runoff Summary Report now includes both pervious and + impervious total runoff volumes (prior to any LID treatment) for each + subcatchment. + +7. A choice of method used to handle surcharging has been added to the list + of Dynamic Wave options. The EXTRAN method continues to use the traditional + Surcharge Algorithm to update the head at surcharged nodes. The new SLOT + option attaches a Preissmann Slot to closed conduits flowing more than 98.5% + full that eliminates the need to switch to the Surcharge Algorithm for + surcharged nodes. + +8. A closed vessel can now be modeled as a storage unit node that is allowed + to pressurize up to a designated Surcharge Depth value. If this depth is 0 + then the unit is modeled as before as an open vessel. + +9. A weir's discharge coefficient can now be allowed to vary with head across + the weir by assigning it a Weir Curve (see Weir Properties). Weir curves + tabulate coefficient values at specific head levels. + +10. When the upstream offset of a regulator link is below the invert of its + downstream node it is now automatically raised only for Dynamic Wave flow + routing with a warning message issued. For other flow routing choices only + the warning message is issued and no automatic offset adjustment is made. + +11. Users can now choose to set a periodic time step for control rule evaluation. + If this step is 0 then rules are tested as before at every routing time step. + +12. The option was added to have time series results for a project's nodes and + links be reported as average values computed over a reporting time step + instead of being interpolated point values at the end of the reporting time + step. + +13. The following bugs were fixed: + - Unused rain gages are no longer examined when adjusting the wet runoff + time step. + - The surface inflow rate to a permeable pavement LID unit is not allowed + to exceed the pavement’s permeability. + - The Minimum Nodal Surface Area dynamic wave routing option was being used + as surface area always available at a node instead of an amount available + only when the surface area of the node's connecting links fell below it. + - The top width of a full closed rectangular cross section shape is now set + to 0 since it can no longer supply any surface area. + - The ‘C’ parameter value for a Mitered Corrugated Metal Arch culvert was + corrected. + - An incorrect reporting of flow continuity error for systems with backflow + through outfall nodes was fixed. + +GUI Updates + +1. Various property editors and dialog forms were updated to accomodate the newly + added engine features (monthly time patterns for subcatchment properties, LID + underdrain parameters, LID pollutant removal, choice of surcharge method, + storage unit surcharge depth, weir coefficient curve, control rule time step, + and reporting of average values). + +2. A bug that failed to record the choice of number of barrels for rectangular + conduits in the Cross-Section Editor form was fixed. + +3. A bug in the GetLinkOutVal function of the Uoutput.pas unit was fixed. + + +------------------------ +Build 5.1.012 (03/14/17) +------------------------ + +Engine Updates: + +1. The direct.h header is now only #included in the swmm5.c file when + compiled for Windows. (swmm5.c) + +2. Engine Update #7 in Build 5.1.011 (internally aligning the wet time + step with the reporting time step) was redacted since it caused + problems for certain combinations of time steps. (runoff.c) + +3. A subcatchment's bottom elevation is now used instead its parent + aquifer's value when saving a water table value to the binary results + file. (subcatch.c) + +4. A bug that failed to limit surface inflitration into a saturated rain + garden LID unit was fixed. (lidproc.c) + +5. Calculation of the maximum limit on LID drain flows was modified to + produce smoother results at low depths above the drain offset. + (lidproc.c) + +6. A variable used for reporting detailed LID results is now properly + initialized. (lid.c & lid.h) + +7. The occasional writing of duplicate lines to the detailed LID results + file was fixed. (lidproc.c) + +8. The conversion from conduit seepage rate per unit area to rate per unit + of length was changed to use top width instead of wetted perimeter since + only vertical seepage is assumed to occur. (link.c) + +9. The coefficient of the evaporation/seepage term in the dynamic wave + equation for updating conduit flow was corrected (from 1.5 to 2.5). + (dwflow.c) + +10. The Engels flow equation for side flow weirs was corrected (the original + equation used in SWMM 3 & 4 was incorrect). (link.c) + +11. Crest length reductions for end contractions are no longer used for + trapezoidal weirs. (link.c) + +12. The Slope Correction Factor for culverts with mitered inlets was corrected. + (culvert.c) + +13. An entry in the table of gravel roadway weir coefficients was corrected. + (roadway.c) + +14. The user supplied minimum slope option is now initialized to 0.0 + (meaning none is provided). (project.c) + +15. NO/YES are no longer accepted as attributes for the NORMAL_FLOW_LIMITED + dynamic wave simulation option (only SLOPE/FROUDE/BOTH are valid). + (project.c) + +16. Changes were made so that the Routing Events and Skip Steady Flow + options work correctly together. (routing.c & globals.h) + +17. Steady state periods with no flow routing no longer contribute to the + routing time step statistics. (stats.c and report.c) + +18. When compiling statistics on the frequency of full conduit flow the + number of barrels is now accounted for. (stats.c) + +19. Under kinematic wave or steady flow routing, the water level in + storage nodes that have no outflow links is now updated correctly + over time. (flowrout.c) + +20. The formula for the depth at maximum width for the Modified Basket Handle + cross section was corrected. (xsect.c) + + +GUI Updates: + +1. Profile plots now correctly update the main and axis title text when + changed via the Profile Plot Options dialog. Also the downstream + offset height of non-conduit links is set to 0 on the plot. + +2. The LID Control Editor now sets the Storage Layer Thickness to 0 when + a Rain Garden is selected as the type of LID being edited. + +3. An OnChange event handler was added to each of the LID Control Editor's + data fields to record when a value is changed. + +------------------------ +Build 5.1.011 (08/22/16) +------------------------ + +Engine Updates: + +1. Detailed flow routing can now be restricted to a set of pre-defined + event periods. The event periods are listed in an [EVENTS] section + of the SWMM input file, where each line contains a start date, + start time, end date and end time for each event. + +2. New functions, swmm_getError() and swmm_getWarnings(), were added to + the API used for interfacing SWMM with other applications (see the + updated Interfacing Guide for details). + +3. The error codes returned by the API functions (swmm_open, swmm_start, + swmm_step, etc.) were corrected. + +4. The new format for precipitation amounts in rainfall files downloaded + from the NCDC's Climate Data Online service are now recognized. + +5. Monthly adjustments for hydraulic conductivity are now also + applied to the internal Green-Ampt "Lu" parameter which varies with + the square root of conductivity. + +6. A check was added to insure that subcatchment imperviousness does + not exceed 100 percent. + +7. Runoff time steps are now adjusted to stay aligned with the Report + time step (making model validation easier). + +8. A time step correction was made when computing the outflow volume + produced by an outfall that sends its outflow back onto a designated + subcatchment. + +9. The LID routines were modified so that native soil infiltration is + satisfied first when it occurs along with underdrain flow, instead + of the other way around. + +10. The allowable offset height for an LID underdrain is no longer + limited to the top of the storage layer thus allowing upturned + drains to be modelled. + +11. The detailed LID report file now lists results by both date/time + and elapsed hours and reports water level instead of moisture + content for permeable pavement. + +12. If the offset of the opening of a regulator link is below its + downstream node invert it is now raised to the invert level + and a warning message is still issued. + +13. A weir with an open rectangular shape and non-zero slope parameter + will no longer generate an input error message - the slope value is + now ignored. + +14. An illegal array index bug that could occur when checking the pump + curve type for an Ideal Pump in dynamic wave flow routing was fixed. + +15. A redundant unit conversion of max. reported depth from the Node + Depth Summary table was removed. + +16. Node surcharging is now only reported for dynamic wave flow routing + and storage nodes are never classified as surcharged. + +17. A failure to convert a storage unit's surface area curve from metric + to internal units when computing bottom exfiltration was fixed. + +18. A bug that caused a link's TIMEOPEN variable (used in control rule + conditions) to be re-set to 0 when its setting changed from one + partly opened state to another was fixed. + +19. The Status Report no longer lists control actions taken by modulated + controls since they occur continuously over time and can produce an + enormous number of actions. + +20. A failure to convert a Roadway Weir's road width that was in metric + units was fixed. + +21. A bug that caused the saved link settings read from a hot start + file to be incorrect in a model containing pollutants was fixed. + +22. Rule premises can now include SIMULATION DAYOFYEAR as a condition, + where day of year is either in month/day format or a number between + 1 and 365. + +23. A refactoring bug that affected water quality mass balance results + for Steady Flow routing was fixed. + +24. The function that decodes the fractional part of a date/time value + into hours:minutes:seconds was modified so that rounding doesn't + cause the time to exceed 24 hours. + +25. Microsoft exception handling statements are now only enabled when + the engine is compiled with the Microsoft C compiler. + +GUI Updates: + +1. A new sub-category of simulation options named Events was added along + with an Event Editor dialog to allow users to restrict detailed flow + routing calculations to specific periods of time. See the Help file + topic under Reference | Special Dialog Forms | Events Editor. + +2. When a Rain Garden is selected in the LID Control Editor, the Storage + layer tab now appears so that a bottom Seepage Rate can be specified. + This fixes the problem of having no inflitration out of a Rain Garden. + +3. After a simulation has been completed the Run Status dialog box now + indicates if any warning messages have been issued. + +4. A previously uninitialized variable (elapsed simulation time) passed + between the GUI and the engine is now assigned an initial value. + +5. The ground surface line on a Profile Plot can now be made visible or + not via the Profile Plot Options dialog, with the default being visible. + +6. An option to use thick lines to outline conduits and the ground surface + in Profile Plots was added. + + +------------------------- +Build 5.1.010 (08/05/15) +------------------------- + +Engine Updates: + +1. A modified version of Green-Ampt infiltration (MODIFIED GREEN AMPT) was + added that no longer redistributes upper zone moisture deficit during + low rainfall events. The original authors of SWMM's Green-Ampt model + have endorsed this modified version. It will produce more infiltration + for storm events that begin with low rainfall intensities, such as the + SCS design storm distributions. + +2. A new type of weir, a ROADWAY weir, has been added. It models roadway + overtopping using the FHWA HDS-5 method and would typically be used in + parallel with a culvert conduit. + +3. Rule premises can now test whether a link has been open (or closed) for + a specific period of time. See the Help file for more details. + +4. Unsaturated hydraulic conductivity ("K") was added to the list of + variables that can be used in a user-supplied groundwater flow equation. + +5. A bug introduced in update 2 of release 5.1.008 that failed to include + infiltration from LID units into the groundwater routine was fixed. + +6. A bug that failed to properly initialize the flag indicating that one or + more LID controls was initially wet was fixed. + +7. Duplicate printing of the first line of an LID detailed report file was + corrected. + +8. The Hargreaves evaporation forumla was modified to use a 7-day running + average of daily temperatures, instead of just single day values, as + recommended by the formula's authors. + +9. Daily potential evapotranspiration (PET) was added as a system output + variable. + +10. The qualrout.c module was refactored to make it more compact and easier + to follow. + +11. Storage seepage and evaporation losses are now based on the storage volume + at the end, not the start, of the prior time step. + +12. The command line used to build the engine included in the "makefile" + for the GNU C/C++ compiler was corrected to include the OpenMP libraries. + +GUI Updates: + +1. Potential evapotranspiration (PET) was added as new system-wide variable + whose time series can be viewed with a graph or table. + +2. A bug in the Number of Threads dropdown list on the Dynamic Wave Options + dialog was fixed. + +3. Additional bugs in the Cross Section Editor remaining from changes made + in release 5.1.008 were fixed. + +4. Changes were made to accommodate the new Modified Green-Ampt infiltration + option and the new Roadway weir option. + +5. The automatic scaling of plots with all y-values the same was improved. + +6. The automatic scaling of profile plots was improved and the ground surface + line was removed to improve clarity. + +7. The function that automatically converts all link depth offsets to + elevation offsets now uses the node invert elevation value rather + than *' for a zero offset. + +8. A bug in using the Macro List on the Add-In Tools Properties form to + insert a selection into the Working Directory edit box was fixed. + + +-------------------------- +Build 5.1.009 (04/30/2015) +-------------------------- + +General: + +1. A missing DLL file used to parallelize SWMM's flow routing + routine was added to the installation package. + +Engine Updates: + +1. A re-factoring bug that prevented running simulations longer + than 68 years was fixed. + +2. An input parsing error that prevented the program from + recognizing a comparison between two variables in a + control rule premise was fixed. + +3. A new warning message was added for when a control rule + premise compares two different types of variables. + +4. When implementing 5.1.008 update 12, the runon to a + subcatchment fully occupied by LIDs was not being included + in the subcatchment's Summary Report. + +5. A bug was fixed that allowed LID units to return outflow to + a subcatchment's pervious area even though LIDs occupied the + entire subcatchment. + +6. For quality routing the definition of a dry conduit/storage + node was changed to <= 1 mm of depth to avoid concentrations + from blowing up due to evaporation losses. + +7. The units label for Total Inflow Volume in the Node Inflow + Summary table of a saved report file was corrected. + +GUI Updates: + +1. A bug introduced as a result of 5.1.008 GUI Update 11 that + prevented the name of a conduit's Transect or Shape Curve + from appearing in the Cross Section Editor was fixed. + +2. A nicer default axis scaling routine is now used for time + series and scatter plots. + +3. The user-supplied custom scaling for the vertical axis of a + profile plot is now recognized. + + + +-------------------------- +Build 5.1.008 (04/02/2015) +-------------------------- + +Engine Updates: + +New Features: +============= + +1. Monthly adjustments for hydraulic conductivity used for + rainfall infiltration and for exfiltration from storage + nodes and conduits was added. + +2. LID drains can now send their outflow to a different node + or subcatchment than the parent subcatchment in which they + were placed. + +3. Conveyance system Outfall nodes now have the option to + send their outflow onto a subcatchment, to simulate + irrigation or complex LID treatment options. + +4. A new LID practice, Rooftop Disconnection, has been added. + It allows one to explicitly model roof runoff with an + optional limit on the flow capacity of their downspouts. + Disconnection is specified by setting the Return To Pervious + Area field in the [LID_USAGE] section to 1. + +5. An optional soil layer has been added to Permeable Pavement + LIDs so that a sand filter or bedding layer beneath the + pavement can be modeled. + +6. Several new built-in variable names can now be used in + custom groundwater flow equations for porosity, unsaturated + hydraulic conductivity, infiltration rate, and percolation + rate. See the Help file for more details. + +7. A Groundwater Summary table has been added that reports + several groundwater statistics for each subcatchment. + +8. A new option, the Minimum Variable Time Step, was added that + limits the smallest time step that can be computed under + variable time stepping for dynamic wave flow routing. In + previous releases it was fixed at 0.5 seconds which remains + the default. The smallest value it can now have is 0.001 sec. + +9. The dynamic wave routing procedure was parallelized to take + advantage of multiple processors, making it run several times + faster. A new option, THREADS, sets the number of parallel + threads to use, where the default is 1. + +10. A new column was added to the Node Depth Summary report table + that shows the maximum depth recorded at the Reporting Time Step + so it can be compared to the maximum depth attained over all + routing time steps also shown in the table. + +11. Control rule premises can now contain conditions that compare + the values of a node or link variable at two different locations + (e.g. IF NODE 123 HEAD > NODE 456 HEAD) and node volume was + added as a condition clause variable. + +Improvements: +============= + +12. When a subcatchment with LID controls receives runon from another + source (e.g., a subcatchment, LID drain or outfall node) the + runon is now distributed only across the non-LID area of the sub- + catchment instead of the full area. If a single LID takes up the + full subcatchment area then the runon is directed onto the LID. + +13. Storage node HRT was added to the state variables saved in the + Hot Start file. + +14. The threshold value for reporting a non-zero runoff result + was changed from 0.001 cfs to 0.001 inches/hr. + +15. The calculation of overall flow routing mass balance was + modified to account for cases where some flow streams, like + total external inflow, are negative. + +16. The "Surface Runoff" label in the Runoff Continuity Report was + replaced with "Total Runoff" since the value reported consists + of both surface runoff and LID drain flow. + +17. The "Internal Outflow" label in the Flow Routing Continuity + Report was replaced with "Flooding Losses" to improve clarity. + +18. The pollutant washoff routines were moved to a new code module + (surfqual.c) and revised to account for the reduction in + pollutant load that results from runoff flow reduction by LID + units. + +19. Initial flows for Steady Flow routing are now ignored since they + are not used in the routing calculation and the initial volume + associated with them contributed to system mass balance error. + +20. The various types of lateral inflows to conveyance system nodes + are now evaluated at the date/time for the start of the routing + time step instead of at the end of the time step. + +21. The final runoff and routing time steps are adjusted to insure + that the simulation's total duration is not exceeded. + +22. When evaluating user-supplied math expressions, any NaN (Not a + Number) result (caused by an underflow, overflow or divide by zero) + is set to 0 so that the NaN doesn't propagate through subsequent + calculations. + +Bug Fixes: +========== + +23. The evaporation rates read from a time series would only change + when a new day was reached (even though values at more frequent + intervals were present) and could cause a run to stop pre- + maturely in some rare cases. + +24. The runoff read from a Hot Start file should have been assigned + to a subcatchment's newRunoff property, not to oldRunoff. + +25. An indexing bug that caused Hot Start files with snowmelt + parameters to be read incorrectly was fixed. + +26. The setting for a non-conduit link read from a Hot Start file + was not being used to initialize the link. + +27. A bug in adjusting snowmelt for snow covered area derived from + an areal depletion curve was fixed. + +28. Snowmelt should not have been included in the total + precipitation reported for a subcatchment since the snowfall + which produced it was already accounted for. + +29. When computing a flow rate through the Drainage Mat of a Green + Roof LID unit, the mat's void ratio was being applied to the + water depth instead of to the mat's area. + +30. The state of LID controls was not being considered when + choosing to use the wet or dry runoff time step which sometimes + lead to excessive LID continuity errors. + +31. A re-factoring bug that left reporting time in minutes instead + of hours in the detailed LID results file was fixed and results + are now written to the file at each runoff time step where the + state of the LID unit changes. + +32. Failure to initialize groundwater evaporation loss to 0 was + causing problems with the reported groundwater mass balance + for subcatchments that had no pervious area. + +33. Excessive continuity errors for systems having conduits with + large seepage rates was fixed. + +34. Pollutant loss through seepage in conduits and storage nodes was + not being included in the mass balance calculations. + +35. Concentrations in conduits and storage nodes were not being + increased to account for loss of water volume when evaporation + was occurring. + +36. Premature exiting of the routine that checks for capacity limited + links whenever a non-conduit link was encountered was fixed. + +37. A bug in identifying the percent of time that a conduit has either + end full was fixed. + +38. A re-factoring bug that prevented surcharged weirs (see Update 5 + for 5.1.007) from passing any flow was fixed. + +39. A bug in evaluating recursive calls to nodal water quality treatment + functions was fixed. + + +GUI Updates: + +1. The missing July - December column labels were restored on both + the evaporation and wind speed tables in the Climatology Editor. + +2. The label "Surface Water Height (Hsw)" in the Groundwater Flow + Editor was changed to "Surface Water Depth" to make clear that + it is the depth of water at the receiving node and not the height + of water above the aquifer bottom (or Hsw as shown in the dialog's + diagram). + +3. The label "Channel Bottom Height (Hcb)" also in the Groundwater + Flow Editor was changed to "Threshold Water Table Elev." to make + clear that it is an elevation and not a height above the aquifer + bottom (as Hcb is in the dialog's diagram). + +4. A Groundwater Summary table was added to the form that displays + summary results tables. + +5. Groundwater upper zone soil moisture and node lateral inflow are + now included in the abridged Hot Start file that the GUI can + produce (using the File | Export | Hot Start File command). + +6. The column labels in the various sections of the SWMM input file + generated by the GUI were modified to better match the labels used + to describe the input file format in Appendix D of the Users Manual. + +7. A "Route To" field was added to the Outfall Node property editor to + accommodate the new option of allowing outfall nodes to discharge + onto a subcatchment. + +8. The Dynamic Wave page of the Simulation Options dialog was modified + to include the new Minimum Routing Time Step option and the new + Number of Threads option. + +9. The LID Control Editor was updated to include the new Rooftop + Disconnection LID practice and the option to add a soil layer + to the Permeable Pavement practice. + +10. A Drain Outlet field was added to the LID Usage Editor. + +11. The conduit Cross Section Editor dialog was enhanced to display + a selectable list of standard size codes and their dimensions for + elliptical and arch pipes. + +12. Custom changes made to the Map's Legends are now saved with the + rest of a project's settings in its .ini file. + +13. Word wrapping was added to the Title/Notes display in the + Project Browser to make the contents easier to read. + +14. Modal dialog message windows now appear centered over the form that + generates them instead of in the middle of the full display screen. + +15. The style of all dropdown list boxes was changed to make them more + visually appealing. + + +------------------------- +Build 5.1.007 (9/15/2014) +------------------------- + +Engine Updates: + +1. A new feature that provides monthly adjustments for + temperature, evaporation rate, and rainfall was added. + +2. Support for reading the new GHCN-Daily climate data + files available from NCDC's Climate Data Online service + was added. + +3. In addition to lateral groundwater flow, a custom + equation can now also be used for seepage flow to + a deeper groundwater aquifer. + +4. The [GW_FLOW] section of the project file was renamed + to [GWF] and its format was changed to accommodate + both lateral and deep groundwater flow equations. + +5. A new Weir parameter was added that specifies if the + weir can surcharge using an orifice equation or not. + Surcharging was the only option in SWMM 5.0 but was + switched to no surcharing in earlier 5.1 releases. + This new parameter accommodates both closed top weirs + that can surcharge and open channel weirs that cannot. + +6. The formula used to recover infiltration capacity during + dry periods for the Modified Horton method was revised. + +7. The initial cumulative infiltration into the upper soil + zone for Green-Ampt infiltration had been incorrectly + set to the maximum value instead of zero. + +8. All of the Green-Ampt infiltration functions were + re-factored to make the code easier to follow. + +9. The calculation of infiltration out of the bottom of a + Bio-Retention Cell or Permeable Pavement LID unit with a + zero-depth storage layer was corrected. + +10. Most of the LID simulation routines were modified to + provide more accurate results under flooded conditions. + +11. Results written to the detailed LID results report now + always correspond to a full reporting time step. + +12. The name of the variable used to represent the height of + the receiving channel bottom in a user-defined groundwater + flow equation was corrected to match the name displayed + in the GUI's Groundwater Editor dialog (Hcb). + +13. A problem with the program crashing when a climate file + was used to provide evaporation rates for open channels + and storage nodes when runoff was not computed (as when + there were no subcatchments in the project) was fixed. + +14. Flow and pollutant routing mass balance accounting was + modified to correctly handle negative external inflows. + +15. The procedure for computing the area available for seepage + out of a storage node that has a tabular storage curve was + corrected. + +16. Seepage from storage units can now be modeled using Green- + Ampt infiltration, which makes the seepage rate a function + of storage level. The constant seepage rate option can + still be used by setting the G-A initial moisture deficit + to 0. + +17. The function that finds depth as a function of volume from + a storage curve was corrected for the case where the depth + falls within a portion of the curve where area is constant + with depth (i.e., vertical side walls). + +GUI Updates: + +1. The Object Toolbar was restored. + +2. A new page was added to the Climatology Editor to edit + values for monthly adjustments for temperature, evaporation, + and rainfall. + +3. A field for the new weir surcharge option was added to the + Weir Property editor. + +4. A problem with the current project being closed without + asking if it should be saved first whenever a new style + theme was selected from the Program Preferences dialog + has been fixed. + +5. The default seepage rate from an LID storage layer was changed. + +6. The Infiltration Editor was restored for editing Green-Ampt + parameters for storage unit seepage loss. + +7. The Groundwater Flow Equation Editor was extended to accept + equations for deep groundwater flow. + + +------------------------- +Build 5.1.006 (5/19/2014) +------------------------- + +Engine Updates: + +1. The updating of the next time that detailed LID results + should be written to file during a simulation was modified + to avoid an off-by-one error. + +2. The number of decimal places for hourly evaporation written + to a detailed LID report was increased. + +3. The amount of soil water available for evaporation in + LID units with soil layers wasn't being limited by the + water remaining below the wilting point. + +4. The equation that computes the rate of water infiltrating + into permeable pavement LIDs had a misplaced parenthesis. + +5. There was a units conversion error in computing the + contribution of a pollutant in direct precipitation + to the water quality on a subcatchment. + +GUI Updates: + +1. As a result of the switch to Delphi XE2 some components on + the Options and Climatology dialogs were not recording that + project data had changed after edits were made. + + +------------------------- +Build 5.1.005 (4/23/2014) +------------------------- + +Engine Updates: + +1. A problem with reading hydraulic results from a hot start + file was fixed. + +GUI Updates: + +1. The appearance of the Open File Dialog with preview panel + was improved. + +2. In the property Editor for storage nodes, the Ponded Area + property was made read-only since storage nodes are not + allowed to pond water. + +3. Some issues with pop-up topic windows in the Help file + being obscured by the main Help window were fixed. + + +------------------------- +Build 5.1.004 (4/14/2014) +------------------------- + +Engine Updates: + +1. Support for the Ignore RDII analysis option was added to + the engine. + +GUI Updates: + +1. A refactoring bug that ignored any changes to numerical + precision made in the Program Preferences dialog was fixed. + +2. Another refactoring bug from 5.1.003 that caused projects + with groundwater aquifers not to run was fixed. + + +------------------------ +Build 5.1.003 (4/8/2014) +------------------------ +Engine Updates: + +1. A new property, the Upper Zone Evap. Pattern, was added to + the Aquifer object. It allows one to adjust the aquifer's + upper zone evaporation fraction by month of the year. + +2. A bug in writing/reading RDII flows to the new binary RDII + file was fixed. + +GUI Updates: + +1. A refactoring bug that prevented SWMM from working + correctly for users with non-US Windows regional settings + was fixed. + +2. A refactoring bug the prevented the Group Delete feature from + working was fixed. + +4. Issues with stay-on-top forms obscuring modal dialog forms, + with the Browser panel disappearing if its width was made too + small and with not being able to browse the Help system when a + modal form had focus were fixed (these unforseen issues were + caused by the switch to Delphi XE2 from Delphi 7). + +5. The Aquifer Editor form was updated to accept the new upper + evaporation pattern property. + + +------------------------- +Build 5.1.002 (3/31/2014) +------------------------- +Engine Updates: + +1. A bug that prevented hotstart files with the latest format + from being read was fixed. + +2. Only non-ponded surface area is saved for use in the dynamic + wave surcharge algorithm (when water depth is close to the + node's crown elevation). + +GUI Updates: + +1. Creation of auxilary forms on startup was moved from the + main form's OnActivate event to its OnCreate event, while + creation of the map form was moved tothe OnShow event. + +2. The routines for saving and reading the main form's position + and size in the swmm5 .ini file were modified. + +3. A memory leak related to copying cells from the grid editor + used in various dialogs was fixed. + + +------------------------- +Build 5.1.001 (3/24/2014) +------------------------- +Engine Updates + +New Features: +============= +1. SWMM can now read the new file format for precipitation + data retrieved online from NOAA-NCDC. + +2. A new choice of infiltration method, the Modified Horton + method, has been added. This method uses the cumulative + infiltration in excess of the minimum rate as its state + variable (instead of time along the Horton curve), + providing a more accurate infiltration estimate when + low rainfall intensities occur. + +3. RDII interface files created internally by SWMM are now + saved in a binary format to reduce storage space. The ASCII + text format for these files is still supported for users + that find it desireable to create the files outside of SWMM. + +4. Two new categories of LID controls, one for Green Roofs and + another for Rain Gardens, have been added so they no longer + have to be configured from the Bio-Retention Cell control + (although that option still remains). The Green Roof uses + a new Drainage Mat layer to store and convey the water that + percolates through the soil layer. + +5. Users can now add their own groundwater outflow equation to + a subcatchment, to be used in place of or in addition to the + standard equation. Similar to treatment functions, the equation + can be any mathematical expression that uses the same ground- + water variables that appear in the standard equation. + +6. Evaporation of water from open channels has been added. + +7. A new conduit property named Seepage Rate (in/hr or mm/hr) + has been added to model uniform seepage along the bottom + and sloped sides of a conduit. + +8. Infiltration from storage units is now referred to as + seepage, to be consistent with seepage from conduits. The + only required parameter is a seepage rate (in/hr or mm/hr). + Previous data files that supply a set of Green-Ampt + infiltration parameters will still be recognized. + +9. Separate accounting and reporting of evaporation and + seepage losses in storage units is now made. + +10. Open rectangular channels now have a new parameter that + specifies if one or both side wall surfaces should be + ignored when computing a hydraulic radius (to provide + improved support for quasi-2D modeling of wide channels + and overland flooding). + +11. New Dynamic Wave Analysis options have been added for + the maximum number of iterations and head tolerance used + at each time step. The percentage of time steps where + convergence is not achieved is also now reported. + +12. Users can now set the flow tolerances that determine if + flow routing calculations can be skipped because steady + state conditions hold. + +13. Control rules can now use a conduit's OPEN/CLOSED status + in both premise conditions and action clauses. + +14. The meaning of the link view variable "Capacity" has been + changed. For conduits it is now the fraction of the full + cross section area filled by the flow, while it is the + control setting for all other types of links (the meaning + of the control setting varies by link type -- see the Help + file or the Users Manual). + +15. The link Froude number view variable has been replaced with + the link's flow volume, the subcatchment Losses variable has + been replaced by two new variables - Evaporation and + Infiltration, and upper groundwater zone Soil Moisture has + been added as a new view variable. + +16. The Node Inflows Summary table of the Status Report now + includes a new column that lists the mass balance error + in volume units for each node. + +17. A new summary table, Link Pollutant Load, has been added + that displays the total mass load of each pollutant that + flows through each link. + +Improvements: +============= +18. Using a Drain Delay time of 0 for Rain Barrel LIDs now means + that the barrel is allowed to drain continuously, even as it + is filling during wet weather periods. + +19. The requirement that an impervious surface must be dry + (have no more than 0.05 inches of standing water) before + it could be subjected to street sweeping has been dropped. + +20. After runoff ceases and a land surface goes dry due to + evaporation, any remaining mass of pollutant originating + from direct deposition or upstream runon is assumed to be + unavailabe for future washoff (it shows up as Remaining + Buildup in the mass balance report). + +21. The way that wet weather washoff inflow loads are + interpolated across a flow routing time step was modified + to produce a better match between the reported total runoff + load and total quality routing inflow load. + +22. The method used to select a time step for processing RDII + unit hydrographs was modified to consider the case where + K (the ratio of rising limb to falling limb duration) is + below 1.0. + +23. When the moisture content of the upper groundwater zone + reaches saturation, the depth of the lower saturated zone + is now set equal to the full aquifer depth (minus a small + tolerance). + +24. Conduits with negative slopes whose absolute value is + below the Minimum Slope option will have their slope + changed to the positive minimum value, thus allowing + them to be analyzed using the Steady Flow and Kinematic + Wave routing options. + +25. The Avg. Froude Number and Avg. Flow Change columns in the + Flow Classification Summary table have been replaced with the + fraction of time steps that flow is limited to normal flow + and the fraction of time steps that flow is inlet controlled + (for culverts). + +26. An error condition now occurs if a pump's startup depth + is less than its shutoff depth. + +27. Only the upstream node for orifice and weir links is now + checked to see if its maximum depth needs to be increased + to meet the top elevation of the orifice or weir opening. + +28. Weirs are no longer allowed to operate as an orifice when + they surcharge. Instead any excess flow will flood the + upstream node. + +29. A warning message is now written to the Status Report if + the crest elevation of a regulator link is below its + downstream node's invert. + +30. When a reporting time falls in between a computational time + step during which a pump's on/off status changes, the reported + pump flow is the value at either the start or end of the time + step depending on which is closer to the reporting time (i.e., + no interpolation is used). + +31. Control rule conditions can now accept elapsed time or + time of day values as decimal hours in addition to hours: + minutes:seconds. + +32. The test for a control rule condition equaling a specified + elapsed time or time of day was modified to more accurately + capture its occurrence. + +33. If the Water Quality analysis option is disabled then the + binary results file no longer contains any pollutant values + (of 0) for all time periods. + +34. Hot Start files now contain the complete state of the watershed + and conveyance system, so that future simulations can start up + correctly where they left off. + +35. The following changes to error reporting were made: + - Error 319 was re-numbered to 320 and a new Error 319 + was added for a rainfall data file with unknown format. + - Format errors in external time series files are now + listed as Error 363 (invalid data) instead of Error + 173 (time series out of sequence). + +36. Warning messages written to the Status Report are now + single spaced instead of double spaced. See report.c. + +37. The Link Summary table in the Status Report now lists conduits + with negative slopes in their original orientation instead of + in their reversed state. + +Bug Fixes: +========== +38. A refactoring bug from 5.0.022 that prevented snowmelt + from infiltrating has been fixed. + +39. Snowmelt rate during rainfall conditions and the updating + of the antecedent temperature index were were not being + converted from the six hour time interval used in Anderson's + original NWS snowmelt model to the hourly basis used in SWMM. + +40. A refactoring bug that failed to set the maximum number of + characters high enough for a line read from a user-prepared + rainfall data file has been fixed. + +41. The optional Maximum Volume parameter for Horton + infiltration was not allowing any recovery of infiltration + capacity between storm events. + +42. Evaporation from the lower groundwater zone was being + computed from the rate remaining after surface and upper + zone evaporation was considered instead of from the + unadjusted rate (with a reduction afterwards if it exceeds + the remaining available rate). + +43. An error in applying the Vegetation Volume Fraction parameter + to swales was corrected. + +44. The time from the last rainfall used to determine when a + Rain Barrel should begin to empty wasn't being computed + correctly. + +45. An erroneous error message for Rain Barrel LIDs with a + zero Void Ratio has been fixed (the Void Ratio parameter + should be ignored for Rain Barrels). + +46. The display of extraneous infiltration results in detailed + reports for Rain Barrel LIDs has been eliminated. + +47. The check on no street sweeping for a subcatchment during + wet periods was checking rainfall over the entire study + area instead of just the subcatchment. + +48. An erroneous warning message regarding negative offsets for + pumps when elevation offsets are used has been eliminated. + +49. A possible divide by zero error for trapezoidal channels + with zero bottom width has been eliminated. + +50. A program crash that occurred when the Ignore Routing + option was selected and results were to be saved to a + Routing Interface file has been fixed. + +51. Projects that had no subcatchments or had the Ignore + Runoff switch selected were not able to evaporate water + from storage units. + +52. Weekday and weekend hourly time patterns for Dry Weather + inflows are now correctly applied in a mutually exclusive + manner. + +53. The Node Flooding Summary table in the Status Report now + correctly lists the peak depth of ponded water above the + node's maximum depth (i.e., its rim or ground elevation) + instead of above its invert elevation. + +54. Occasional problems caused by the date/time functions not + returning an hour between 0 and 23 (for hourly time patterns) + and being off by 1 second (when writing results to outflow + interface files) have been fixed. + +55. A bug introduced in release 5.0.017 that caused the + concentration after first-order decay in a storage node to + be ignored has been fixed. + +56. A bug in the Total Elapsed Time listed at the end of the + Status Report for runs taking longer than 24 hours of + computer time was fixed. + +57. A correction was made for the slope correction factor used + for mitered culvert inlets. + +58. The procedure for finding the surface area of a storage unit + given its volume was corrected for the case where the + storage curve has a section of decreasing area with depth. + +59. The procedure for finding a cross-section area given a + section factor value was corrected for the case where the + section factor table does not have its highest value as + the last entry in the table. + +60. An error in computing the hydraulic radius of the Rectangular- + Triangular conduit shape as a function of flow depth was + corrected. + +GUI Updates + +1. The entire GUI code was ported from Delphi 7 into Delphi XE2. + +2. Different color themes for the user interface can be + selected from the Program Preferences dialog. + +3. The "Data" Browser panel is now named as the "Project" + Browser. + +4. The Object Toolbar has been eliminated. Visual objects + are now added to the map in the same manner as non-visual + objects -- by selecting their category from the Project + Browser and then clicking the Browser's "+" button (or + by selecting Project | Add... on the main menu). + +5. The LID Control and LID Usage editors were re-designed to + accomodate the new LID control options. + +6. Modifications were made to accept the new engine features + mentioned above (modified Horton infiltration, seepage rate + parameter for conduits, side wall option for rectangular + channels, and the additional Dynamic Wave routing options). + +7. Modifications were made to accept the updated set of output + view variables. + +8. The summary results tables that used to appear as part of the + Status Report have been moved into a separate Summary Report + that makes it easier to view and sort them. + +9. The Time Series Plot selection dialog was modified to allow + more than one kind of object/variable pair to be plotted. + +10. The Graph Options dialog was modified to allow a vertical + axis to be inverted (as when plotting an inverted rainfall + hyetograph on the same graph as a runoff hydrograph). + +11. The option to compute evaporation using the Hargreaves + equation wasn't being saved along with the rest of a project. + +12. If pollutants are defined for a project but the Water Quality + analysis option is not selected, then after a new analysis is + made pollutants will no longer be listed as theme variables in + the Map Browser nor will they be available for graphs, tables + or statistical reports. + +13. The columns for the [XSECTIONS] section of a saved project + file now includes a heading label for "Culvert Code". diff --git a/extern/boost.cmake b/extern/boost.cmake new file mode 100644 index 000000000..4f9c10aaa --- /dev/null +++ b/extern/boost.cmake @@ -0,0 +1,35 @@ +# +# CMakeLists.txt - CMake configuration file for swmm-solver/extern +# +# Created: March 16, 2020 +# Updated: May 21, 2020 +# +# Author: Michael E. Tryby +# US EPA - ORD/CESER +# + + +if(WIN32) + set(Boost_USE_STATIC_LIBS ON) +else() + set(Boost_USE_STATIC_LIBS OFF) + add_definitions(-DBOOST_ALL_DYN_LINK) +endif() + + +# Environment variable "BOOST_ROOT_X_XX_X" points to local install location +if (DEFINED ENV{BOOST_ROOT_1_72_0}) + set(BOOST_ROOT $ENV{BOOST_ROOT_1_72_0}) + +elseif(DEFINED ENV{BOOST_ROOT_1_67_0}) + set(BOOST_ROOT $ENV{BOOST_ROOT_1_67_0}) + +endif() + + +find_package(Boost 1.67.0 + COMPONENTS + unit_test_framework + ) + +include_directories (${Boost_INCLUDE_DIRS}) diff --git a/src/outfile/CMakeLists.txt b/src/outfile/CMakeLists.txt index 5c5d2ed96..288c5d36d 100644 --- a/src/outfile/CMakeLists.txt +++ b/src/outfile/CMakeLists.txt @@ -48,6 +48,15 @@ install(TARGETS swmm-output EXPORT swmm-outputTargets FRAMEWORK DESTINATION "${TOOL_DIST}" ) +install( + EXPORT + swmm-outputTargets + DESTINATION + "${CONFIG_DIST}" + FILE + swmm-output-config.cmake + ) + install(FILES ${SWMM_OUT_PUBLIC_HEADERS} DESTINATION "${INCLUDE_DIST}") diff --git a/src/outfile/swmm_output.c b/src/outfile/swmm_output.c index dae5dc8fb..84ae6594c 100644 --- a/src/outfile/swmm_output.c +++ b/src/outfile/swmm_output.c @@ -10,6 +10,8 @@ */ +#define _CRT_SECURE_NO_DEPRECATE + #include #include #include @@ -670,7 +672,7 @@ int EXPORT_OUT_API SMO_getSystemSeries(SMO_Handle p_handle, SMO_systemAttribute errorcode = 411; else { // loop over and build time series - for (k = 0; k < length; k++) + for (k = 0; k < len; k++) temp[k] = getSystemValue(p_data, startPeriod + k, attr); *outValueArray = temp; diff --git a/src/run/main.c b/src/run/main.c index b4af1f112..0d050cf69 100644 --- a/src/run/main.c +++ b/src/run/main.c @@ -10,7 +10,9 @@ // to be run with swmm5.dll. #include +#include #include + #include "swmm5.h" int main(int argc, char *argv[]) @@ -35,7 +37,7 @@ int main(int argc, char *argv[]) int msgLen = 127; time_t start; double runTime; - + version = swmm_getVersion(); vMajor = version / 10000; vMinor = (version - 10000 * vMajor) / 1000; @@ -94,10 +96,10 @@ int main(int argc, char *argv[]) } // --- Use the code below if you need to keep the console window visible -/* +/* printf(" Press Enter to continue..."); getchar(); */ return 0; -} \ No newline at end of file +} diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index f109b5f49..428deb891 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -31,7 +31,7 @@ if(BUILD_DEF) add_library(swmm5 SHARED ${SWMM_SOURCES} - ${PROJECT_SOURCE_DIR/bindings/swmm5.def} + ${PROJECT_SOURCE_DIR}/bindings/swmm5.def ) else() @@ -78,6 +78,16 @@ install(TARGETS swmm5 EXPORT swmm5Targets FRAMEWORK DESTINATION "${TOOL_DIST}" ) +# Create target import scripts so other cmake projects can use swmm libraries +install( + EXPORT + swmm5Targets + DESTINATION + "${CONFIG_DIST}" + FILE + swmm5-config.cmake + ) + install(FILES ${SWMM_PUBLIC_HEADERS} DESTINATION "${INCLUDE_DIST}") diff --git a/src/solver/consts.h b/src/solver/consts.h index 4421dd1eb..829b089fd 100644 --- a/src/solver/consts.h +++ b/src/solver/consts.h @@ -7,16 +7,21 @@ // 08/01/16 (Build 5.1.011) // 05/10/18 (Build 5.1.013) // 03/01/20 (Build 5.1.014) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman // // Various Constants //----------------------------------------------------------------------------- +#ifndef CONSTS_H +#define CONSTS_H + + //------------------ // General Constants //------------------ -#define VERSION 51014 +#define VERSION 51015 #define MAGICNUMBER 516114522 #define EOFMARK 0x1A // Use 0x04 for UNIX systems #define MAXTITLE 3 // Max. # title lines @@ -93,5 +98,8 @@ //--------------------------- // Token separator characters -//--------------------------- -#define SEPSTR " \t\n\r" +//--------------------------- +#define SEPSTR " \t\n\r" + + +#endif //CONSTS_H diff --git a/src/solver/datetime.h b/src/solver/datetime.h index 397f3180c..8c84b3b79 100644 --- a/src/solver/datetime.h +++ b/src/solver/datetime.h @@ -18,6 +18,10 @@ // - New getTimeStamp function added. //----------------------------------------------------------------------------- +#ifndef DATETIME_H +#define DATETIME_H + + typedef double DateTime; #define Y_M_D 0 @@ -61,3 +65,6 @@ void datetime_setDateFormat(int fmt); DateTime datetime_addSeconds(DateTime date1, double seconds); DateTime datetime_addDays(DateTime date1, DateTime date2); long datetime_timeDiff(DateTime date1, DateTime date2); + + +#endif //DATETIME_H diff --git a/src/solver/dynwave.c b/src/solver/dynwave.c index 294adc873..8c901057e 100644 --- a/src/solver/dynwave.c +++ b/src/solver/dynwave.c @@ -9,6 +9,8 @@ // 03/19/15 (5.1.008) // 08/01/16 (5.1.011) // 05/10/18 (5.1.013) +// 03/01/20 (5.1.014) +// 07/10/20 (5.1.015) // Author: L. Rossman (EPA) // M. Tryby (EPA) // R. Dickinson (CDM) @@ -49,6 +51,9 @@ // - updateNodeFlows() modified to subtract conduit evap. and seepage losses // from downstream node inflow instead of upstream node outflow. // +// Build 5.1.015: +// - Roll back the 5.1.014 change for conduit losses in updateNodeFlows(). +// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -403,7 +408,7 @@ void findLinkFlows(double dt) for ( i = 0; i < Nobjects[LINK]; i++) { if ( !isTrueConduit(i) ) - { + { if ( !Link[i].bypassed ) findNonConduitFlow(i, dt); updateNodeFlows(i); } @@ -551,13 +556,13 @@ void updateNodeFlows(int i) // --- update total inflow & outflow at upstream/downstream nodes if ( q >= 0.0 ) { - Node[n1].outflow += q; //(5.1.014) - Node[n2].inflow += q - uniformLossRate; //(5.1.014) + Node[n1].outflow += q + uniformLossRate; //(5.1.015) + Node[n2].inflow += q; //(5.1.015) } else { - Node[n1].inflow -= q + uniformLossRate; //(5.1.014) - Node[n2].outflow -= q; //(5.1.014) + Node[n1].inflow -= q; //(5.1.015) + Node[n2].outflow -= q - uniformLossRate; //(5.1.015) } // --- add surf. area contributions to upstream/downstream nodes diff --git a/src/solver/enums.h b/src/solver/enums.h index e76f27519..4bc50e3f8 100644 --- a/src/solver/enums.h +++ b/src/solver/enums.h @@ -35,10 +35,14 @@ // // Build 5.1.013: // - SURCHARGE_METHOD and RULE_STEP options added. -// - WEIR_CURVE added as a curve type. +// - WEIR_CURVE added as a curve type. // //----------------------------------------------------------------------------- +#ifndef ENUMS_H +#define ENUMS_H + + //------------------------------------- // Names of major object types //------------------------------------- @@ -113,18 +117,18 @@ // Cross section shape types //------------------------------------- enum XsectType { - DUMMY, // 0 + DUMMY, // 0 CIRCULAR, // 1 closed FILLED_CIRCULAR, // 2 closed RECT_CLOSED, // 3 closed - RECT_OPEN, // 4 - TRAPEZOIDAL, // 5 - TRIANGULAR, // 6 + RECT_OPEN, // 4 + TRAPEZOIDAL, // 5 + TRIANGULAR, // 6 PARABOLIC, // 7 - POWERFUNC, // 8 - RECT_TRIANG, // 9 + POWERFUNC, // 8 + RECT_TRIANG, // 9 RECT_ROUND, // 10 - MOD_BASKET, // 11 + MOD_BASKET, // 11 HORIZ_ELLIPSE, // 12 closed VERT_ELLIPSE, // 13 closed ARCH, // 14 closed @@ -193,7 +197,7 @@ //------------------------------------- // Computed node quantities //------------------------------------- - #define MAX_NODE_RESULTS 7 + #define MAX_NODE_RESULTS 7 enum NodeResultType { NODE_DEPTH, // water depth above invert NODE_HEAD, // hydraulic head @@ -410,7 +414,7 @@ enum CompatibilityType { enum PumpCurveType { TYPE1_PUMP, // flow varies stepwise with wet well volume - TYPE2_PUMP, // flow varies stepwise with inlet depth + TYPE2_PUMP, // flow varies stepwise with inlet depth TYPE3_PUMP, // flow varies with head delivered TYPE4_PUMP, // flow varies with inlet depth IDEAL_PUMP}; // outflow equals inflow @@ -479,3 +483,6 @@ enum NoneAllType { NONE, ALL, SOME}; + + +#endif //ENUMS_H diff --git a/src/solver/error.c b/src/solver/error.c index 843823878..17c0a0ed5 100644 --- a/src/solver/error.c +++ b/src/solver/error.c @@ -6,6 +6,7 @@ // Date: 03/20/14 (Build 5.1.001) // 03/19/15 (Build 5.1.008) // 08/05/15 (Build 5.1.010) +// 04/14/20 (Build 5.1.015) // Author: L. Rossman // // Error messages @@ -16,6 +17,8 @@ // Build 5.1.010: // - Text of Error 318 for rainfall data files modified. // +// Build 5.1.015: +// - Added new Error 140 for storage nodes. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -55,6 +58,8 @@ #define ERR138 \ "\n ERROR 138: Node %s has initial depth greater than maximum depth." #define ERR139 "\n ERROR 139: Regulator %s is the outlet of a non-storage node." +#define ERR140 \ +"\n ERROR 140: Storage node %s has negative volume at full depth." //(5.1.015) #define ERR141 \ "\n ERROR 141: Outfall %s has more than 1 inlet link or an outlet link." #define ERR143 "\n ERROR 143: Regulator %s has invalid cross-section shape." @@ -197,7 +202,7 @@ char* ErrorMsgs[] = ERR327, ERR329, ERR330, ERR331, ERR333, ERR335, ERR336, ERR337, ERR338, ERR339, ERR341, ERR343, ERR345, ERR351, ERR353, ERR355, ERR357, ERR361, ERR363, ERR401, ERR402, ERR403, ERR405, ERR501, ERR502, ERR503, ERR504, - ERR505, ERR506, ERR507, ERR508, ERR509}; + ERR505, ERR506, ERR507, ERR508, ERR509, ERR140}; //(5.1.015) int ErrorCodes[] = { 0, 101, 103, 105, 107, 108, 109, 110, 111, @@ -212,7 +217,7 @@ int ErrorCodes[] = 327, 329, 330, 331, 333, 335, 336, 337, 338, 339, 341, 343, 345, 351, 353, 355, 357, 361, 363, 401, 402, 403, 405, 501, 502, 503, 504, - 505, 506, 507, 508, 509}; + 505, 506, 507, 508, 509, 140}; //(5.1.015) char ErrString[256]; diff --git a/src/solver/error.h b/src/solver/error.h index 7ffef1b7e..20737960c 100644 --- a/src/solver/error.h +++ b/src/solver/error.h @@ -4,12 +4,17 @@ // Project: EPA SWMM5 // Version: 5.1 // Date: 03/20/14 (Build 5.1.001) +// 04/14/20 (Build 5.1.015) // Author: L. Rossman // // Error codes // //----------------------------------------------------------------------------- +#ifndef ERROR_H +#define ERROR_H + + enum ErrorType { //... Runtime Errors @@ -116,7 +121,7 @@ enum ErrorType { ERR_RAIN_FILE_SCRATCH, //313 72 ERR_RAIN_FILE_OPEN, //315 73 ERR_RAIN_FILE_DATA, //317 74 - ERR_RAIN_FILE_SEQUENCE, //318 75 + ERR_RAIN_FILE_SEQUENCE, //318 75 ERR_RAIN_FILE_FORMAT, //319 76 ERR_RAIN_IFACE_FORMAT, //320 77 ERR_RAIN_FILE_GAGE, //321 78 @@ -143,7 +148,7 @@ enum ErrorType { ERR_RDII_FILE_SCRATCH, //341 91 ERR_RDII_FILE_OPEN, //343 92 ERR_RDII_FILE_FORMAT, //345 93 - + //... Routing File Errors ERR_ROUTING_FILE_OPEN, //351 94 ERR_ROUTING_FILE_FORMAT, //353 95 @@ -165,13 +170,19 @@ enum ErrorType { ERR_API_INPUTNOTOPEN, //502 105 ERR_API_SIM_NRUNNING, //503 106 ERR_API_WRONG_TYPE, //504 107 - ERR_API_OBJECT_INDEX, //505 108 + ERR_API_OBJECT_INDEX, //505 108 ERR_API_POLLUT_INDEX, //506 109 ERR_API_INFLOWTYPE, //507 110 ERR_API_TSERIES_INDEX, //508 111 ERR_API_PATTERN_INDEX, //509 112 + + //... Additional Errors + ERR_STORAGE_VOLUME, //140 113 //(5.1.015) MAXERRMSG}; - + char* error_getMsg(int i); int error_getCode(int i); int error_setInpError(int errcode, char* s); + + +#endif //ERROR_H diff --git a/src/solver/exfil.h b/src/solver/exfil.h index 6958735cc..8315c08c4 100644 --- a/src/solver/exfil.h +++ b/src/solver/exfil.h @@ -12,6 +12,7 @@ #ifndef EXFIL_H #define EXFIL_H + //---------------------------- // EXFILTRATION OBJECT //---------------------------- @@ -32,4 +33,5 @@ int exfil_readStorageParams(int k, char* tok[], int ntoks, int n); void exfil_initState(int k); double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area); -#endif + +#endif //EXFIL_H diff --git a/src/solver/findroot.h b/src/solver/findroot.h index 253a4ba4e..c0ae10dff 100644 --- a/src/solver/findroot.h +++ b/src/solver/findroot.h @@ -5,8 +5,16 @@ // // Last modified on 11/19/13. //----------------------------------------------------------------------------- + +#ifndef FINDROOT_H +#define FINDROOT_H + + int findroot_Newton(double x1, double x2, double* rts, double xacc, void (*func) (double x, double* f, double* df, void* p), void* p); double findroot_Ridder(double x1, double x2, double xacc, double (*func)(double, void* p), void* p); + + +#endif //FINDROOT_H diff --git a/src/solver/flowrout.c b/src/solver/flowrout.c index b50b12787..675e39300 100644 --- a/src/solver/flowrout.c +++ b/src/solver/flowrout.c @@ -640,9 +640,9 @@ void setNewNodeState(int j, double dt) // --- update terminal storage nodes if ( Node[j].type == STORAGE ) - { - if ( Node[j].updated == FALSE ) - updateStorageState(j, Nobjects[LINK], NULL, dt); + { + if ( Node[j].updated == FALSE ) + updateStorageState(j, Nobjects[LINK], NULL, dt); return; } diff --git a/src/solver/funcs.h b/src/solver/funcs.h index bdc2a4fe0..b9da07048 100644 --- a/src/solver/funcs.h +++ b/src/solver/funcs.h @@ -32,6 +32,11 @@ // - Arguments to link_getLossRate function changed. // //----------------------------------------------------------------------------- + +#ifndef FUNCS_H +#define FUNCS_H + + void project_open(char *f1, char *f2, char *f3); void project_close(void); @@ -86,7 +91,7 @@ void report_writeSysStats(TSysStats* sysStats); void report_writeErrorMsg(int code, char* msg); void report_writeErrorCode(void); void report_writeInputErrorMsg(int k, int sect, char* line, long lineCount); -void report_writeWarningMsg(char* msg, char* id); +void report_writeWarningMsg(char* msg, char* id); void report_writeTseriesErrorMsg(int code, TTable *tseries); void inputrpt_writeInput(void); @@ -275,7 +280,7 @@ void stats_report(void); void stats_updateCriticalTimeCount(int node, int link); void stats_updateFlowStats(double tStep, DateTime aDate, int stepCount, int steadyState); -void stats_updateSubcatchStats(int subcatch, double rainVol, +void stats_updateSubcatchStats(int subcatch, double rainVol, double runonVol, double evapVol, double infilVol, double impervVol, double pervVol, double runoffVol, double runoff); //(5.1.013) void stats_updateGwaterStats(int j, double infil, double evap, @@ -356,12 +361,12 @@ void node_getResults(int node, double wt, float x[]); int inflow_readExtInflow(char* tok[], int ntoks); int inflow_readDwfInflow(char* tok[], int ntoks); int inflow_readDwfPattern(char* tok[], int ntoks); -int inflow_setExtInflow(int j, int param, int type, - int tSeries, int basePat, double cf, +int inflow_setExtInflow(int j, int param, int type, + int tSeries, int basePat, double cf, double baseline, double sf); -int inflow_validate(int param, int type, int tSeries, - int basePat, double *cf); - +int inflow_validate(int param, int type, int tSeries, + int basePat, double *cf); + void inflow_initDwfInflow(TDwfInflow* inflow); void inflow_initDwfPattern(int pattern); @@ -471,7 +476,7 @@ int shape_validate(TShape *shape, TTable *curve); int controls_create(int n); void controls_delete(void); int controls_addRuleClause(int rule, int keyword, char* Tok[], int nTokens); -int controls_evaluate(DateTime currentTime, DateTime elapsedTime, +int controls_evaluate(DateTime currentTime, DateTime elapsedTime, double tStep); //----------------------------------------------------------------------------- @@ -518,3 +523,6 @@ void writecon(char *s); // writes string to console DateTime getDateTime(double elapsedMsec); // convert elapsed time to date void getElapsedTime(DateTime aDate, // convert elapsed date int* days, int* hrs, int* mins); + + +#endif //FUNCS_H diff --git a/src/solver/globals.h b/src/solver/globals.h index 1d7c9207e..400f35b9b 100644 --- a/src/solver/globals.h +++ b/src/solver/globals.h @@ -10,6 +10,7 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman // // Global Variables @@ -26,7 +27,7 @@ // // Build 5.1.011: // - Changed WarningCode to Warnings (# warnings issued) -// - Added error message text as a variable. +// - Added error message text as a variable. // - Added elapsed simulation time (in decimal days) variable. // - Added variables associated with detailed routing events. // @@ -35,8 +36,15 @@ // // Build 5.1.013: // - CrownCutoff and RuleStep added as analysis option variables. +// +// Build 5.1.015: +// - Fixes bug in summary statistics when Report Start date > Start Date. //----------------------------------------------------------------------------- +#ifndef GLOBALS_H +#define GLOBALS_H + + EXTERN TFile Finp, // Input file Fout, // Output file @@ -52,7 +60,8 @@ EXTERN TFile EXTERN long Nperiods, // Number of reporting periods - StepCount, // Number of routing steps used + TotalStepCount, // Total routing steps used //(5.1.015) + ReportStepCount, // Reporting routing steps used //(5.1.015) NonConvergeCount; // Number of non-converging steps EXTERN char @@ -166,3 +175,6 @@ EXTERN TTable* Tseries; // Array of time series tables EXTERN TTransect* Transect; // Array of transect data EXTERN TShape* Shape; // Array of custom conduit shapes EXTERN TEvent* Event; // Array of routing events + + +#endif //GLOBALS_H diff --git a/src/solver/hash.h b/src/solver/hash.h index 26f1684fa..90ac1ca22 100644 --- a/src/solver/hash.h +++ b/src/solver/hash.h @@ -4,6 +4,10 @@ // Header file for Hash Table module hash.c. //----------------------------------------------------------------------------- +#ifndef HASH_H +#define HASH_H + + #define HTMAXSIZE 1999 #define NOTFOUND -1 @@ -21,3 +25,6 @@ int HTinsert(HTtable *, char *, int); int HTfind(HTtable *, char *); char *HTfindKey(HTtable *, char *); void HTfree(HTtable *); + + +#endif //HASH_H diff --git a/src/solver/headers.h b/src/solver/headers.h index 5f76a8a6e..eafda8a2c 100644 --- a/src/solver/headers.h +++ b/src/solver/headers.h @@ -10,6 +10,7 @@ // // DO NOT CHANGE THE ORDER OF THE #INCLUDE STATEMENTS //----------------------------------------------------------------------------- + #include #include "consts.h" #include "macros.h" diff --git a/src/solver/hotstart.c b/src/solver/hotstart.c index be81670bd..33c005e45 100644 --- a/src/solver/hotstart.c +++ b/src/solver/hotstart.c @@ -8,6 +8,7 @@ // 04/23/14 (Build 5.1.005) // 03/19/15 (Build 5.1.008) // 08/01/16 (Build 5.1.011) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman (EPA) // // Hot Start file functions. @@ -37,6 +38,8 @@ // Build 5.1.011: // - Link control setting bug when reading a hot start file fixed. // +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -378,7 +381,7 @@ void saveRunoff(void) // Infiltration state (max. of 6 elements) for (j=0; j= 0 ) + { + m = i; + --ntoks; + } + // --- number of input tokens depends on infiltration model m - if ( m == HORTON ) n = 5; + if ( m == HORTON ) n = 5; else if ( m == MOD_HORTON ) n = 5; else if ( m == GREEN_AMPT ) n = 4; else if ( m == MOD_GREEN_AMPT ) n = 4; else if ( m == CURVE_NUMBER ) n = 4; - else return 0; - if ( ntoks < n ) return error_setInpError(ERR_ITEMS, ""); + else return 0; + if ( ntoks < n ) return error_setInpError(ERR_ITEMS, ""); + // --- parse numerical values from tokens for (i = 0; i < 5; i++) x[i] = 0.0; for (i = 1; i < n; i++) { - if ( ! getDouble(tok[i], &x[i-1]) ) + if (!getDouble(tok[i], &x[i - 1])) return error_setInpError(ERR_NUMBER, tok[i]); } @@ -183,18 +181,19 @@ int infil_readParams(int m, char* tok[], int ntoks) return error_setInpError(ERR_NUMBER, tok[n]); } - // --- assign parameter values to infil. object + // --- assign parameter values to infil, infilModel object Subcatch[j].infil = j; + Subcatch[j].infilModel = m; switch (m) { case HORTON: - case MOD_HORTON: status = horton_setParams(&HortInfil[j], x); + case MOD_HORTON: status = horton_setParams(&Infil[j].horton, x); break; case GREEN_AMPT: case MOD_GREEN_AMPT: - status = grnampt_setParams(&GAInfil[j], x); + status = grnampt_setParams(&Infil[j].grnAmpt, x); break; - case CURVE_NUMBER: status = curvenum_setParams(&CNInfil[j], x); + case CURVE_NUMBER: status = curvenum_setParams(&Infil[j].curveNum, x); break; default: status = TRUE; } @@ -204,49 +203,47 @@ int infil_readParams(int m, char* tok[], int ntoks) //============================================================================= -void infil_initState(int j, int m) +void infil_initState(int j) // // Input: j = subcatchment index -// m = infiltration method code // Output: none // Purpose: initializes state of infiltration for a subcatchment. // { - switch (m) + switch (Subcatch[j].infilModel) { case HORTON: - case MOD_HORTON: horton_initState(&HortInfil[j]); break; + case MOD_HORTON: horton_initState(&Infil[j].horton); break; case GREEN_AMPT: case MOD_GREEN_AMPT: - grnampt_initState(&GAInfil[j]); break; - case CURVE_NUMBER: curvenum_initState(&CNInfil[j]); break; + grnampt_initState(&Infil[j].grnAmpt); break; + case CURVE_NUMBER: curvenum_initState(&Infil[j].curveNum); break; } } //============================================================================= -void infil_getState(int j, int m, double x[]) +void infil_getState(int j, double x[]) // // Input: j = subcatchment index -// m = infiltration method code -// Output: none +// Output: x = subcatchment's infiltration state // Purpose: retrieves the current infiltration state for a subcatchment. // { - switch (m) + switch (Subcatch[j].infilModel) { case HORTON: - case MOD_HORTON: horton_getState(&HortInfil[j], x); break; + case MOD_HORTON: horton_getState(&Infil[j].horton, x); break; case GREEN_AMPT: case MOD_GREEN_AMPT: - grnampt_getState(&GAInfil[j],x); break; - case CURVE_NUMBER: curvenum_getState(&CNInfil[j], x); break; + grnampt_getState(&Infil[j].grnAmpt, x); break; + case CURVE_NUMBER: curvenum_getState(&Infil[j].curveNum, x); break; } } //============================================================================= -void infil_setState(int j, int m, double x[]) +void infil_setState(int j, double x[]) // // Input: j = subcatchment index // m = infiltration method code @@ -254,14 +251,14 @@ void infil_setState(int j, int m, double x[]) // Purpose: sets the current infiltration state for a subcatchment. // { - switch (m) + switch (Subcatch[j].infilModel) { case HORTON: - case MOD_HORTON: horton_setState(&HortInfil[j], x); break; + case MOD_HORTON: horton_setState(&Infil[j].horton, x); break; case GREEN_AMPT: case MOD_GREEN_AMPT: - grnampt_setState(&GAInfil[j],x); break; - case CURVE_NUMBER: curvenum_setState(&CNInfil[j], x); break; + grnampt_setState(&Infil[j].grnAmpt, x); break; + case CURVE_NUMBER: curvenum_setState(&Infil[j].curveNum, x); break; } } @@ -295,11 +292,10 @@ void infil_setInfilFactor(int j) //============================================================================= -double infil_getInfil(int j, int m, double tstep, double rainfall, +double infil_getInfil(int j, double tstep, double rainfall, double runon, double depth) // // Input: j = subcatchment index -// m = infiltration method code // tstep = runoff time step (sec) // rainfall = rainfall rate (ft/sec) // runon = runon rate from other sub-areas or subcatchments (ft/sec) @@ -308,22 +304,23 @@ double infil_getInfil(int j, int m, double tstep, double rainfall, // Purpose: computes infiltration rate depending on infiltration method. // { - switch (m) + switch (Subcatch[j].infilModel) { case HORTON: - return horton_getInfil(&HortInfil[j], tstep, rainfall+runon, depth); + return horton_getInfil(&Infil[j].horton, tstep, rainfall+runon, depth); case MOD_HORTON: - return modHorton_getInfil(&HortInfil[j], tstep, rainfall+runon, + return modHorton_getInfil(&Infil[j].horton, tstep, rainfall+runon, depth); case GREEN_AMPT: case MOD_GREEN_AMPT: - return grnampt_getInfil(&GAInfil[j], tstep, rainfall+runon, depth, m); + return grnampt_getInfil(&Infil[j].grnAmpt, tstep, rainfall+runon, depth, + Subcatch[j].infilModel); case CURVE_NUMBER: depth += runon / tstep; - return curvenum_getInfil(&CNInfil[j], tstep, rainfall, depth); + return curvenum_getInfil(&Infil[j].curveNum, tstep, rainfall, depth); default: return 0.0; @@ -341,7 +338,7 @@ int horton_setParams(THorton *infil, double p[]) // { int k; - for (k=0; k<5; k++) if ( p[k] < 0.0 ) return FALSE; + for (k = 0; k < 5; k++) if ( p[k] < 0.0 ) return FALSE; // --- max. & min. infil rates (ft/sec) infil->f0 = p[0] / UCF(RAINFALL); @@ -566,6 +563,21 @@ double modHorton_getInfil(THorton *infil, double tstep, double irate, //============================================================================= +void grnampt_getParams(int j, double p[]) +// +// Input: j = subcatchment index +// p[] = array of parameter values +// Output: none +// Purpose: retrieves Green-Ampt infiltration parameters for a subcatchment. +// +{ + p[0] = Infil[j].grnAmpt.S * UCF(RAINDEPTH); // Capillary suction head (ft) + p[1] = Infil[j].grnAmpt.Ks * UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) + p[2] = Infil[j].grnAmpt.IMDmax; // Max. init. moisture deficit +} + +//============================================================================= + int grnampt_setParams(TGrnAmpt *infil, double p[]) // // Input: infil = ptr. to Green-Ampt infiltration object diff --git a/src/solver/infil.h b/src/solver/infil.h index cf208e234..c4923e88d 100644 --- a/src/solver/infil.h +++ b/src/solver/infil.h @@ -7,6 +7,7 @@ // 09/15/14 (Build 5.1.007) // 08/05/15 (Build 5.1.010) // 05/10/18 (Build 5.1.013) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman (US EPA) // // Public interface for infiltration functions. @@ -16,11 +17,15 @@ // // Build 5.1.013: // - New function infil_setInfilFactor() added. +// +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. //----------------------------------------------------------------------------- #ifndef INFIL_H #define INFIL_H + //--------------------- // Enumerated Constants //--------------------- @@ -93,19 +98,21 @@ extern TCurveNum* CNInfil; //----------------------------------------------------------------------------- // Infiltration Methods //----------------------------------------------------------------------------- -void infil_create(int subcatchCount, int model); +void infil_create(int n); void infil_delete(void); -int infil_readParams(int model, char* tok[], int ntoks); -void infil_initState(int area, int model); -void infil_getState(int j, int m, double x[]); -void infil_setState(int j, int m, double x[]); -void infil_setInfilFactor(int j); //(5.1.013) -double infil_getInfil(int area, int model, double tstep, double rainfall, - double runon, double depth); +int infil_readParams(int m, char* tok[], int ntoks); +void infil_initState(int j); +void infil_getState(int j, double x[]); +void infil_setState(int j, double x[]); +void infil_setInfilFactor(int j); +double infil_getInfil(int area, double tstep, double rainfall, double runon, + double depth); +void grnampt_getParams(int j, double p[]); int grnampt_setParams(TGrnAmpt *infil, double p[]); void grnampt_initState(TGrnAmpt *infil); double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, double depth, int modelType); -#endif + +#endif //INFIL_H diff --git a/src/solver/inflow.c b/src/solver/inflow.c index e11e15086..42976c13e 100644 --- a/src/solver/inflow.c +++ b/src/solver/inflow.c @@ -71,11 +71,11 @@ int inflow_readExtInflow(char* tok[], int ntoks) Tseries[tseries].refersTo = EXTERNAL_INFLOW; } - // --- assign type & cf values for a FLOW inflow - if (param == -1) - { - type = FLOW_INFLOW; - } + // --- assign type & cf values for a FLOW inflow + if (param == -1) + { + type = FLOW_INFLOW; + } // --- do the same for a pollutant inflow if ( ntoks >= 4 && param > -1) @@ -115,9 +115,9 @@ int inflow_readExtInflow(char* tok[], int ntoks) basePat = project_findObject(TIMEPATTERN, tok[7]); if ( basePat < 0 ) return error_setInpError(ERR_NAME, tok[7]); } - - return(inflow_setExtInflow(j, param, type, tseries, basePat, - cf, baseline, sf)); + + return(inflow_setExtInflow(j, param, type, tseries, basePat, + cf, baseline, sf)); } int inflow_validate(int param, int type, int tseries, int basePat, double *cf) @@ -130,49 +130,49 @@ int inflow_validate(int param, int type, int tseries, int basePat, double *cf) // Output: cf = Unit Conversion // Return: returns Error Code { - int errcode = 0; - // Validate param - if (param >= Nobjects[POLLUT]) - { - errcode = ERR_API_POLLUT_INDEX; - } - // Validate Type - else if (type != FLOW_INFLOW - && type != CONCEN_INFLOW - && type != MASS_INFLOW) - { - errcode = ERR_KEYWORD; - } - // Validate Timeseries Index - else if (tseries >= Nobjects[TSERIES]) - { - errcode = ERR_API_TSERIES_INDEX; - } - // Validate Timepattern Index - else if (basePat >= Nobjects[TIMEPATTERN]) - { - errcode = ERR_API_PATTERN_INDEX; - } - else - { - // --- assign type & cf values for a FLOW inflow - if ( type == FLOW_INFLOW ) - { - *cf = 1.0/UCF(FLOW); - } - // --- include LperFT3 term in conversion factor for MASS_INFLOW - else if ( type == MASS_INFLOW ) - { - *cf /= LperFT3; - } - } - - return(errcode); + int errcode = 0; + // Validate param + if (param >= Nobjects[POLLUT]) + { + errcode = ERR_API_POLLUT_INDEX; + } + // Validate Type + else if (type != FLOW_INFLOW + && type != CONCEN_INFLOW + && type != MASS_INFLOW) + { + errcode = ERR_KEYWORD; + } + // Validate Timeseries Index + else if (tseries >= Nobjects[TSERIES]) + { + errcode = ERR_API_TSERIES_INDEX; + } + // Validate Timepattern Index + else if (basePat >= Nobjects[TIMEPATTERN]) + { + errcode = ERR_API_PATTERN_INDEX; + } + else + { + // --- assign type & cf values for a FLOW inflow + if ( type == FLOW_INFLOW ) + { + *cf = 1.0/UCF(FLOW); + } + // --- include LperFT3 term in conversion factor for MASS_INFLOW + else if ( type == MASS_INFLOW ) + { + *cf /= LperFT3; + } + } + + return(errcode); } int inflow_setExtInflow(int j, int param, int type, int tseries, int basePat, - double cf, double baseline, double sf) + double cf, double baseline, double sf) // Purpose: This function assigns property values to the inflow object // Inputs: j = Node index // param = FLOW (-1) or pollutant index @@ -185,45 +185,45 @@ int inflow_setExtInflow(int j, int param, int type, int tseries, int basePat, // Return: returns Error Code { - int errcode = 0; - - // Validate Inflow - errcode = inflow_validate(param, type, tseries, basePat, &cf); - - if (errcode == 0) - { - TExtInflow* inflow; // external inflow object - - // --- check if an external inflow object for this constituent already exists - inflow = Node[j].extInflow; - while ( inflow ) - { - if ( inflow->param == param ) break; - inflow = inflow->next; - } - - // --- if it doesn't exist, then create it - if ( inflow == NULL ) - { - inflow = (TExtInflow *) malloc(sizeof(TExtInflow)); - if ( inflow == NULL ) - { - return error_setInpError(ERR_MEMORY, ""); - } - inflow->next = Node[j].extInflow; - Node[j].extInflow = inflow; - } - - // Assigning Values to the inflow object - inflow->param = param; - inflow->type = type; - inflow->tSeries = tseries; - inflow->cFactor = cf; - inflow->sFactor = sf; - inflow->baseline = baseline; - inflow->basePat = basePat; - inflow->extIfaceInflow = 0.0; - } + int errcode = 0; + TExtInflow* inflow; // external inflow object + + // Validate Inflow + errcode = inflow_validate(param, type, tseries, basePat, &cf); + + if (errcode == 0) + { + + // --- check if an external inflow object for this constituent already exists + inflow = Node[j].extInflow; + while ( inflow ) + { + if ( inflow->param == param ) break; + inflow = inflow->next; + } + + // --- if it doesn't exist, then create it + if ( inflow == NULL ) + { + inflow = (TExtInflow *) malloc(sizeof(TExtInflow)); + if ( inflow == NULL ) + { + return error_setInpError(ERR_MEMORY, ""); + } + inflow->next = Node[j].extInflow; + Node[j].extInflow = inflow; + } + + // Assigning Values to the inflow object + inflow->param = param; + inflow->type = type; + inflow->tSeries = tseries; + inflow->cFactor = cf; + inflow->sFactor = sf; + inflow->baseline = baseline; + inflow->basePat = basePat; + inflow->extIfaceInflow = 0.0; + } return(errcode); } @@ -265,7 +265,7 @@ double inflow_getExtInflow(TExtInflow* inflow, DateTime aDate) double sf = inflow->sFactor; // scaling factor double blv = inflow->baseline; // baseline value double tsv = 0.0; // time series value - double extIfaceInflow = inflow->extIfaceInflow;// external interfacing inflow + double extIfaceInflow = inflow->extIfaceInflow;// external interfacing inflow if ( p >= 0 ) { diff --git a/src/solver/input.c b/src/solver/input.c index b36fd4289..390b6f088 100644 --- a/src/solver/input.c +++ b/src/solver/input.c @@ -6,6 +6,7 @@ // Date: 03/20/14 (Build 5.1.001) // 09/15/14 (Build 5.1.007) // 08/01/16 (Build 5.1.011) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman // // Input data processing functions. @@ -16,6 +17,8 @@ // Build 5.1.011: // - Support added for reading hydraulic event dates. // +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -478,7 +481,7 @@ int parseLine(int sect, char *line) return subcatch_readSubareaParams(Tok, Ntokens); case s_INFIL: - return infil_readParams(InfilModel, Tok, Ntokens); + return infil_readParams(InfilModel, Tok, Ntokens); //(5.1.015) case s_AQUIFER: j = Mobjects[AQUIFER]; diff --git a/src/solver/keywords.h b/src/solver/keywords.h index 3724a0c89..af89e0d58 100644 --- a/src/solver/keywords.h +++ b/src/solver/keywords.h @@ -17,6 +17,10 @@ // - New keyword array defined for surcharge method. //----------------------------------------------------------------------------- +#ifndef KEYWORDS_H +#define KEYWORDS_H + + extern char* BuildupTypeWords[]; extern char* CurveTypeWords[]; extern char* DividerTypeWords[]; @@ -65,3 +69,6 @@ extern char* VolUnitsWords2[]; extern char* WashoffTypeWords[]; extern char* WeirTypeWords[]; extern char* XsectTypeWords[]; + + +#endif //KEYWORDS_H diff --git a/src/solver/lid.c b/src/solver/lid.c index 4dcf02a1d..57dd9c60d 100644 --- a/src/solver/lid.c +++ b/src/solver/lid.c @@ -13,6 +13,7 @@ // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) // 03/01/20 (Build 5.1.014) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman (US EPA) // // This module handles all data processing involving LID (Low Impact @@ -77,6 +78,9 @@ // Build 5.1.014: // - Fixed bug in creating LidProcs when there are no subcatchments. // - Fixed bug in adding underdrain pollutant loads to mass balances. +// +// Build 5.1.015: +// - Support added for mutiple infiltration methods within a project. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -262,7 +266,7 @@ void lid_create(int lidCount, int subcatchCount) return; } } - + //... initialize LID groups for (j = 0; j < GroupCount; j++) LidGroups[j] = NULL; @@ -761,7 +765,7 @@ int readDrainData(int j, char* toks[], int ntoks) for (i = 0; i < 6; i++) x[i] = 0.0; //(5.1.013) for (i = 2; i < 8; i++) // { - if ( ntoks > i && ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) //(5.1.013) + if ( ntoks > i && ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) //(5.1.013) return error_setInpError(ERR_NUMBER, toks[i]); } @@ -803,7 +807,7 @@ int readDrainMatData(int j, char* toks[], int ntoks) //... read numerical parameters if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); - if ( LidProcs[j].lidType != GREEN_ROOF ) return 0; + if ( LidProcs[j].lidType != GREEN_ROOF ) return 0; for (i = 2; i < 5; i++) { if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) @@ -1096,12 +1100,12 @@ void validateLidProc(int j) //... set storage layer parameters of a green roof if ( LidProcs[j].lidType == GREEN_ROOF ) - { - LidProcs[j].storage.thickness = LidProcs[j].drainMat.thickness; - LidProcs[j].storage.voidFrac = LidProcs[j].drainMat.voidFrac; - LidProcs[j].storage.clogFactor = 0.0; - LidProcs[j].storage.kSat = 0.0; - } + { + LidProcs[j].storage.thickness = LidProcs[j].drainMat.thickness; + LidProcs[j].storage.voidFrac = LidProcs[j].drainMat.voidFrac; + LidProcs[j].storage.clogFactor = 0.0; + LidProcs[j].storage.kSat = 0.0; + } } //============================================================================= @@ -1155,11 +1159,10 @@ void validateLidGroup(int j) //... assign vegetative swale infiltration parameters if ( LidProcs[k].lidType == VEG_SWALE ) { - if ( InfilModel == GREEN_AMPT || InfilModel == MOD_GREEN_AMPT ) + if ( Subcatch[j].infilModel == GREEN_AMPT || //(5.1.015) + Subcatch[j].infilModel == MOD_GREEN_AMPT ) //(5.1.015) { - p[0] = GAInfil[j].S * UCF(RAINDEPTH); - p[1] = GAInfil[j].Ks * UCF(RAINFALL); - p[2] = GAInfil[j].IMDmax; + grnampt_getParams(j, p); //(5.1.015) if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) { strcpy(Msg, LidProcs[k].ID); @@ -1720,10 +1723,10 @@ void findNativeInfil(int j, double tStep) //... otherwise find infil. rate for the subcatchment's rainfall + runon else { - NativeInfil = infil_getInfil(j, InfilModel, tStep, + NativeInfil = infil_getInfil(j, tStep, Subcatch[j].rainfall, Subcatch[j].runon, - getSurfaceDepth(j)); //(5.1.008) + getSurfaceDepth(j)); //(5.1.015) } //... see if there is any groundwater-imposed limit on infil. diff --git a/src/solver/lid.h b/src/solver/lid.h index 93ee2b779..146b58e2a 100644 --- a/src/solver/lid.h +++ b/src/solver/lid.h @@ -18,7 +18,7 @@ // - Detailed LID reporting modified. // // Build 5.1.011: -// - Water depth replaces moisture content for LID's pavement layer. +// - Water depth replaces moisture content for LID's pavement layer. // - Arguments for lidproc_saveResults() modified. // // Build 5.1.012: @@ -41,6 +41,7 @@ #ifndef LID_H #define LID_H + #include #include #include @@ -51,8 +52,8 @@ //----------------------------------------------------------------------------- enum LidTypes { BIO_CELL, // bio-retention cell - RAIN_GARDEN, // rain garden - GREEN_ROOF, // green roof + RAIN_GARDEN, // rain garden + GREEN_ROOF, // green roof INFIL_TRENCH, // infiltration trench POROUS_PAVEMENT, // porous pavement RAIN_BARREL, // rain barrel @@ -73,7 +74,7 @@ typedef struct { double thickness; // depression storage or berm ht. (ft) double voidFrac; // available fraction of storage volume - double roughness; // surface Mannings n + double roughness; // surface Mannings n double surfSlope; // land surface slope (fraction) double sideSlope; // swale side slope (run/rise) double alpha; // slope/roughness term in Manning eqn. @@ -184,7 +185,7 @@ typedef struct int drainNode; // node receiving drain flow TLidRptFile* rptFile; // pointer to detailed report file - TGrnAmpt soilInfil; // infil. object for biocell soil layer + TGrnAmpt soilInfil; // infil. object for biocell soil layer double surfaceDepth; // depth of ponded water on surface layer (ft) double paveDepth; // depth of water in porous pavement layer double soilMoisture; // moisture content of biocell soil layer @@ -192,7 +193,7 @@ typedef struct // net inflow - outflow from previous time step for each LID layer (ft/s) double oldFluxRates[MAX_LAYERS]; - + double dryTime; // time since last rainfall (sec) double oldDrainFlow; // previous drain flow (cfs) double newDrainFlow; // current drain flow (cfs) @@ -236,4 +237,5 @@ double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, double ucfRainDepth); -#endif + +#endif //LID_H diff --git a/src/solver/macros.h b/src/solver/macros.h index 998e92ac0..6f10ae464 100644 --- a/src/solver/macros.h +++ b/src/solver/macros.h @@ -7,6 +7,10 @@ // Author: L. Rossman //----------------------------------------------------------------------------- +#ifndef MACROS_H +#define MACROS_H + + //-------------------------------------------------- // Macro to test for successful allocation of memory //-------------------------------------------------- @@ -36,3 +40,6 @@ // Macro to evaluate function x with error checking //------------------------------------------------- #define CALL(x) (ErrorCode = ((ErrorCode>0) ? (ErrorCode) : (x))) + + +#endif //MACROS_H diff --git a/src/solver/mathexpr.h b/src/solver/mathexpr.h index 34d81eafd..ab56b7fba 100644 --- a/src/solver/mathexpr.h +++ b/src/solver/mathexpr.h @@ -8,6 +8,10 @@ ** LAST UPDATE: 03/20/14 ******************************************************************************/ +#ifndef MATHEXPR_H +#define MATHEXPR_H + + // Node in a tokenized math expression list struct ExprNode { @@ -27,3 +31,6 @@ double mathexpr_eval(MathExpr* expr, double (*getVal) (int)); // Deletes a tokenized math expression void mathexpr_delete(MathExpr* expr); + + +#endif //MATHEXPR_H diff --git a/src/solver/mempool.h b/src/solver/mempool.h index cc7b45cc1..864d2d6c3 100644 --- a/src/solver/mempool.h +++ b/src/solver/mempool.h @@ -7,6 +7,10 @@ // alloc pool - only the alloc routines know its structure. //----------------------------------------------------------------------------- +#ifndef MEMPOOL_H +#define MEMPOOL_H + + typedef struct { long dummy; @@ -17,3 +21,6 @@ char *Alloc(long); alloc_handle_t *AllocSetPool(alloc_handle_t *); void AllocReset(void); void AllocFreePool(void); + + +#endif //MEMPOOL_H diff --git a/src/solver/node.c b/src/solver/node.c index 4312b2d01..ac1061621 100644 --- a/src/solver/node.c +++ b/src/solver/node.c @@ -9,6 +9,7 @@ // 08/05/15 (Build 5.1.010) // 05/10/18 (Build 5.1.013) // 03/01/20 (Build 5.1.014) +// 04/14/20 (Build 5.1.015) // Author: L. Rossman // // Conveyance system node functions. @@ -31,6 +32,10 @@ // // Build 5.1.014: // - Fixed bug in storage_losses() that affected storage exfiltration. +// +// Build 5.1.015: +// - Fatal error issued if a storage node's area curve produces a negative +// volume when extrapolated to the node's full depth. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -211,6 +216,11 @@ void node_validate(int j) if ( Node[j].initDepth > Node[j].fullDepth + Node[j].surDepth ) report_writeErrorMsg(ERR_NODE_DEPTH, Node[j].ID); + // --- check for negative volume for storage node at full depth //(5.1.015) + if (Node[j].type == STORAGE) // + if (node_getVolume(j, Node[j].fullDepth) < 0.0) // + report_writeErrorMsg(ERR_STORAGE_VOLUME, Node[j].ID); // + if ( Node[j].type == DIVIDER ) divider_validate(j); // --- initialize dry weather inflows @@ -795,7 +805,7 @@ void storage_getVolDiff(double y, double* f, double* df, void* p) int k; double e, v; TStorageVol* storageVol; - + // ... cast void pointer p to a TStorageVol object storageVol = (TStorageVol *)p; k = storageVol->k; @@ -831,7 +841,7 @@ double storage_getVolume(int j, double d) // --- use table integration if area v. depth table exists if ( i >= 0 ) - return table_getArea(&Curve[i], d*UCF(LENGTH)) / UCF(VOLUME); + return table_getArea(&Curve[i], d*UCF(LENGTH)) / UCF(VOLUME); // --- otherwise use functional area v. depth relation else @@ -841,6 +851,7 @@ double storage_getVolume(int j, double d) v += Storage[k].aCoeff / (Storage[k].aExpon+1.0) * pow(d, Storage[k].aExpon+1.0); return v / UCF(VOLUME); + } } diff --git a/src/solver/objects.h b/src/solver/objects.h index 9fd738067..87716b86a 100644 --- a/src/solver/objects.h +++ b/src/solver/objects.h @@ -9,6 +9,7 @@ // 08/05/15 (Build 5.1.010) // 08/01/16 (Build 5.1.011) // 05/10/18 (Build 5.1.013) +// 04/01/20 (Build 5.1.015) // // Author: L. Rossman (EPA) // M. Tryby (EPA) @@ -54,8 +55,16 @@ // - Adjustment patterns added to TSubcatch structure. // - Members impervRunoff and pervRunoff added to TSubcatchStats structure. // - Member cdCurve (weir coeff. curve) added to TWeir structure. +// +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. +// - Support added for grouped freqency table of routing time steps. //----------------------------------------------------------------------------- +#ifndef OBJECTS_H +#define OBJECTS_H + + #include "mathexpr.h" #include "infil.h" #include "exfil.h" @@ -106,7 +115,7 @@ typedef struct typedef struct { char* ID; // raingage name - int dataSource; // data from time series or file + int dataSource; // data from time series or file int tSeries; // rainfall data time series index char fname[MAXFNAME+1]; // name of rainfall data file char staID[MAXMSG+1]; // station number @@ -130,7 +139,7 @@ typedef struct double reportRainfall; // rainfall value used for reported results int coGage; // index of gage with same rain timeseries int isUsed; // TRUE if gage used by any subcatchment - int isCurrent; // TRUE if gage's rainfall is current + int isCurrent; // TRUE if gage's rainfall is current } TGage; //------------------- @@ -138,7 +147,7 @@ typedef struct //------------------- typedef struct { - int dataSource; // data from time series or file + int dataSource; // data from time series or file int tSeries; // temperature data time series index DateTime fileStartDate; // starting date of data read from file double elev; // elev. of study area (ft) @@ -186,11 +195,11 @@ typedef struct int tSeries; // time series index double monthlyEvap[12]; // monthly evaporation values double panCoeff[12]; // monthly pan coeff. values - int recoveryPattern; // soil recovery factor pattern + int recoveryPattern; // soil recovery factor pattern int dryOnly; // true if evaporation only in dry periods //---------------------------- double rate; // current evaporation rate (ft/sec) - double recoveryFactor; // current soil recovery factor + double recoveryFactor; // current soil recovery factor } TEvap; //------------------- @@ -258,7 +267,7 @@ typedef struct //------------------------ typedef struct { - int aquifer; // index of associated gw aquifer + int aquifer; // index of associated gw aquifer int node; // index of node receiving gw flow double surfElev; // elevation of ground surface (ft) double a1, b1; // ground water outflow coeff. & exponent @@ -366,6 +375,7 @@ typedef struct int gage; // raingage index int outNode; // outlet node index int outSubcatch; // outlet subcatchment index + int infilModel; // infiltration method index //(5.1.015) int infil; // infiltration object index TSubarea subArea[3]; // sub-area data double width; // overland flow width (ft) @@ -386,7 +396,7 @@ typedef struct double lidArea; // area devoted to LIDs (ft2) double rainfall; // current rainfall (ft/sec) double evapLoss; // current evap losses (ft/sec) - double infilLoss; // current infil losses (ft/sec) + double infilLoss; // current infil losses (ft/sec) double runon; // runon from other subcatchments (cfs) double oldRunoff; // previous runoff (cfs) double newRunoff; // current runoff (cfs) @@ -538,7 +548,7 @@ typedef struct TExfil* exfil; // ptr. to exfiltration object //----------------------------- double hrt; // hydraulic residence time (sec) - double evapLoss; // evaporation loss (ft3) + double evapLoss; // evaporation loss (ft3) double exfilLoss; // exfiltration loss (ft3) } TStorage; @@ -590,10 +600,10 @@ typedef struct double aFull; // area when full (ft2) double rFull; // hyd. radius when full (ft) double wMax; // width at widest point (ft) - double ywMax; // depth at max width (ft) + double ywMax; // depth at max width (ft) double sMax; // section factor at max. flow (ft^4/3) double aMax; // area at max. flow (ft2) - double lengthFactor; // floodplain / channel length + double lengthFactor; // floodplain / channel length //-------------------------------------- double roughness; // Manning's n double areaTbl[N_TRANSECT_TBL]; // table of area v. depth @@ -610,7 +620,7 @@ typedef struct { int curve; // index of shape curve int nTbl; // size of geometry tables - double aFull; // area when full + double aFull; // area when full double rFull; // hyd. radius when full double wMax; // max. width double sMax; // max. section factor @@ -698,10 +708,10 @@ typedef struct { int type; // pump type int pumpCurve; // pump curve table index - double initSetting; // initial speed setting + double initSetting; // initial speed setting double yOn; // startup depth (ft) double yOff; // shutoff depth (ft) - double xMin; // minimum pt. on pump curve + double xMin; // minimum pt. on pump curve double xMax; // maximum pt. on pump curve } TPump; @@ -832,7 +842,7 @@ typedef struct //------------------------------- typedef struct { // All volume totals are in ft3. - double rainfall; // rainfall volume + double rainfall; // rainfall volume double evap; // evaporation loss double infil; // infiltration loss double runoff; // runoff volume @@ -900,6 +910,7 @@ typedef struct //----------------------- // SYSTEM-WIDE STATISTICS //----------------------- +#define TIMELEVELS 6 //(5.1.015) typedef struct { double minTimeStep; @@ -907,6 +918,8 @@ typedef struct double avgTimeStep; double avgStepCount; double steadyStateCount; + double timeStepIntervals[TIMELEVELS]; //(5.1.015) + int timeStepCounts[TIMELEVELS]; //(5.1.015) } TSysStats; //-------------------- @@ -931,7 +944,7 @@ typedef struct double evap; double infil; double runoff; - double maxFlow; + double maxFlow; double impervRunoff; //(5.1.013) double pervRunoff; // } TSubcatchStats; @@ -979,11 +992,11 @@ typedef struct { double avgFlow; double maxFlow; - double* totalLoad; + double* totalLoad; int totalPeriods; } TOutfallStats; -//---------------- +//---------------- // PUMP STATISTICS //---------------- typedef struct @@ -1030,15 +1043,18 @@ typedef struct int objType; // either NODE or LINK int index; // node or link index double value; // value of node or link statistic -} TMaxStats; +} TMaxStats; //------------------ // REPORT FIELD INFO //------------------ -typedef struct +typedef struct { - char Name[80]; // name of reported variable + char Name[80]; // name of reported variable char Units[80]; // units of reported variable char Enabled; // TRUE if appears in report table int Precision; // number of decimal places when reported } TRptField; + + +#endif //OBJECTS_H diff --git a/src/solver/odesolve.h b/src/solver/odesolve.h index 86fc86e67..77316452f 100644 --- a/src/solver/odesolve.h +++ b/src/solver/odesolve.h @@ -5,8 +5,15 @@ // //----------------------------------------------------------------------------- +#ifndef ODESOLVE_H +#define ODESOLVE_H + + // functions that open, close, and use the ODE solver int odesolve_open(int n); void odesolve_close(void); int odesolve_integrate(double ystart[], int n, double x1, double x2, double eps, double h1, void (*derivs)(double, double*, double*)); + + +#endif //ODESOLVE_H diff --git a/src/solver/project.c b/src/solver/project.c index 0fae207d5..db9d116c6 100644 --- a/src/solver/project.c +++ b/src/solver/project.c @@ -11,6 +11,7 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman // // Project management functions. @@ -55,6 +56,8 @@ // - More robust parsing of MinSurfarea option provided. // - Support added for new RuleStep analysis option. // +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -1014,7 +1017,7 @@ void createObjects() if ( ErrorCode ) return; // --- allocate memory for infiltration data - infil_create(Nobjects[SUBCATCH], InfilModel); + infil_create(Nobjects[SUBCATCH]); //(5.1.015) // --- allocate memory for water quality state variables for (j = 0; j < Nobjects[SUBCATCH]; j++) diff --git a/src/solver/qualrout.c b/src/solver/qualrout.c index 7c16e45af..3a23e4e72 100644 --- a/src/solver/qualrout.c +++ b/src/solver/qualrout.c @@ -7,6 +7,7 @@ // 04/02/15 (Build 5.1.008) // 04/30/15 (Build 5.1.009) // 08/05/15 (Build 5.1.010) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman // // Water quality routing functions. @@ -22,6 +23,8 @@ // - Entire module re-written to be more compact and easier to follow. // - Neglible depth limit replaced with a negligible volume limit. // +// Build 5.1.015: +// - Fixed mass balance issue for empty storage nodes that flood. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -443,8 +446,8 @@ void findStorageQual(int j, double tStep) wIn = Node[j].newQual[p]; c2 = getMixedQual(c1, v1, wIn, qIn, tStep); - // --- set concen. to zero if remaining volume is negligible - if ( Node[j].newVolume <= ZeroVolume ) +// --- set concen. to zero if remaining volume & inflow is negligible //(5.1.015) + if (Node[j].newVolume <= ZeroVolume && qIn <= FLOW_TOL) //(5.1.015) { massbal_addToFinalStorage(p, c2 * Node[j].newVolume); c2 = 0.0; diff --git a/src/solver/report.c b/src/solver/report.c index 7a0ced556..12d73fd7c 100644 --- a/src/solver/report.c +++ b/src/solver/report.c @@ -11,6 +11,7 @@ // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) // 03/01/20 (Build 5.1.014) +// 05/18/20 (Build 5.1.015) // Author: L. Rossman (EPA) // // Report writing functions. @@ -43,6 +44,10 @@ // // Build 5.1.014: // - Fixed bug in confusing keywords with ID names in report_readOptions(). +// +// Build 5.1.015: +// - Fixes bug in summary statistics when Report Start date > Start Date. +// - Support added for grouped freqency table of routing time steps. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -86,7 +91,7 @@ static void report_Nodes(void); static void report_NodeHeader(char *id); static void report_Links(void); static void report_LinkHeader(char *id); - +static void report_RouteStepFreq(TSysStats* sysStats); //(5.1.015) //============================================================================= @@ -1031,10 +1036,11 @@ void report_writeSysStats(TSysStats* sysStats) // { double x; - double eventStepCount = (double)StepCount - sysStats->steadyStateCount; + double eventStepCount; // Routing steps taken during reporting period //(5.1.015) - if ( Nobjects[LINK] == 0 || StepCount == 0 - || eventStepCount == 0.0 ) return; + eventStepCount = ReportStepCount - sysStats->steadyStateCount; //(5.1.015) + if ( Nobjects[LINK] == 0 || TotalStepCount == 0 + || eventStepCount == 0.0 ) return; WRITE(""); WRITE("*************************"); WRITE("Routing Time Step Summary"); @@ -1057,9 +1063,37 @@ void report_writeSysStats(TSysStats* sysStats) fprintf(Frpt.file, "\n Percent Not Converging : %7.2f", 100.0 * (double)NonConvergeCount / eventStepCount); + + // --- write grouped frequency table of variable routing time steps //(5.1.015) + if (RouteModel == DW && CourantFactor > 0.0) // + report_RouteStepFreq(sysStats); // WRITE(""); } +//============================================================================= + +//// New function added to release 5.1.015. //// //(5.1.015) +void report_RouteStepFreq(TSysStats* sysStats) +// +// Input: sysStats = simulation statistics for overall system +// Output: none +// Purpose: writes grouped frequency table of routing time steps to report file. +// +{ + double totalSteps = 0.0; + int i; + + for (i = 1; i < TIMELEVELS; i++) + totalSteps += sysStats->timeStepCounts[i]; + fprintf(Frpt.file, + "\n Time Step Frequencies :"); + for (i = 1; i < TIMELEVELS; i++) + fprintf(Frpt.file, + "\n %6.3f - %6.3f sec : %7.2f %%", + sysStats->timeStepIntervals[i-1], sysStats->timeStepIntervals[i], + 100.0 * (double)(sysStats->timeStepCounts[i]) / totalSteps); +} + //============================================================================= // SIMULATION RESULTS REPORTING diff --git a/src/solver/stats.c b/src/solver/stats.c index 7d6cb8b90..11c4ace09 100644 --- a/src/solver/stats.c +++ b/src/solver/stats.c @@ -9,6 +9,7 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman (EPA) // R. Dickinson (CDM) // @@ -36,6 +37,11 @@ // - Statistics on impervious and pervious runoff totals added. // - Storage nodes with a non-zero surcharge depth (e.g. enclosed tanks) // can now be classified as being surcharged. +// +// Build 5.1.015: +// - Fixes bug in summary statistics when Report Start date > Start Date. +// - Fixes failure to initialize all subcatchment groundwater statistics. +// - Support added for grouped freqency table of routing time steps. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -106,6 +112,9 @@ int stats_open() // { int j, k; + double timeStepDelta; //(5.1.015) + double logMaxTimeStep; //(5.1.015) + double logMinTimeStep; //(5.1.015) // --- set all pointers to NULL NodeStats = NULL; @@ -147,6 +156,8 @@ int stats_open() Subcatch[j].groundwater->stats.deepFlow = 0.0; Subcatch[j].groundwater->stats.evap = 0.0; Subcatch[j].groundwater->stats.maxFlow = 0.0; + Subcatch[j].groundwater->stats.finalUpperMoist = 0.0; //(5.1.015) + Subcatch[j].groundwater->stats.finalWaterTable = 0.0; // } } @@ -286,6 +297,20 @@ int stats_open() SysStats.avgTimeStep = 0.0; SysStats.avgStepCount = 0.0; SysStats.steadyStateCount = 0.0; + + // --- divide range between min and max routing time steps into //(5.1.015) + // equal intervals using a logarithmic scale // + logMaxTimeStep = log10(RouteStep); // + logMinTimeStep = log10(MinRouteStep); // + timeStepDelta = (logMaxTimeStep - logMinTimeStep) / (TIMELEVELS-1); // + SysStats.timeStepIntervals[0] = RouteStep; // + for (j = 1; j < TIMELEVELS; j++) // + { // + SysStats.timeStepIntervals[j] = // + pow(10., logMaxTimeStep - j * timeStepDelta); // + SysStats.timeStepCounts[j] = 0; // + } // + SysStats.timeStepIntervals[TIMELEVELS - 1] = MinRouteStep; // return 0; } @@ -446,6 +471,7 @@ void stats_updateFlowStats(double tStep, DateTime aDate, int stepCount, } // --- update count of times in steady state + ReportStepCount++; SysStats.steadyStateCount += steadyState; // --- update time step stats if not in steady state @@ -455,6 +481,15 @@ void stats_updateFlowStats(double tStep, DateTime aDate, int stepCount, if ( OldRoutingTime > 0 ) { SysStats.minTimeStep = MIN(SysStats.minTimeStep, tStep); + + // --- locate interval that logged time step falls in //(5.1.015) + // and update its count // + for (j = 1; j < TIMELEVELS; j++) // + if (tStep >= SysStats.timeStepIntervals[j]) // + { // + SysStats.timeStepCounts[j]++; // + break; // + } // } SysStats.avgTimeStep += tStep; SysStats.maxTimeStep = MAX(SysStats.maxTimeStep, tStep); @@ -703,6 +738,7 @@ void stats_findMaxStats() { int j; double x; + double stepCount = ReportStepCount - SysStats.steadyStateCount; //(5.1.015) // --- initialize max. stats arrays for (j=0; j 2 ) + if ( stepCount > 2 ) //(5.1.015) { for (j=0; j Start Date. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -337,7 +341,7 @@ void writeNodeDepths() fprintf(Frpt.file, " %-9s ", NodeTypeWords[Node[j].type]); getElapsedTime(NodeStats[j].maxDepthDate, &days, &hrs, &mins); fprintf(Frpt.file, "%7.2f %7.2f %7.2f %4d %02d:%02d %10.2f", - NodeStats[j].avgDepth / StepCount * UCF(LENGTH), + NodeStats[j].avgDepth / ReportStepCount * UCF(LENGTH), //(5.1.015) NodeStats[j].maxDepth * UCF(LENGTH), (NodeStats[j].maxDepth + Node[j].invertElev) * UCF(LENGTH), days, hrs, mins, NodeStats[j].maxRptDepth); @@ -538,7 +542,7 @@ void writeStorageVolumes() if ( Node[j].type != STORAGE ) continue; k = Node[j].subIndex; fprintf(Frpt.file, "\n %-20s", Node[j].ID); - avgVol = StorageStats[k].avgVol / StepCount; + avgVol = StorageStats[k].avgVol / (double)ReportStepCount; //(5.1.015) maxVol = StorageStats[k].maxVol; pctMaxVol = 0.0; pctAvgVol = 0.0; @@ -634,7 +638,7 @@ void writeOutfallLoads() // --- print node ID, flow freq., avg. flow, max. flow & flow vol. fprintf(Frpt.file, "\n %-20s", Node[j].ID); - x = 100.*flowCount/(double)StepCount; + x = 100.*flowCount/(double)ReportStepCount; //(5.1.015) fprintf(Frpt.file, "%7.2f", x); freqSum += x; if ( flowCount > 0 ) @@ -804,7 +808,7 @@ void writeFlowClass() for ( i=0; iinflow, subarea->depth); // --- limit infiltration rate by available void space in unsaturated @@ -1139,7 +1143,7 @@ void adjustSubareaParams(int i, int j) // Input: i = type of subarea being analyzed // j = index of current subcatchment being analyzed // Output adjusted values of module-level variables Dstore & Alpha -// Purpose: adjusts a subarea's depression storage and its pervious +// Purpose: adjusts a pervious subarea's depression storage and its //(5.1.015) // runoff coeff. by month of the year. // { @@ -1147,22 +1151,25 @@ void adjustSubareaParams(int i, int j) int m; // current month of the year double f; // adjustment factor - // --- depression storage adjustment - p = Subcatch[j].dStorePattern; - if (p >= 0 && Pattern[p].type == MONTHLY_PATTERN) - { - m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; - f = Pattern[p].factor[m]; - if (f >= 0.0) Dstore *= f; - } - - // --- pervious area roughness - p = Subcatch[j].nPervPattern; - if (i == PERV && p >= 0 && Pattern[p].type == MONTHLY_PATTERN) + if (i == PERV) //(5.1.015) { - m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; - f = Pattern[p].factor[m]; - if (f <= 0.0) Alpha = 0.0; - else Alpha /= f; - } + // --- depression storage adjustment + p = Subcatch[j].dStorePattern; + if (p >= 0 && Pattern[p].type == MONTHLY_PATTERN) + { + m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; + f = Pattern[p].factor[m]; + if (f >= 0.0) Dstore *= f; + } + + // --- roughness adjustment to runoff coeff. //(5.1.015) + p = Subcatch[j].nPervPattern; + if (p >= 0 && Pattern[p].type == MONTHLY_PATTERN) //(5.1.015) + { + m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; + f = Pattern[p].factor[m]; + if (f <= 0.0) Alpha = 0.0; + else Alpha /= f; + } + } } diff --git a/src/solver/swmm5.c b/src/solver/swmm5.c index 2917cb5e3..1ddf9f77c 100644 --- a/src/solver/swmm5.c +++ b/src/solver/swmm5.c @@ -8,6 +8,7 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 04/01/20 (Build 5.1.015) // Author: L. Rossman // // This is the main module of the computational engine for Version 5 of @@ -38,7 +39,9 @@ // Build 5.1.013: // - Support added for saving average results within a reporting period. // - SWMM engine now always compiled to a shared object library. -// +// +// Build 5.1.015: +// - Fixes bug in summary statistics when Report Start date > Start Date. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -306,7 +309,8 @@ int DLLEXPORT swmm_start(int saveResults) NewRunoffTime = 0.0; NewRoutingTime = 0.0; ReportTime = (double)(1000 * ReportStep); - StepCount = 0; + TotalStepCount = 0; //(5.1.015) + ReportStepCount = 0; //(5.1.015) NonConvergeCount = 0; IsStartedFlag = TRUE; @@ -440,7 +444,7 @@ int DLLEXPORT swmm_step(double* elapsedTime) #ifdef EXH // --- end of try loop; handle exception here - __except(xfilter(GetExceptionCode(), "swmm_step", ElapsedTime, StepCount)) + __except(xfilter(GetExceptionCode(), "swmm_step", ElapsedTime, TotalStepCount)) //(5.1.015) { ErrorCode = ERR_SYSTEM; } @@ -466,7 +470,7 @@ void execRouting() #endif { // --- determine when next routing time occurs - StepCount++; + TotalStepCount++; //(5.1.015) if ( !DoRouting ) routingStep = MIN(WetStep, ReportStep); else routingStep = routing_getRoutingStep(RouteModel, RouteStep); if ( routingStep <= 0.0 ) @@ -480,7 +484,7 @@ void execRouting() if ( nextRoutingTime > TotalDuration ) { routingStep = (TotalDuration - NewRoutingTime) / 1000.0; - routingStep = MAX(routingStep, 1./1000.0); + routingStep = MAX(routingStep, 1. / 1000.0); nextRoutingTime = TotalDuration; } @@ -504,7 +508,7 @@ void execRouting() #ifdef EXH // --- end of try loop; handle exception here __except(xfilter(GetExceptionCode(), "execRouting", - ElapsedTime, StepCount)) + ElapsedTime, TotalStepCount)) //(5.1.015) { ErrorCode = ERR_SYSTEM; return; @@ -816,7 +820,7 @@ void writecon(char *s) // Purpose: writes string of characters to the console. // { - fprintf(stdout,s); + fprintf(stdout,"%s",s); fflush(stdout); } diff --git a/src/solver/text.h b/src/solver/text.h index 53cc0817a..204f6d518 100644 --- a/src/solver/text.h +++ b/src/solver/text.h @@ -15,11 +15,18 @@ // 08/01/16 (Build 5.1.011) // 03/14/17 (Build 5.1.012) // 05/10/18 (Build 5.1.013) +// 03/01/20 (Build 5.1.014) +// 04/01/20 (Build 5.1.015) +// // Author: L. Rossman // // Text strings //----------------------------------------------------------------------------- +#ifndef TEXT_H +#define TEXT_H + + #define FMT01 \ "\tswmm5 \n" @@ -29,7 +36,7 @@ #define FMT06 "\n o Retrieving project data" #define FMT07 "\n o Writing output report" #define FMT08 \ - "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build 5.1.014)" //(5.1.014) + "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build 5.1.015)" //(5.1.015) #define FMT09 \ "\n --------------------------------------------------------------" #define FMT10 "\n" @@ -443,3 +450,6 @@ #define ws_GWF "[GWF" #define ws_ADJUST "[ADJUSTMENT" #define ws_EVENT "[EVENT" + + +#endif //TEXT_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4e5a06dbf..6849149e9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,18 +1,16 @@ # -# CMakeLists.txt - CMake configuration file for epanet/tests +# CMakeLists.txt - CMake configuration file for swmm-solver/tests +# +# Created: Mar 4, 2020 +# Updated: May 21, 2020 +# +# Author: Michael E. Tryby +# US EPA ORD/CESER # #Prep ourselves for compiling with boost -if(WIN32) - set(Boost_USE_STATIC_LIBS ON) -else() - set(Boost_USE_STATIC_LIBS OFF) - add_definitions(-DBOOST_ALL_DYN_LINK) -endif() - -find_package(Boost COMPONENTS unit_test_framework system thread filesystem) -include_directories (${Boost_INCLUDE_DIRS}) +include(../extern/boost.cmake) add_subdirectory(outfile) diff --git a/tests/Unit_Testing.md b/tests/Unit_Testing.md index 65869798d..c0b39a69a 100644 --- a/tests/Unit_Testing.md +++ b/tests/Unit_Testing.md @@ -16,10 +16,10 @@ Before the project can be built and tested the required dependencies must be installed. **Summary of Build Dependencies: Windows** - - Build - Build Tools for Visual Studio 2017 - CMake 3.13 + - ci-tools repository - Unit Test - Boost 1.67.0 (installed in default location "C:\\local") @@ -31,5 +31,5 @@ Before the project can be built and tested the required dependencies must be ins SWMM can be built and unit tests run with one simple command. ``` \> cd swmm -\swmm>tools\make.cmd /t +\swmm>ci-tools\windows\make.cmd /t ``` diff --git a/tools/Reg_Testing.md b/tools/Reg_Testing.md deleted file mode 100644 index b6d31afec..000000000 --- a/tools/Reg_Testing.md +++ /dev/null @@ -1,50 +0,0 @@ - - -## Regression Testing SWMM locally on Windows - - -### Dependencies - -Before the project can be built and tested the required dependencies must be installed. - -**Summary of Build Dependencies: Windows** - - - Build - - Build Tools for Visual Studio 2017 - - CMake 3.13 - - - Regression Test - - Python 3.6 64 bit - - curl - - git - - 7z - -Once Python is present, the following command installs the required packages for regression testing. -``` -\> cd swmm -\swmm>pip install -r tools\requirements-win.txt -``` - - -### Build - -EPANET can be built with one simple command. -``` -\swmm>tools\make.cmd -``` - - -### Regression Test - -This command runs regression tests for the local build and compares them to the latest benchmark. -``` -\swmm>tools\before-nrtest.cmd && tools\run-nrtest.cmd -``` diff --git a/tools/app-config.cmd b/tools/app-config.cmd deleted file mode 100644 index 09ce04daf..000000000 --- a/tools/app-config.cmd +++ /dev/null @@ -1,58 +0,0 @@ -:: -:: app-config.cmd - Generates nrtest app configuration file for SUT executable -:: -:: Date Created: 10/16/2019 -:: Date Modified: 10/17/2019 -:: -:: Author: Michael E. Tryby -:: US EPA - ORD/CESER -:: -:: Requires: -:: git -:: -:: Environment Variables: -:: PROJECT -:: -:: Arguments: -:: 1 - absolute path to test executable (valid path seperator for nrtest is "/") -:: 2 - (platform) -:: 3 - (build identifier for SUT) -:: - -@echo off -setlocal - - -:: check requirements -where git > nul -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: git not installed" & exit /B 1 ) - -:: check environment -if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) - - -:: swmm target created by the cmake build script -set TEST_CMD=run%PROJECT%.exe - -:: remove quotes from path and convert backward to forward slash -set ABS_BUILD_PATH=%~1 -set ABS_BUILD_PATH=%ABS_BUILD_PATH:\=/% - -if [%2]==[] ( set "PLATFORM=unknown" -) else ( set "PLATFORM=%~2" ) - -if [%3]==[] ( set "BUILD_ID=unknown" -) else ( set "BUILD_ID=%~3" ) - -:: determine version -for /F "tokens=1" %%v in ( 'git rev-parse --short HEAD' ) do ( set "VERSION=%%v" ) -if not defined VERSION ( echo "ERROR: VERSION could not be determined" & exit /B 1 ) - - -echo { -echo "name" : "%PROJECT%", -echo "version" : "%VERSION%", -echo "description" : "%PLATFORM% %BUILD_ID%", -echo "setup_script" : "", -echo "exe" : "%ABS_BUILD_PATH%/%TEST_CMD%" -echo } diff --git a/tools/before-nrtest.cmd b/tools/before-nrtest.cmd deleted file mode 100644 index 6a7a36428..000000000 --- a/tools/before-nrtest.cmd +++ /dev/null @@ -1,116 +0,0 @@ -:: -:: before-test.cmd - Stages test and benchmark files for nrtest -:: -:: Date Created: 10/16/2019 -:: Date Updated: -:: -:: Author: Michael E. Tryby -:: US EPA - ORD/CESER -:: -:: Dependencies: -:: curl -:: 7z -:: -:: Environment Variables: -:: PROJECT -:: BUILD_HOME - defaults to "build" -:: PLATFORM -:: -:: Arguments: -:: 1 - (RELEASE_TAG) release tag for benchmark version (defaults to latest tag) -:: -:: Note: -:: Tests and benchmark files are stored in the project-nrtests repo. -:: This script retrieves them using a stable URL associated with a GitHub -:: release, stages the files, and sets up the environment for nrtest to run. -:: - -::@echo off - -:: set global default -set "TEST_HOME=nrtests" - -:: determine project directory -set "CUR_DIR=%CD%" -set "SCRIPT_HOME=%~dp0" -cd %SCRIPT_HOME% -cd .. - -setlocal - - -:: check that dependencies are installed -where curl > nul -if %ERRORLEVEL% neq 0 ( echo "ERROR: curl not installed" & exit /B 1 ) -where 7z > nul -if %ERRORLEVEL% neq 0 ( echo "ERROR: 7zip not installed" & exit /B 1 ) - - -:: set URL to github repo with test files -set "NRTESTS_URL=https://github.com/SWMM-Project/%PROJECT%-nrtestsuite" - - -:: if release tag isn't provided latest tag will be retrieved -if [%1] == [] (set "RELEASE_TAG=" -) else (set "RELEASE_TAG=%~1") - - -:: check env variables and apply defaults -if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) -if not defined BUILD_HOME ( echo "ERROR: BUILD_HOME must be defined" & exit /B 1 ) -if not defined PLATFORM ( echo "ERROR: PLATFORM must be defined" & exit /B 1 ) - - -echo INFO: Staging files for regression testing - - -:: determine latest tag in the tests repo -if [%RELEASE_TAG%] == [] ( - for /F delims^=^"^ tokens^=2 %%g in ('curl --silent %NRTESTS_URL%/releases/latest') do ( - set "RELEASE_TAG=%%~nxg" - ) -) - -if defined RELEASE_TAG ( - set "TESTFILES_URL=%NRTESTS_URL%/archive/%RELEASE_TAG%.zip" - set "BENCHFILES_URL=%NRTESTS_URL%/releases/download/%RELEASE_TAG%/benchmark-%PLATFORM%.zip" -) else ( echo ERROR: tag %RELEASE_TAG% is invalid & exit /B 1 ) - - -:: create a clean directory for staging regression tests -if exist %TEST_HOME% ( - rmdir /s /q %TEST_HOME% -) -mkdir %TEST_HOME% -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to make %TEST_HOME% dir" & exit /B 1 ) -cd %TEST_HOME% -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to cd %TEST_HOME% dir" & exit /B 1 ) - - -:: retrieve nrtest cases and benchmark results for regression testing -curl -fsSL -o nrtestfiles.zip %TESTFILES_URL% -curl -fsSL -o benchmark.zip %BENCHFILES_URL% - - -:: extract tests, scripts, benchmarks, and manifest -7z x nrtestfiles.zip * > nul -7z x benchmark.zip -obenchmark\ > nul -7z e benchmark.zip -o. manifest.json -r > nul - - -:: set up symlinks for tests directory -mklink /D .\tests .\%PROJECT%-nrtestsuite-%RELEASE_TAG:~1%\public > nul - - -endlocal - - -:: determine REF_BUILD_ID from manifest file -for /F delims^=^"^ tokens^=4 %%d in ( 'findstr %PLATFORM% %TEST_HOME%\manifest.json' ) do ( - for /F "tokens=2" %%r in ( 'echo %%d' ) do ( set "REF_BUILD_ID=%%r" ) -) -if not defined REF_BUILD_ID ( echo "ERROR: REF_BUILD_ID could not be determined" & exit /B 1 ) - - -:: return to users current directory -cd %CUR_DIR% diff --git a/tools/make.cmd b/tools/make.cmd deleted file mode 100644 index 3fabcc683..000000000 --- a/tools/make.cmd +++ /dev/null @@ -1,108 +0,0 @@ -:: -:: make.cmd - builds project -:: -:: Date Created: 10/15/2019 -:: Date Updated: -:: -:: Author: Michael E. Tryby -:: US EPA - ORD/CESER -:: -:: Requires: -:: Build Tools for Visual Studio download: -:: https://visualstudio.microsoft.com/downloads/ -:: -:: CMake download: -:: https://cmake.org/download/ -:: -:: Optional Arguments: -:: /g ("GENERATOR") defaults to "Visual Studio 15 2017" -:: /t builds and runs unit tests (requires Boost) -:: - - -::echo off - - -:: set global defaults -set "PROJECT=swmm" -set "BUILD_HOME=build" -set "PLATFORM=win32" - -:: determine project directory -set "CUR_DIR=%CD%" -set "SCRIPT_HOME=%~dp0" -cd %SCRIPT_HOME% -cd .. - -:: check for requirements -where cmake > nul -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: cmake not installed" & exit /B 1 ) - - -setlocal EnableDelayedExpansion - - -echo INFO: Building %PROJECT% ... - - -:: set local defaults -set "GENERATOR=Visual Studio 15 2017" -set "TESTING=0" - -:: process arguments -:loop -if NOT [%1]==[] ( - if "%1"=="/g" ( - set "GENERATOR=%~2" - shift - ) - if "%1"=="/t" ( - set "TESTING=1" - ) - shift - goto :loop -) - - -:: if generator has changed delete the build folder -if exist %BUILD_HOME% ( - for /F "tokens=*" %%f in ( 'findstr CMAKE_GENERATOR:INTERNAL %BUILD_HOME%\CmakeCache.txt' ) do ( - for /F "delims=:= tokens=3" %%m in ( 'echo %%f' ) do ( - set CACHE_GEN=%%m - if not "!CACHE_GEN!" == "!GENERATOR!" ( rmdir /s /q %BUILD_HOME% & mkdir %BUILD_HOME% ) - ) - ) -) else ( - mkdir %BUILD_HOME%^ - & if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to make %BUILD_HOME% dir" & exit /B 1 ) -) - - -:: perform the build -cd %BUILD_HOME% -if %ERRORLEVEL% NEQ 0 ( echo "ERROR: unable to cd %BUILD_HOME% dir" & exit /B 1 ) - -if %TESTING% EQU 1 ( - cmake -G"%GENERATOR%" -DBUILD_TESTS=ON -DBOOST_ROOT=C:\local\boost_1_67_0 ..^ - && cmake --build . --config Debug^ - & echo. && ctest -C Debug --output-on-failure -) else ( - cmake -G"%GENERATOR%" -DBUILD_TESTS=OFF ..^ - && cmake --build . --config Release --target install -) - - -endlocal - - -:: determine platform from CmakeCache.txt file -for /F "tokens=*" %%f in ( 'findstr CMAKE_SHARED_LINKER_FLAGS:STRING %BUILD_HOME%\CmakeCache.txt' ) do ( - for /F "delims=: tokens=3" %%m in ( 'echo %%f' ) do ( - if "%%m" == "X86" ( set "PLATFORM=win32" ) else if "%%m" == "x64" ( set "PLATFORM=win64" ) - ) -) -if not defined PLATFORM ( echo "ERROR: PLATFORM could not be determined" & exit /B 1 ) - - -:: return to users current dir -:: cd %CUR_DIR% diff --git a/tools/requirements-win.txt b/tools/requirements-win.txt deleted file mode 100644 index 0f18bb0ee..000000000 --- a/tools/requirements-win.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# requirements-win.txt - Python requirements for running nrtest on Win32/Win64 -# -# Date Created: 10/17/2019 -# Date Updated: 11/26/2019 -# -# Author: Michael E. Tryby -# US EPA ORD/CESER -# -# Useful for configuring a python environment to run nrtests on swmm. -# -# usage: -# pip install -r tools/requirements-win.txt -# - -nrtest - --f https://github.com/michaeltryby/swmm-python/releases/download/v0.3.0-dev3/swmm.output-0.4.0.dev3-cp36-cp36m-win_amd64.whl -swmm.output - --f https://github.com/michaeltryby/swmm-python/releases/download/v0.3.0-dev3/nrtest_swmm-0.5.0-py3-none-any.whl -nrtest-swmm diff --git a/tools/run-nrtests.cmd b/tools/run-nrtests.cmd deleted file mode 100644 index c51d96c80..000000000 --- a/tools/run-nrtests.cmd +++ /dev/null @@ -1,114 +0,0 @@ -:: -:: run_nrtest.cmd - Runs numerical regression test -:: -:: Date Created: 10/16/2019 -:: Date Updated: -:: -:: Author: Michael E. Tryby -:: US EPA - ORD/CESER -:: -:: Dependencies: -:: python -m pip install -r requirements.txt -:: -:: Environment Variables: -:: PROJECT -:: BUILD_HOME - relative path -:: TEST_HOME - relative path -:: PLATFORM -:: REF_BUILD_ID -:: -:: Arguments: -:: 1 - (SUT_VERSION) - optional argument -:: 2 - (SUT_BUILD_ID) - optional argument -:: - -::@echo off -setlocal EnableDelayedExpansion - - -:: Check that required environment variables are set -if not defined PROJECT ( echo "ERROR: PROJECT must be defined" & exit /B 1 ) -if not defined BUILD_HOME ( echo "ERROR: BUILD_HOME must be defined" & exit /B 1 ) -if not defined TEST_HOME ( echo "ERROR: TEST_HOME must be defined" & exit /B 1 ) -if not defined PLATFORM ( echo "ERROR: PLATFORM must be defined" & exit /B 1 ) -if not defined REF_BUILD_ID ( echo "ERROR: REF_BUILD_ID must be defined" & exit /B 1 ) - - -:: determine project directory -set "CUR_DIR=%CD%" -set "SCRIPT_HOME=%~dp0" -cd %SCRIPT_HOME% -pushd .. -set PROJ_DIR=%CD% -popd - - -cd %PROJ_DIR%\%TEST_HOME% - -:: Process optional arguments -if [%1]==[] (set "SUT_VERSION=unknown" -) else ( set "SUT_VERSION=%~1" ) - -if [%2]==[] ( set "SUT_BUILD_ID=local" -) else ( set "SUT_BUILD_ID=%~2" ) - - -:: check if app config file exists -if not exist apps\%PROJECT%-%SUT_BUILD_ID%.json ( - mkdir apps - call %SCRIPT_HOME%\app-config.cmd %PROJ_DIR%\%BUILD_HOME%\bin\Release^ - %PLATFORM% %SUT_BUILD_ID% %SUT_VERSION% > apps\%PROJECT%-%SUT_BUILD_ID%.json -) - - -:: recursively build test list -set TESTS= -for /F "tokens=*" %%T in ('dir /b /s /a:d tests') do ( - set FULL_PATH=%%T - set TESTS=!TESTS! !FULL_PATH:*%TEST_HOME%\=! -) - - -:: determine location of python Scripts folder -for /F "tokens=*" %%G in ('where python.exe') do ( - set PYTHON_DIR=%%~dpG - goto break_loop_1 -) -:break_loop_1 -set "NRTEST_SCRIPT_PATH=%PYTHON_DIR%Scripts" - - -:: build nrtest execute command -set NRTEST_EXECUTE_CMD=python.exe %NRTEST_SCRIPT_PATH%\nrtest execute -set TEST_APP_PATH=apps\%PROJECT%-%SUT_BUILD_ID%.json -set TEST_OUTPUT_PATH=benchmark\%PROJECT%-%SUT_BUILD_ID% - -:: build nrtest compare command -set NRTEST_COMPARE_CMD=python.exe %NRTEST_SCRIPT_PATH%\nrtest compare -set REF_OUTPUT_PATH=benchmark\%PROJECT%-%REF_BUILD_ID% -set RTOL_VALUE=0.01 -set ATOL_VALUE=1.E-6 - -:: change current directory to test suite -::cd %TEST_HOME% - -:: if present clean test benchmark results -if exist %TEST_OUTPUT_PATH% ( - rmdir /s /q %TEST_OUTPUT_PATH% -) - -:: perform nrtest execute -echo INFO: Creating SUT %SUT_BUILD_ID% artifacts -set NRTEST_COMMAND=%NRTEST_EXECUTE_CMD% %TEST_APP_PATH% %TESTS% -o %TEST_OUTPUT_PATH% -:: if there is an error exit the script with error value 1 -%NRTEST_COMMAND% || exit /B 1 - -echo. - -:: perform nrtest compare -echo INFO: Comparing SUT artifacts to REF %REF_BUILD_ID% -set NRTEST_COMMAND=%NRTEST_COMPARE_CMD% %TEST_OUTPUT_PATH% %REF_OUTPUT_PATH% --rtol %RTOL_VALUE% --atol %ATOL_VALUE% -o benchmark\receipt.json -%NRTEST_COMMAND% - -:: Return user to their current dir -cd %CUR_DIR% From 71fde16cfc32b9b668121d73537a33d8ca377e68 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 8 Feb 2021 10:12:13 -0500 Subject: [PATCH 190/266] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 09a0e1894..b18d02aa6 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,4 @@ SWMM is a dynamic hydrology-hydraulic water quality simulation model. It is used ## Find Out More The source code distributed here is identical to the code found at the official [SWMM Website](http://www2.epa.gov/water-research/storm-water-management-model-swmm). + From 339a99caa105b5c9eaae8331ae70f5d144cb9fe7 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 8 Feb 2021 12:06:50 -0500 Subject: [PATCH 191/266] Update build-and-test.yml Checkout ci-tools/own-master branch --- .github/workflows/build-and-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index b92ab7048..fb28668ad 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -39,6 +39,7 @@ jobs: uses: actions/checkout@v2 with: repository: michaeltryby/ci-tools + ref: owa-master path: ci-tools - name: Setup python From 07a9ad39f627c29bf469f614a9ec8ac98ca3eba3 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 8 Feb 2021 17:22:38 -0500 Subject: [PATCH 192/266] Update build-and-test.yml Updating ci-tools to master branch --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index fb28668ad..7b383f29a 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -39,7 +39,7 @@ jobs: uses: actions/checkout@v2 with: repository: michaeltryby/ci-tools - ref: owa-master + ref: master path: ci-tools - name: Setup python From 930e74edcdadfb9f50d52cba833cc9165d8ee6b5 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Tue, 30 Mar 2021 14:47:09 -0400 Subject: [PATCH 193/266] Release 5.2.0 --- doc/Images/Fig1.png | Bin 0 -> 67350 bytes doc/Images/Fig2a.png | Bin 0 -> 32564 bytes doc/Images/Fig2b.png | Bin 0 -> 12757 bytes doc/Images/Fig3.png | Bin 0 -> 56462 bytes doc/Images/Fig4.png | Bin 0 -> 2097 bytes doc/Images/Fig5.png | Bin 0 -> 1517 bytes doc/Images/Fig6.png | Bin 0 -> 2751 bytes doc/New SWMM 5.2 Features.md | 290 ++++++ src/run/main.c | 2 +- src/solver/Roadmap.txt | 28 +- src/solver/climate.c | 24 +- src/solver/consts.h | 15 +- src/solver/controls.c | 563 +++++++++--- src/solver/culvert.c | 9 +- src/solver/datetime.c | 10 +- src/solver/datetime.h | 9 +- src/solver/dwflow.c | 55 +- src/solver/dynwave.c | 89 +- src/solver/enums.h | 69 +- src/solver/error.c | 26 +- src/solver/error.h | 18 +- src/solver/exfil.c | 98 +- src/solver/exfil.h | 10 +- src/solver/findroot.h | 6 +- src/solver/flowrout.c | 30 +- src/solver/forcmain.c | 4 +- src/solver/funcs.h | 62 +- src/solver/gage.c | 99 +- src/solver/globals.h | 31 +- src/solver/gwater.c | 14 +- src/solver/headers.h | 5 +- src/solver/hotstart.c | 21 +- src/solver/iface.c | 4 +- src/solver/infil.c | 67 +- src/solver/infil.h | 20 +- src/solver/inflow.c | 4 +- src/solver/inlet.c | 1641 ++++++++++++++++++++++++++++++++++ src/solver/inlet.h | 107 +++ src/solver/input.c | 49 +- src/solver/inputrpt.c | 44 +- src/solver/keywords.c | 44 +- src/solver/keywords.h | 11 +- src/solver/kinwave.c | 14 +- src/solver/landuse.c | 8 +- src/solver/lid.c | 211 ++--- src/solver/lid.h | 50 +- src/solver/lidproc.c | 49 +- src/solver/link.c | 140 +-- src/solver/macros.h | 4 +- src/solver/massbal.c | 51 +- src/solver/mathexpr.c | 2 +- src/solver/mathexpr.h | 2 +- src/solver/node.c | 293 +++--- src/solver/objects.h | 100 +-- src/solver/output.c | 90 +- src/solver/project.c | 103 ++- src/solver/qualrout.c | 23 +- src/solver/rain.c | 15 +- src/solver/rdii.c | 13 +- src/solver/report.c | 83 +- src/solver/roadway.c | 8 +- src/solver/routing.c | 87 +- src/solver/runoff.c | 21 +- src/solver/shape.c | 4 +- src/solver/snow.c | 8 +- src/solver/stats.c | 180 ++-- src/solver/statsrpt.c | 92 +- src/solver/street.c | 129 +++ src/solver/street.h | 49 + src/solver/subcatch.c | 102 +-- src/solver/surfqual.c | 8 +- src/solver/swmm5.c | 43 +- src/solver/swmm5.def | 14 + src/solver/swmm5.h | 57 ++ src/solver/table.c | 200 +++-- src/solver/text.h | 55 +- src/solver/toposort.c | 10 +- src/solver/transect.c | 202 ++++- src/solver/treatmnt.c | 14 +- src/solver/xsect.c | 143 ++- src/solver/xsect.dat | 4 +- 81 files changed, 4574 insertions(+), 1685 deletions(-) create mode 100644 doc/Images/Fig1.png create mode 100644 doc/Images/Fig2a.png create mode 100644 doc/Images/Fig2b.png create mode 100644 doc/Images/Fig3.png create mode 100644 doc/Images/Fig4.png create mode 100644 doc/Images/Fig5.png create mode 100644 doc/Images/Fig6.png create mode 100644 doc/New SWMM 5.2 Features.md create mode 100644 src/solver/inlet.c create mode 100644 src/solver/inlet.h create mode 100644 src/solver/street.c create mode 100644 src/solver/street.h create mode 100644 src/solver/swmm5.def create mode 100644 src/solver/swmm5.h diff --git a/doc/Images/Fig1.png b/doc/Images/Fig1.png new file mode 100644 index 0000000000000000000000000000000000000000..188c2c15d127ce67ab25050d303f53e90c72b673 GIT binary patch literal 67350 zcmaI7bx>T*6E2(thv4o37I%kW!QI_uad#&KcNTXE?jGENI|O%KSRlANT;BY?`u@6g zyXw@QbEiH zxZLcp_m5!|M{QrnEcuGjxON)!`E@Wz3r|Q zmMS3_yX)t#67klkdGNLZ}pMc>hygjS){)toY&;S3eXkta^ ze=>+3N~MYXpTtmnmaiuGCz5y>dYl~pNh}LTtF`5SLS$^|JpWG*p58yZ<^SJ+dH?SL z#j7c-pwa&~{4`3{|7Fn93q30wlJy{1)V!J!WV1#_rM_7SaSFmF1|0nhq2f0k;-#t|IVvPSh3C3!oT6`NjHP>!1F0Q9!X)l%IcN;4bzu{UaoE zy>LKp;O6^n!(1_KSx3*ecwxJdBTp}VC`hg|4wd+pS86w0q%-@W8+KhFSsGp`R<5qnL^i>2=373A{YePc{cAmq4G;Aq%+c6b z(9lT2$K@a)4h$<~iw+RvP(SS}MkcIn|AlD0JbmWSGZ~YKP81nBzbEW(!RUU%6tHeA z`fCEEVxG;n{+}7N!pIk=?J&5|urZ;;dJpVvL@h+IMW!R99Ql+l{yJQ=9yy4LTPnp` z_mOtrn(<$FjneMJ5guCf9%fEDXQ zBuEH%^KnDb$=Uitg{FOIw;wl&r-?v{-i`e8^#bYkpv~k!7xtToo7_jHr@8dB?gLlf zy@ph9Qjx{>FU)=51@Ge4qpX5>WS^AbKW%_waYqeDHs_ht?%S=%jiI75aD!;DkD*E| zU{gK6bzG+n4AQ3R<&e1A(o2U(j7arof)Gn4M*@N$^XXB~$7=m!<@5I9x|yVY$dm^M zvV%#1di|rf{nz9GxSXz0CxLR&z7HdzkE;b>%Tg#PByLc0=$pT|V{# zS6|;cC%vF>*Ym&-fud0r0(k`7((Xz>7i}27ibk|5I<2=gvh*JPX)UZbS;B=O0(%aV=Ds20>#1j7*5rtT=@b`ESY^JC0QU zJP${3LMTd2{1wGV_WGh<$ZwuS(AY8G^}d@3GHInDrM~z<2gR)_5zGO=qKjr;N>ohr z6V{Vq&FB!Ir;lG;iBlgLv|__~-cTu&XZZg-op3abGvy;3Y>3h7#gti5sSVOj2h1{$ zzcC7iej91ozioK%JKWMm0IQC5I!!w%{AiK@1Z}~eqoWxksi8@pzsmJ*x+D-JvYZ95 zd~Cj6ET+oTHn(BoGK>xs4ecXrcU2j|!NxFgowe(0h=Tp{nU_N4nfW8(Nc~`Lpp=J) zb7SZQJs5H1N5u?(Vsfe@OAV8P#B|5c@g;GBw(XNr=le3V;c~~yGg#$5qg?0N?-TIf zF8Ujp*>Pu`erD*rXK#6413U68N8IZwE6>Kxb$s%)t?Sc$S4;5oKc&Fz0{6#OU>4rR zsC}-vt@uUy=^49dfw3Q$HMZ4#P4({L^#lZx7(SY7piKWe7T6Mo{sG)96wRN|%pF~l zjo{`p-I&apR-7?Fm45Gt;|H!B3EwH7jv zs)S7-xCNDOvnhK&x^Lf~iM`&Z`))!(28rP2^Y(y9juAU?<2I!BiniK^Z}i?G=;Ji! zdF!Nuh6W76;)$dUk-oLMf?6>?MviDcqXd6e4khJWIcyj1w*?3Yevh{M2hImv++U!+ zqLwfk{D48iG5#COF9!Fw_A$BSd@jlcJuwF)tn8N(3=n&=QC3+;u&{+&s9cVnw1o3D zfP?jEuk#oquw83Ivdi_ma1(?#3A>9GMxDyq^hRC;~{33g$y z(jJCG>uk#~2CteNEUwtq>^OkB0I^j3d= zKsolrR9~-4AjK77gsc7jlDX8?j<{`!8v1Oe^|CVSd0E8hSX@-FxEb3Y&A;^Eg4hVI zY8 z!|KkOKa%{V=P}rbde2jNm7qUkUA`QZLEWwh?dY|QNp4J0OegTf&*x+Yqsz+yx$C+? z^89jTU8|vyDDzgWpuQSen9N~iYiUrnW~*fhG)xe(cTIcdKbh^iLTAN3t~R@=&$@`X{X+z9MES_JB5qPA{G@O$1RE@^0lEL2me zEsED}C1IlLUr~K`9b;^{ntqRjUc@B+2f}kph^#)&f|c6>_oI}W+Yx)ZwZ{*8T2G@; zo(FX|r4BShn`aO72D%q?uU>bec%uR+4TB{jZuE^t<|K z%UmYj8fWLD`Qg)Zg}tXJK1liC{103k>2pgavM3Nq~cYmYk54Rd&IFRfA$mn~S1q1Kk{|S`r?X(?|`&)XpL+fRY zO79gcwC%&K7tm`OrN$A{p-n6;b+c3l91y*K5*<^b>PGO8fpwluL?>6m+NVk>dOyu<--o=Y_hBkP-P2c-R6*d%(-U-DJ4!%M@TnkF@6dg(cA)Gx26l|V zlHN+}&b*=y{oP_>Dg#AE)y|KAxQv!Hdbx+Q5TU7NKwt zWjx2>W)y~YOh>lTxH?*A?*oG}$ALulG>v`&bVC{+04cg%3MUO>z=2R zhTDxRA51>UFa0t{ZaUqJig7r>b@Toj3BKE@ZYL3T@a3m#w(EbaTCD?a;v{LbIYJ&v zz6i9TfCO5I*qNR=aO7n)ovP)<1_mh#95HZ6xk*p&Q5bx#Nj-h#d2zBlxu{IFYhRWv z{%oa(B$f{t4oBKgNg)E?LZ_-EIF6RkGhpfE$ubD0ANal)5b|IFTJZ;-c6y<^Sff<~$tal!XrlyU-r+YHq~#V&ZP-b~{oLKIv`pI_wcWB}J`~W? zBYb@~)O&QA2|j{{Juw?$b&484hL*>|IX8V)z8<_#`&Y%Qr3WbV@Gxa+|Zp4x6rU3A|=rY zF+@Xs$+W~8cHRVi=)S)uD{eg^?e9E;hbZWmLqZ`a{GHB(-<|F%TJ~;BF8uv{ar_eq zUJ~h>j3*Y@)E1h_mgpady5M4_13$RiZN_FmNbs)lV-E6YvF#ZfUczW|d>Us>3WhtRSaJNA0pp{PZ7{s>NK>-npc%0%CdkE{QAy!yf3_Kf?K*+t~G0&N2rS9J{S~ z-LaojXZ=u|l#OrbEoGt&s5nqtyD!+>so89Y&ot2K6PC|Q8)TY7Bt3z`3*#_Qd1S>#>oNe9k&^ z{!;6~<49n$1aPWLW$kL*yW^KT>ZrA@(&Ad#czg2A$tafG>}mCkcactwpan`ohkO6P zK*jCc>+AQv0BOVw&$9SLSNtWR{3g*Ekfcl?Ca;x&9ARpJm#Q<1gLABPgPSh?eK%=U zwy<6Z3b$h;q>Co=yE%WEYSL!<3SzXVI zi=m4pSwfc6Ugj;?+%yCqf4GHeUxJz{t8bdB#Q8HtdIU7qM4O3rX@Lj8;;?D*w z`dr>Rh-+##qef>iI_`H0Jgp{3OcioMdw&7$>A83E`~_m5o~-N4#;p18f4^8ZL5xO& ziIzfGQU%<&H&&LzA(wPokr47(ySb3&u5;Yy18V>ND;o{q-sqm^)<)PL%iItLzas)u zKCrQ()L=le2YAEc)_7yjuxFkccdVueuHy9q?iq4Qt=#5Ms!^jCsk^Z?jDNx5JHguu#-JB&Ru~b;9dQoN2aj`?fZ95M46Q*mD@{8s zJh7w*{7X9>FLztyH|g)ln5R1XS*Ef9RFj@v2EE!sypk#AerfirS|b&zdX=?N4m~n4 zLVP@y6^AG`wW@4$77W@6le)UPTAeCuT^2ks2Zu9Zat^Fur|5NS;mYAO*IX!Y17@&g zgX3eM6OXYw8PzTo+48$jg>P)DgCg`$-oWE zDD?wVl?u0KW;VFESmAd@#FgNiz}jW&>{cvDB8wVnmKv*|-j>=MJj3d;!2!^PWi_4p z{k)vGrCe8Auq8!5NIQgEP;~f#&RPfEg0}Y|H7jTt++f`3!|F19F|3GSRG>E4##g}l z9fG0fbun^280FkZWn^OfafN{H4}rpflPvC+W$Rrg#u_}Y>hBzjTz#8_Q5Q%;LAnl| z=&G~XGQtuAJZNRh(VTlYz8A$)K$&9ACLLuSC!)4YncDVzgu3(+Bw>H#L{*0Hi`{%4 zdIV(TJ@`SKt>B^tR;L+X2K}S}{r2X9x;k8U$&Z3B4~ls_>-2P8r0cI_a_Q`-Fjn?r z$jGD3B*HR19OL+@)t5YIC_ksN!zizcDSC=m^*9GfBwT}w?}BFjny=mdcD7wN3>%oO zuQT;-A}9*Y>4e0j6Vz4l1vm0p*P6J5Fx>5AYUcdFwAiO7r<5ai;~}!1bW31LW= z(Gj6kxMfa+R#!7M+MlYHYZhB_L9=%b$CWOB_?yY6v^qn9r~{eMx5P5J341hZ#Pfrj zO%xcOI?v+~LKXx~3$badu{~{CTlyM(hQ3&I_>MT`HZgG0{eXjr8K$lMPW%D;N~sq& zg<3wLcK<@SqB47r^Jy^cG%6c2{_XS$B-t@s$7Mr)%qQPu@-eyfyWm@`43>^j?q9x} zkx;d=A!9J?=P60WSFWCD9QOD38-1H&7B+!+@h{jnSNLV6DHs}(nwaro9Ex@fz&^TP zql#~T6EBFfEsxL6J#RZ6R=CNh;luYz7sN89GWNAEk89FsBsfFe zE|zcE$(l0SNqq~r5Auhp@(^Ln#3Z|bv6gS4eUulM@>W7*%XR+Doob#mcly1ElwYs> z%KJi(fO)CHDm`cCoR5TH&quW2xlSwv7Gn8>`z_H|@pR6Whp|niIM0cLrF^8ZupIv# zS3spDq()EW^2s(9bdkozah+5QO`Ylo$tq+=R!rs4?k?7>=vX1%dUr-bONZ-GOO+SX z?Xe-uaS+eSjkj7yVc(2Oj=J#i@y)jaG)Y8vD5VDZ7Yi+#vlbD<%f){Y;R1z=&`!EW&F30#e^!^trl)vS_en@H*QhjeP~z?ZD45s705w-e zPc{G8m!U9a`D*zSftFKh)K8*8_oL!hahdklx_-=Y(Rjb!(&wKplH_u`jRXZ-UE9b- z1QAcU5t2-$ub3hde(vnn4}s(Ne*PWBWgJRBso+l)8v|n<@)AYr$aFb0at?GIi+OQ$avLz&g8d z($%M}eOl;G&EtZ1XGA%2YgGf`$+|IiS^2Lfb+@}Rn^Tcgp-MWUY3FQyGOdhwml@2Y zQTeOZZKsu85{6N=f3*Q8)AvQ3ykwvfmiv4n&e7xs-ocEjbF}7C>2Tj$GA-A_pp&lj zH-aa;TL3R}J}K)g2GA?!Mj|O@e6AU)iY<}>)YU~HOUKyN0@M>2Myd)5L{B{0^-HZz z2L1}1NZqa|#X&xSbsUL2yuS?f_tV`h xq%$ET>`Lv)1l~}2&R!gA^$qF7x8$ov1qWz_$u#_5{( zt5`kW-p2zzKuN4|%A1I59@%oGMUG6Dlr6DySn8tm2mZgpMi@w0vHB(d8qfNi8PrW? zU4@)3%{0$w;qDX~7bYB;=O{!+__fZ~hRSD;j?ab3E$4-Tvl>W-%!!p_})UoGbU_T^TcYv;mDx6WQNM8!55Sx$vw`#vZd ze+@MJ`wP`xiOEPbQ5HwD)D3MB6#7VF@TjElCCA7p2b@d34xlIg*D)9UbG()s2b}W! zQUp|E_5p%q9y>=kWYT~$?L&5PrPH+3tyX-xrFecLDMGB){B8PiWOB!dTZlAws@ry5 zqg&}2Jh*bXjK{^CEny7@TC|f+v4cc9$0eb-%sgF@eF^4j&<~)Yux5_II5Ezjde{H$ zsLpvJ`NB7lK<@~*CMbtN6>HqFV~hsmqSvnUh4)bzB=5Yku$MUk2J zo9YxR(A`FCH7P&Fi#_m2Nfmn+(WV?_wb*7@mWd$vfBBKpr0``SQY#9RlX0D<=12_a zAymFfejY|HqLEmijK=H6GovS zRlTTG;9AKhog_04&}4TQuaK^WxRVU|?DV@F_N+1Qqlyn~Ezh^tdg}4lCs+z{-`>fX zkpqGi*xfz9$TY5VHNs636a0Q%vA3;)Cu75dshN$uHsu~PD=Gcn{MoT5T;({bfNd{feBjJVC7&O%OU?ABc zk+u6C+g;)s5BG`**;o)Bnlsqz8ghb(0mKuFZC?gN)|RnH%P62S8E2!BD zL0&HIdB`UH)ESplRRSB#DNF4I*X%Por6=_u9DhiaMI(?$eZj9dwsfH1;3lMHl>GcW zq?-wS6{`c);EecV<;biTd;G!p^?*4Rn-tHbN~gBXs`qMUS|6)qb@a-0gMUY$AGX{5 z3iUp{qEX3g(RdlObONB)jVVzLUJ&*x8M-+xUSiro_}s#RJBxGTa@UAovCb|Rom7sj zGuv#jwfWQEt*sw4YfRVLCHuw+|O-2?>R&`%5H;Hr7ZbZK?V z%?!eVtpXQ!-PxU!q<@-|>YQ4@{CGlWnjM5+?rw9(tzQ$yDqQ}vexf;)HWli)FLf!Ayh%aZZYzL}#K9Y${fC&LA)Z~8 zoptWwk@BJY9g4~NtGg}DVSm8jPXfhR9NZu=O@?BN)jCs|`ch5QbVK4P{*2v8-=^l~ z3Vs&9>MOiJFC&t4ZVfz7lx9v3=sK?<5+WgGI>KJ3>1o!;V6V?pX0Kkzkof)8LY3MY zjp6Rq*eHc`GV9#YEHZMa18kELN%y3}p7WS>kzRpLQJM<-p4W1!q(wJ8sG%`dwK~93 z)-SQ~?5qNK?vPj&4M7V#nT0=pcy1l#;^`$bAC}jd3kvHIgx+RU3aP#t5mKpOU?2n+ z_PAvzNGlXS0<0X_BBBaK>p53>rN8l~>;k_ErHl0g<*pb&dHJSa)dB$K_7uy`nUQmV zBkY*<77H^0nGiKFupmC(sAujOT5imZY@Il!N9tRV;{3#OdjH5#J=n zxw$DXPk5c;F8-eQoz3*NVt)*>OEWxHJ(=9)Odyo+?tp7*)d^j-@d8DEzA=|ZL)xDd zQ7D&myV!2@VyGFg5SxK~o!CBh8DkO__)g|Zwv#c4l7zTiy`mR;QWQf+>bkSDwqFNy zHV#FzONgXOPRyZ+Ts&a6ZRjued7pZ|yI}4!SsL43vZ#0Ymk~0QOM1-Gq>!cY5e5y1}RaKR>0_EtHT!;)i%c69SZ;~E0 zI=etZfpqXY?kfmaO0mNDZYRkH8wShD$^5abnr64i3k$tL_wy{6Z6v*a;woJ;@Q_WO zom7vj=h_Q3D@`-ea~aanT}>?WjA%}S^C-v&`A|QS!f{xQ1c|=vYh4p>H$6e zAhaMB5=~+PlMte)bda1S-&qO@U9po-JCoNnApQCCP3XJZ8C~U?NozVbjLM2fPQm0ZmVMHIumw@@_2tZMXkn|02@pkZerZYDqH1RQS^laEnd82f&a(KN8e0=BSVWc2})( zGAX1?K$8E>pL;}gm=4jf2WJh%N0;)njw?Iht;9X#&w}R7Jz-o(uZ%GSuLk42(iDqD zXZE<*H^*@!a#p~WW0HxF7#VLGm{6!G$FCwNP-`h!kKF%osrN;|AzCPqt2g;UtK zS8EMN`;H9ILBL2oSsR^N@7a&@5Y5@SfRxxDanjHWp31dMnKYuzR1oS4mLJjvVQBV5 zYYJg;iqV(1>V?$(!o6ESGS>6}LMa?I5CDtGML}e{jz9K@KWxZRVcrsh6GZkgWB(iv zqcnc<2U>$6ty*HK*XNNHfODW1#gh5Bow*00JvBc1I5jm1kcs^bAXGfyir_8FE~Iwh zB+?N~?v4ICTR@m2BEPWLt@W74>l9j=TIO=CL@M)c)McXMa)kt>>m>;nD8Wr>4|ZC2 zHoWVq#jdfX8FIrN?|YzDNOl1*wsu{X;+WZ_BEsbQvtUWE5XB#b)i!q^6uF&9z)O08 zK9+s_oiK<|$ zyu|CtB@eqLq?XxBbMS{jv@l3V!el^V!zcW)pbUsv0=e`Lh&~1>Hfb{}GKodgva?W) zF-8VC!${C_igjGWH0-EN{5LQC>MJb+(EbHa@dVIQzj(xMoST}-g>Y3K&I_6B_S=s_ z+g;E;3nL`zZjyIOd6D!$)6errJm8e60@t~aj@pXw@9hKUQ_|)0Z3#$ILEiO;iQxFJ zGz&<~A>oT@OnjIL5Insy9Whf5>v~UNX?s_YU#;3X+r-p;Sqb?9eJD+WWlCMf^z9f$ ztj4rvwwO-!e?fP;msPqtEU;NpPUxWHkobfnB26X*GZ`QqTqsLkBtzNb zND72<#GwqezJ_!s+bmeV=w=gCHm^|Kt^shd%j?b_9nfE zpx9-9=V-hMFSqNPobldUURl8Y)@-Pjj5WyQ!(Xq$kC&So=(H?&uXj;C!93kp%`hD) zCdJjcZ6HowY`rU%|MM@!h!c508n7+lTPcGyO;+MJX+T!uK}*a~yl+cP;jjx<8S5L{ zg`UBbS_&g0BONj%Y@Z#Ks>g-zJ=-l!$88|QNafx4k_#Jyh&i_BZNeDeF>}GE6mg z%8{v&>NJaN^&9J*B1}Z`XDBV=5_lt7; zH>n?LodEfAeG*YNaMlrk6eC=Ea!rBU#9Qt6n#A4^P+CqdY|BY*6<|+dkx(n$VlFst zZ)MD1=unyT8Ul5!ilCK_i_kW7#aPd*&>Zw+31`a!Vd zH&857xvEc}1i$9G?4-rk@J-!Wsr8HQ9I9r>4mua%YQo37i`R)T7iQIFqN8Dx zY1PXoFciPFxQtN!5~U*Z^VF`Oai23&cmja^7bK-kcbRIkDyjlvpShg19B&76&J=n@#iF30L}Tb5XM zAwaQB;G)_0z)tjzx=w;OojS>MONZBS>GCn;Szuc+`M$tK+p)i`{eJ&5>(ts{2aDl& zK`Uq~xqJa&25y@!XTyv`RJCX()xYfetNZ*PlPIkoMPQkGmV*7UT(@a{GA+)HB_Hc5 z{U@wDOStZT4UR$I5|5rf580k)f>~C4*1h947f{2(nO348YnK9oyvt4O@DG!TD zI@Qm?s>kRH5(WcxsBhL+CRyI@vb;N6S3uPKdcb?)yMdXZCj>1;CqSm$oc(c*%cHzW zw`p~zpwR%)Awyi+GQvKLhMe>=*l|A2g@pEiak~6xlhzJO6REqq8$AYVc7ha!niZ85 z8bIQdqScw)5`!utO$Ic33dQMpGC}OPYO+fp(tm+twxFPJUD`T}69q6N*F&NEmSEG3 z_9Z+0vjv?2=Jxt68p{vg^45{p?@N!kQ_2LoM57jTWyEp`BL5#JlfQREpN9@x4;CFyoFW(LR^x_{e=wCh<{sWwqM)`bri2yc027Q1m&UeDwDZPKAFJ; z-HG-B$}+rI!gJOWHb}_AA5>x$oU_Q~1C~)Ht~^nw1?kxZG;}-}?ZFM7hs9?0u;PCa zqu@S<D??-6mHktD)I}{S(UYnMPEP?rBhNi;2lRV zg;Fho(9~R`VF$cid{F8BMxP2j(Pt{n|EX?bHAx`wSlIvFQVB3qn8XoJNHg8+<|M{6#r!S2X{Nuw(Y>1Ksl3ueLg*Bd)XG7ns>9e+bvjO-y38FaU;81CeM? z#Z55cMbn2gKNPl$zz~I@i+A)tJzXRfH5w04P~n~-`S#u`v-#X7>+B&a(+ZJ=3HbQ@ zhI8~CGO2Wr*UTDm)9S=6{x?y zyA1zE^a}I!PbTtKgkQXtGl!w&V-${Y(~Ub6?dnj(sFn{;Gp%fgva*aOwsaOi{K<|5 z=e~dL877&Tx>Q+&XO#zoeEWPLY zNge8E%7n(`e-(=Vb z30W5muxecsQkwj!yD~ewTsogTnSBN2K!sz!yqHF~Sy`0@*maQai9AU_d9>LSpKfl& z|8RdX-B<->400Va*(i;6)J@dP34lD7Djn|HC*)L4n#=OtTVtk+@1r>fVYPaSECNw% z+1!7Q+|UF~7e10h!3Xz_y6SDtaIT#-3Bxzb+m+DH(T1>!azTo{ifR?aLZ@O8ysV_I zy@QF!{|*rn5{9MR+VeN~Vx}nE{l<|bXB~8*bc&OMPt=nhb(i+=cY~8z-Ekc4h>(%K z)H>x>CrXtTQR~u><-b+;*x|nGuD*1i;ALmA@y&>Y;ph4C_cm@SqEH4~XLBXUVuPlq zyegUJyZJ}^4qw*Wg^=?hT>9Nt@d%_5Kr47Y>2D($x4K$8J?w2MT0%%TKNFuVou`mb zOQcMyY+UMB@80{Piuono*_Z}%fRS_^l0mbmS`~VGx=hlplnS6Hfdk(*sGNh5QGT72 zI)R3|Tx<4uoVVu%U}X<>0kcVGJhe_XX$WS<@grqK;Y$@XR%oKzho}K~t_@Vf4Anp6 z?=3q!21Wy#Bh%$jU2ePgKES9ow{@RLM)$yfUCnqeYy@($Lg7ca&uhLw0WCkgo}&En zaFvCzfk$yjxHJp*Ywm~jt~=S2Ja=v-3rJZwP+lc z6wlh=$7#dWh1d%)wTZQI`EijXJCb+boNc)#iK56h79lQZU-PWLbIMNRWLOn3!D;Om zk=*zVEj8mqxtK6YUJ?5bKXp4#FF)Fhm6x=%+{iP(TQyTa`}CiMpuY=(a~{|E)r`S| zu`D`1GPWfc;XpuM*Zb*`<~=9B1Gr69|#H_D+$D+EcYO0aYv7J8jnQW>5X#`UJJ~ zs~v~A!|MZ_wNA3k9*tNemYA6{P3x^a*#wtR2NOL+esWN}M6Pc)UyjV}cDYD;MQQf+ zn^IY)5dcv?PgsrXMk;_ZBnHjF2Sfwe_)d25oiq67DghLTLIQb8a!1}tJg=BDdXI1Q z5A9GEKO>BVoZ=4{Vb4y9Qb`T+v^zKwPqYe+s$P7HtTG4!g2D5#g^>E)@RNynAY$sB z{6RU6@!tz zo9t2gR=zJ&06~SMN>OxMC*cg|G!s(2uZmvl?&3`ZVL?>BC(*KTuJg?2*<-jph7H7> zlfH$NZRoaieJT2s@M1g496soSl!ONNMY^d4g?Xscmm51L_HtQYRl9iUA6My}R;#){ zrF_TV)X4xXp1t9JKet5#2|Vx>irwTpFDe|D*$a2fhS9Q@kWE+#TL6*E`SMxHSP&X- zki(lLq#vKqvonJ5RU-o?G1lI~bT4W;1%3DFb9)QRX5*80dHBt$Sjo|@M0?Kb2jO86 zrar4p9Vn?Hvez2v2;xjJ^dCp?((T0Q57Yf2?0M|0TGF2n8ilaXWdvH@u`ASyr^{(? zwd*U)@24O-iAv*A#gf*@B|#&ELih5jXDk~?tDAp)bIkS=cA4o&V`CyNnpXqrm%qfD zzB7jM4&!Ih7gjB6j}p9XO-+>`Ff)T9#RO7 zmvWZ9xKlA^h!qO21Rb=<>&=NDjUBv^qC6^Bid#XR0E3J_yx)sxsWpmB2wC9HnyK0M zB53oFyoLs-DgG-4D*24yqw1$X@^0!}UqoDe*St2;>P))iqYh0$7q=R?iG#rpi2NyI z(4cKEfQ}$x;FHuaf2{AA#iYf^f&PVaRv$^t93NOqaTIf2NC4M`%G|h~=1B~s58t9F z4y)VUfbm`}0-oGDW9O)PF>t$;a#)3y1SQ!w`*j}-WlpVyWp86Kq_HC4-0}V~ zh}}2v#egFSEZsOKBH2Li4NO;^?gVQ;pE83eIOc*ZUSBnjNk<0*wN>Ap5Rhdq0@pjO#2wW@UpK0ONjGxEO>t|{qdWSbMfiBL9)tPhu;1y5D$jJ8;=WIHkH^gb z?B`ue~F(#Yx z*!GK1RcZihKW50u?7q@0p%>072Ww7sVZvxe&-C0n(88v>q#@>O8)*k*W^~1p ztXCsfQi1>ygCPLZDcIe;U^{EEVN~#|)y-I)lL#a}fad*(64{qKErZRN!ATZ^Hdw4_ zu82{}o{Ys2**b7ki5bPJg)+rOZRh>tk;9MG6O29YHk!r75_*|SGEINU*MxMcgz8(K417WDo8%fspg%{z;R~O0UQ80{d`l{C}*W3PiBubC%9}e zYNkP2`ZA(QoiDu4uTB0&q5~h8x?cHOj>o;Enr;iKR+(XpvB3`1(xocWmU5B;Q8ok+j8^CNn^9JoG1_%=na@i+VLm}m!W{NJ!o zms6u89l8-6`0s7j+9`ct{;A*EFjcrC4u_sy^R_l!Y*GQ(HHO+S8k#Yp<&3l zm}b?f)rt-P+%#g4ISP6|!z8a}12;<8R@YuB&8wu&>59lZJ2Hf~v<`Nn?L|hwcO+CN zUbQ^r890guoO-(#up#BLc7KQ}HjDb|PEOlXm5;BiKdom=NCKS$-6y@LIYS+gpvN0; zd*~>ZU-VUtN{37*qc4OQTR5SX(P`Q->hfwOf2&r>Y_OnOpm2_E)eAGriAK)DL@xvT zy`z}W3JA98_y68LV@#u-Xvu4lBVCzAC}t&=%0^GgzoC}CHu(Z+OGlB;x>F3i zZXu!>mmew&hjUo`T`L&9B2n_UmD+ABcdjJ z%BR;9sLR6fAGeE*g3xtcpm0dSy&liLF-;{1jm>xJ|=ALowryyeqNMZ4jYjt ze7dauhuY_j)qV7N$$(qxN9^{vOW?F`GA=LWZ^)zK?}MawAVd-lQlHoCN@4V1u@~Nc!AzEHaE*6=ul2lvx+ugPk_Z zy%{#A%O7)!eN0%}f5$|{jty9`CnzTY3Q71%)hKHko%dwa5PH5pt#0-nmgK%Co8C#u z$-s?{R?n*mw=^Cn@~>aNUex#t#9mDakv2AR-p|*3XB5+o#irLVIolARVPFumwl4oW zI=iq?d?^Vsg~Z>DK9^ZAEmE{(j{)R1s7Kz3gRwRH1mZgvax21BE#;j!9|=ve98q?` z0+9=W*eRLcJIk1kGg%k;1_HxomJ(&dwWv4dUSaK{f9s^-Z)2*8mTD_xLiTsRVG+US zZbAj=MXOVnrFp_RN0tDM&e)fx)59Y+5TCCX>1z)XHBF|k#cX~32#XOvhTYlqS1UZ+ zep;8xae`&y^C8i@knm|{L#kPi{-r7uRHN61_1?FD&YYaxm4nk!XAYH6%+T{IOY9x; z?L6$KknQhA#m}@NIix`C=V%2tYgC8%K*-0(o%4OibSlfk2RLyVzwO0x*LIEsT`6Qj zt=9`a=8d?04uGL+_pL=`T(yt7nqhH}VN%GU=6v)5J3Wt+n-){i6^ZUOR28DsI4}*Y zoTz)c_)xx|gV3aa30WRuXZtavc`RY)g8JTQ_0=5Lf)3`dD?xmU6ns`9o^%d?fKERl zT>2|OB>|&3p5y&Sd9uuem%8;Fuh>k&z~r+!nCOxOeAijn1dYfBhaIXKW7lR&;;qc8T{+qek zgw&6|`+S={fiPDWOF$6q{FqPc%dODX0Is1*aCSyU@r;EnP_&56wGcfs-DpWX2iOF> z5|me=lp0=h&|u)X&?D)4f;8-~M*iI6Iko6oS7>*}0={iofoGg{vdEJbouPj+`9d(S z&Lb!X93y*6EFLx@C6rr|wHgmUXxP&9jK(|_UCxCY)f8?D{rr@D?meEuDg+zR?1%dO zsa#oTGylJBA=qmbz zE%NKwpRbWde}EqH6Plwz#f()fPdWntALwFFA>|Dq!eF}UIRW5LO4F?kxt)URsCpd9 zBg9cdSu6g8rH3F$DTYht50w!GNOb_{8WMs06{SGW7B-rz?P5$6#N{cVd@fr~{{W{shj09oF zV!)rD$>}Czodyz(kvWZrHV{914}l{qm_+jkz&sk&$w3j|2BXIIcp+mV6@4zTA+5Ls z{U3(|tx_@*-vS3fQ1d5JlRrfh<*N%>b@GTa-ud2V9!nf^F*2mQk`{5gw81OUc_uad8MZMJrH%B%JD^+^$_ ztNt%XhTtjxSESSK|HIN*hBf)VeOv{kJEU{eXht(Yx_dMtjdXWNcaH9^fuuA@cb9aR zbSp^s?Ds#OH@w`zcAxiko$>vC&VQmgUb|5WI{`Pz`F%(qauTKke(RsDG$M;Vj;da7 zTy7TC^u9JwCXmt7BSSX3pASy2Q)~SQ^Iz|@6xgiHOR9_s6U1Ia74plhgw&lML@B2` z@hvGcJr7r^!F3XSPX`13>oQlwso`K`nP0okegS^1)3qB3-Qaqxe0L4?|kyvC>ruA(pAV3H6QkjUUaX5fWX_Q4V4jP^}}Cx<}D3g zu{I;FyT7z0CBwh=iC3K8D37&BeGcyZjnKMRE;djTieCp9j{XUaV>)K;;SYGQh)s@O z|A)9$ip^wn}mWBgN< zmp`vm&=Sm=txVPMqGB;;h-iFdq~o9bmQp`#jXd7;g;}ak0vZucvoA#pczZ1bsV&oo zYY|plGLL5NlqC3+YYiFOvdVO7l1Jo#IbP(bG5ccyRp9cjF8r2t7Z4Qc_wYFVmHl|g zbglVY>siBiQvT;ad=&wsg$ts^un0={@%b$8ccPhIR8b+e!YJ0JRr(Uf z;YS%3CS4#NxjB54>q!akl}jP1>ijW(U7z@^b+`q&toFKk%@ErZX52(i%E&0V@KuYX z;oF2ww1Lx&dNJ8H9`0YN76LD~9`8_L6uC$p)+EMR)hSmbSARRxSV!6um2#w;c+(eo zisq@rB|8dph><(KjdLuK2alUbM<X&e!vPP z-QSuP#OSL_VI<4l(ni>it2kG^){`5HC@!cOdBA85F-Rph>&vL99C1_0G|Z({lBw|1S>vKRdsj;I?{_p)JnPQGxvh_fm&T*eL4~j__azY~Hj%(Jho@Yd zZi^1M+)*)&RUO_T-*d0*K|M@G9Px7z%Pn61mku6Oc-ulkh#;r~p&!oKXs^{iq_Da2 zY(|~$jWuCagc&%$(8oBvq9&&(SkMP8nf>+i;E@IW*XuW4Jp^rYcYy1$9U^T!snO0q zi7nh85^_L2J?SEKeDV5+VBhW8=x(15PoB3jrG{&91!Z!S*nniCjW?G$ji(9})B zKV-_2>u#2(eAw%GABwUYX7>;VC6=oIsAO5fvj=26gwejx%HML3bw@lDEaEtcK1Xh^hm4j>(h zX(%02R^={IIGzpU8KV7PfNsVZ{-byvkb8%5wT&IVm}qao&Bgf+nVEC(Jn>oaMnEm& zSQ4)4YFA>dAR!Mz;*q_IN|)$v;jfkpg2fe5$P)qH_wjHfD+we?Vd9ah4HPL=N^l!8 znq*1I?jAY=PJaH>mIf;&hstp?!es!HZgRUyA#0mHr)=QAzlvQP#8kl7h=SY-NrDsw zuMfjwyC+p*A^0lW9>obP^ZNrw61#g{YVItw_?-TSG3H&3-$@c!w_<>mDJ6>1=CAuo zN*qP2R*7hBpVgu6Y$D70bCH-Gr!$Px-q&|vzw4s5F{O#4sk;o-O}g6ABT4w+?+2jLVCXpbn*1%N2FM7 z!vR_3xfreOKSInV?r(Ntb>HH9^3`&;`n|JG{Sy#bZ&MT4w*X{rz157S-oSP6wu zQy-7`%FMKdDvaeDAak9nl>IMdsM+HfIDHP{>@7NZ>T;P-K@D}Y4l2TX3iwqLJM`1x zp1pWqH+>@-ZUsq3pHJO0$YL^R9&My*W>_=(@T~o~!BxXYapDs^l$cF^S6s{z8Q#qP z-Q!{L;U_CP@#?)|zF|=oi45p}p&F0aMv3Ml$DNgk`-3ZRsM$ffp+k`@)X88WA^yd7 zFH~2<&tbm=YRonw_Z_HdE~DRY^2FS2N`v-0?4ai+T!9WZu?Ie6!vyL>-;Yl6FyC4k z!xaYCU?@{MHa$b1-7rrJ$bbI34NeE{n_z`B+;1pl%23n4hI|Q()?AJNE6q>b>%~_e z0(}3W(TuyW=4FN1PG_QKhxPJRT>itit2)ekS+s9GeY8i3L3{XHPGmZ@Z?iv@ zd!j=btzheOigPBAI(*GU^Zb^y7#*Pg8m?!3c$t9^%^9t$ zD4RgKmmz|=ycejFaE3Ls7nwm^Suyr!{I^d4i?kr~!!hO&u)J;dKdMp$67-ZK%y3#n zV*l^uwvPM(?A_D&0M#KojsE-ZM+G-p_ji_r?kq?mTB^;7Y!syoge9~bAd*p34}e$f zTQu6FI)EIg0(U$;ROVi`$pViwD%4z33!VVsQ6Di}Cz4m1k`zXT_Pb(hc|S8>6PNY6 zrnD!!UmRUq3UekDN7D;l>w463B^-$gec)|&+fEbfV(=#J!~8IbyO>uoadL@dnLB;H zhi8>`Ym~jb zg!IGQ)w+cL>H=Er!+FQdBw^DbaOp{uL(SS^M(fhQ8*7?HDZk5Gs|v4`ViI`Xg@q0` z{CoN9;4@hIa(xZn343x5Dx-%FvyldRkbTEW>(bnTMn7X$q(jSwf?1pYcxw?96Vy^f zHL&&CM@T|Gi~?{lsA*`_aijG=1jDCtYSx?ddWlL8_%yv4<^yKReY8k&iYE!Zf1?Ju1_?9*QZ&(7$gYAL_#;FT+ZWl9({Ky`+t<9jm& z|37a*m0X=jGBNj@5?dV6+de<1%s)Nuo5k{V0O@31!6Wm-xPdVRqf-RpAo7C1^Q*}*6Ikgyv2h;mV1+|t$<07n z&~+QR;7B2wSr%sIkD(+47RlaCQ8y%so2NuHt2!O;vX;@O?aP>#Laltyl_96BAhmpr zDmz-^bw{kZq?BJWFr4BgW6$Yh;?0&uHpQaJ}WOMsr zN=sa5b6PcZUtfDGDEr@caw}Hy1Tv1#Ma{UK*hUe5di*jT>8zn2+on3XKd(D%erN$* zcIs$W-UyTpT9geRxqTt^!%|9jg~$Aa0_x>_^>^a3FOtWua-!V?CnoBru>yluY8x|A zE^SmoD)lqvW(GxTG|_2r{cKfIkX$>4kGR%TZwOzk$cUK^t8_V-Xm&C_lAu#_W=t&_uR;H{-;e7K{?ZtR5_pUDKJD;{e%KUCWu2#QgLmzVgm}U*;R;%L`!$_n+7R9*Q5lb*OQ3hWJeX$s7n&5d zt542cj*>)~spwB}{OX-l)m@%dF+G|>#(?r?q6&e>iRFJe_SxTwov2tUc>dgo+#SAd45#DP-SS=h_KfLb@nmA~=ZTf!Ks_#o z9x5($d@Mc(H!^FT5FEH%D^eeg1-W2BN?4>(_)5pQj{#@8q`@u=FT24L;O&R*3w)YgI+R2X252YPV zMT3S>x7>Oi&MKv!0+jJAYiX^Ezh#}5gX0e!HpQloJT_TmF-`SHM@NAaWxehyovPXl z0$i$yM*!=oOsuG^>B!+L?<*lUjhH)W0&h1IJ)R~q{|ehIq0~l#sE|ViJoG13D|%n) z4F;OeDe$Hx$El-bzCA$_; z3F?0wQ`^=Y#9=skP@79P%xtG~n-Mu*KORFBbW=g@ayS0h z6t1)nWZNA)Ect$4pNAeWxhQIM_}okf!x6lc8MJ9BVE_17+{3kEHwOSt@!R*K^Ok0& z;#oEf#BBdPtV82wmU_jH6s4%~Y4Gu=aK*xzU7|+K~S1$bm&m0b9=8R1w>k>^c^PY5#nQs!^^HkrRoT9Uo ze-qAM4$Li$6L7Z7Q)*+HP)dA}skUrjm6!O$v7W#y47!QNA@{PoR^jj&%CUTt|0uuq7~W)ph0us+|!@H8x5D8O|KXO^5`oAeA|Hh!5a!Js&_web;g4(m=Cy zW{W#g#M;Ld|3PYv<`|6w&2KmJPOAbLVV^RcRy;EI`EsIuS=UPF=o2qXMyme^V#-{r zCVCW&tIyI4GP^}#`2s0=9@4w!V&g^Z#tlBTL`uF~dd5Qe0m>UUd;E57u%0|Xpsu}EGcg7?yc-*87m*$_E4N+#Q8=KYa=OufmlO z;I4Sr)%gCbF5UrKC2KzAIK9mLE6RE9+Y|_I)$vE=816!@2PC!_9^SttiKc3y$9r=- z&C>mJHcWJj5wCK(nSAFOIJ5YfHt}Kcm0u<$!jbUMXC3AMxV|2fPo0?EmH?b4uEcT$ zmmZ-_WzaA$m@UZ3+eKI*^-{a_A6IhBG5%SI;ZtYku)CDe`GS*9kse(Q=)gG)j2^Lo zf^TgOW4sSQrldI&v@z4s$<7=mr8X4g7pfP$tmbQ*2eM|i>7y!K-qsR{`j zr4Q_y__-NE?C66xq9DyKIf4Tjck?W$_XZtifb9^!l~><9>v$kJfItXm*195UOfyA* zMHirAv56TifeDgRkV-0E!e#01W;!6?V;3VEY#VhE!z!Wol{!}pllmd64TSom-cVv} zz41ui1wp1&Hsk*OHizS(5+6-U6Q|)8=#GN`?Z^j}2T?DKeE#`e?rxR11E+2HiEh_>XZ1WNZ5@fnW2cpYoOgCvbEPtZhd53#5W zF~;Y^;z^F-@3*t=vi4?tgtnIPbqPUE2eq%)X+2W$xOg|3>F15*GNr`3FQpOO!rgz8 z1f-;4y{I=D+pdFEy=cE?Ph9k8sV-9WeYy#lR31yEsqE6hkja}PD;Ub^qkX8js$B?UTr%K*D5ADxRH9FccC6C zI!_;T?ZZ$HQeB^NC#ql##K*&1xaPTsGL7rMzZ&|11>r}Yy+{}VkwQl!`E!27O13j% zS)&rED-e~mhQ2vdFM9n1!`_>DBa+X8Z>9ttz*C>VMsdPQn%(4osR}-D z-?o{$B2oI-mv>@gzut6>r#$+Uz=63#CcPNzUtf#J4Zr?Qx;zrX6K^mzpju=bb@vo} z+GvJ3d;}Q5uwv+mMvWp!g?U+4=v}ci5>Z-2-hUq$Oag@3$rJJ9H{fu~CyXWGFX=63 znx8>eU*qTAKc8J3L|+XbJ(SGwEg;$VlRLIBf!;PUTC&Vjwan(oLe1aG;T_z^?!`&u zFR7nMGZ6V5-V_1`UG)kY)F1%XQ*!ADhGk>jl)`S+%*ZPJ;4i;Wia|5AC@m~s5TBeh z2ldSo&+4f;2Fpg$O0ttr)L#IfmlpyLI)oB)&HEV>#s`8?qu{DkETCQEm)#tE7W#>7 zmjI|AmueZ1|1#?6I@3isDtJ2e$%|vixv06MNV4?j3dKi%GyK2B)l8M=eLwx(ZHPsM z`e%10S;o@n-U+6RLPjLzQcb8=JjTaDr589RQ^%fxVIm=>S3YhT#iPxdK#6a;BCwSp zU`p91u{Bu>u~87;PfneA+QNwzsEj^Tkyr6MAHQi11U%RIoF^c2Qvk0@un+$9pkYb| zVWBdM)HlDKFDEdA+x!V*vI*%xOsJO|kb>u$2S24Nl53J1!OSVy^`R*1E5m@Veur+N zdwgZqQ12n@Q;Z>pf{n)Bq)t6HjGvrA&DOrG|rp>`fsbG1(xJ=gZy& zSX%e*R~m3EaQyR!fi{szk$9Z!1`Fyk4cwJ6Ig(P5eLy4uLiS>iyS(^NUySjN%;nA+ zm42L+(N>gd9vC+t><+Bv^Wg!3$b^}(?uljE-KeW%zuhal(I;n`JCdhi9 zopQ~MwGq8k5l81)A2PqfhcG2hnNN?bkzW*gD|9QVkrvdVgva$~W*E$wQW?T+%|)$= zv=y|nCKs7K4L$!&1otJJS{_}*IVQ0-o=f&gseSZ?~r_bB>X^z0*t-O_BGEw(;H6mYUuk!x? zxzka+Ug^G#ia&28$Ble>|3{mL`G{0(-s&Az$9Ghf1Cq5p;dZG-x~rE?Y?YqK9&8h{ZGAm+bE*A7GQ{lXbPPJA_*%$fJ+K zzrY__Hia{GDx|(~CTpOINKr`hJ!Z_}PnLLAs-9rts98e4eDZomzN2`6O1n?q1b*#w z@FwK3ll?OL(U{U31KnwpC8`&0;mq*@vmk1$7dDoEc&akEI{XbhtNfy!7Aa@01}RoX zDph+6G|x-fe%B8lu?wAPOQ+dw9ZEIUpcPP$2L&Dr3IkG6=q%aSq}rw&ST$qj>Iwvq zyh%{6T9;GDi{zHie3g=UZ-LmKUO^)fm>iw;=ob|#6RdY*t~?j{e@1*6yb}DV$T7OV za-wlu{u+|MEvJ+GQT#Cw%M{_n`?_P>8KCC?VxebPahZy`%)U%}dQcL)&TVMuI~u+W z+rpeT@lhHEm>lLlJ;t`>A32dR8ihPP_$PTCC)8Q`B02ba<)R&XFOtmf|8;C>{P96| zRIjNN?Q4Jy-4DOxU(`z(R4x!J%sA{w1!ktDGY1*xW@Oi4|4!U28E%58L9!`s9+HOq z%}!=-T>-YwW$%=jm5^ntXx=XId77NQ`c!Y|g~j>W)SGw`!m`^_iiy{_i%YjjQY5Zd zv^whcKGLxZT9B3{D1wy6HN0;0S%0Ar<*}aWqBK&Q(~MXoOX=ylh$VpJlaW|PH3%2_ zk!b&L**ln-j7O~rSc#8HK!Iu2*Q?-&Q`S&li%fsY%CcQuu1I_f5~^>s<_8RGYKi1A zQJH+Kc{$;zHyb#--)N+(Wz_K0b&_ZZ%)OHGEfL9#Ey9*ejo|Y5MP6@_!)d^KN~^5c z$*1iX?yUxcOqUJ>jk?Q6)thSNby_z1Q1k*{D6?l zeJ8v>|8h*wXYq%cW(|^0HmeT3{6+i_hO)wbLZCrQLly}|(?bAX_Ei`=((i=KiL-9(oeI5QU{)OkMI+D}k`L;W!xY<9v&hO+XsS&&UUv4ZSeb>TwY%(eQ8%H;Xn|6kV}{qPMpF!t~nV*x5QX64W~C| zgL!Y7bsp6>@YMbsHxHZowIei2RT7r0F0}r@c*~rr@wnTjl8voL%9TxOy&!P0@)O5WSiOe<^SdHQ`UF$YprTa)(|pwe3k#;-?+JeNy`wP0 z@-l3oEQhG3DDwNW?Z!gskSTM2N$0VFjTy?|?ya)-qn;xo^DaGBgPtQ+gWzit=U7uw zvCmM|8-dWS#oT4c#c-dq5n%1$mEN9m`p>B7IzU37Hbtl29H@UIn+&@BKoTqAxJB*b z6L;ao^D@n(5YCo{<%HXh$U2=eUDVd5*TsS-lF!!h@qGDn$QJlH4fepAYY<33@4CQ0 zffns&!cCF_C?EGluTq%pANNo+3U}I|SA;{%^lbjgMf`7g{UeJV++9f27~=<5psikM z$I-nNno{d>34G_13pKWtX0A^x--GBWH=b0DUQhrGJpP$bO-6acK?%G=a>|LM0fyD; zYW2pyeMtQJuC91SQxSXe#iB;335qXSj&i(MTQu*Tgwem2iSN?EiFv^#mN2~ws~rU> zLq%O%T)}W9%EtwFEZ}_GGzn$|z06>m1p&2?TW08(Ew{*Ff|TQ7_V@%xi{>ZiDL*G# z+-t+;T8}5+>yawqtBI2?*tzR*u)KhSWg&O2{z9VHjrqP|*|@7bA~@aT`PVXJai+5U zloMRwVq@ydJ)QI{yRS<*T_ZfKjBk2HO4$V73eEI%DAp4K06J=m-ePyxQEJ7VGmWYA zeVEQIe4lP?A+eE-Q}lV{PbQ%OSN90*9p!3vw3m2Tz!M)v zjYOzM$y&ObE8nTstVUo}QY%dm-ha+v-*V*cR4FD2Nmc&6c1S!ZJf3wx7Cl60HQmn` zrh81zM+)C-vR7xE!LMgvwR7!JSmeClo6ltXXv2Os5FFueuUKUOXEvxaf68KBk&b*J z7bLO?c%NH}2Wd&S%e-lr+jf&HG#A|R6nx!QghI~-`gAL#lwPJO3S#GP zcH14~_b^>7!y~Yu-@ZMX9-P=U)M}&t+y4U|Rr@0ze91!3+!J&)Bj zpOzzU+7`st*mE>s&!h8R;>F8-BOvrI?by4Xo!4dL)ae0MfEKUDYu#0Cy48=C1})s_ zvnaihV*IprWJ&+VFTDA=@;)UMHF?x|`t_JB{Aa^`l`9XsVYj{YJ0v1e!xyZTX=cTV z>B%*bxj{F2Tm`7vOO_+DqWqf(`6T(cwWFS2jI!4rs|7q2N=rNI$)+7``u@IX;6fc4 z=z4*OlnIpTLji4$K(%K4;lYoBh4rdz!}`iD1{Sn%yfHPi#|~c$4hzL^aw7F3i8~&u zh|)gUd4bzO&xy{=$8BG}EwcQU$F91Bn#GHOaQMQVe~&FGAS#MfxtiF9)a)I&fD&UO zGo|>);_Q2PIZQf?@}ODv&X`Fg9%W%>wN&Qgios@RbUP8xcQ4)v+wE93|%=58a#l4L-Fdq)Qz#pm_5L#k@MiUWGXHCSE3ql zKK`F;@I|>*b;{FKYpw!rqbi$LrabW>Zze;pM2`2~fSZ8bcF>8;y2}xFn+%Vr=j?Tc zdJY{@dlJ7^0hPu&7n?(IG2vRErw->2w?^8`g!Dy#@5;4Ruf&^9lop!R6H#+D`L;uis3wcGqOeMVqb5&yZv|_6<132 zVk6%61v5uHDVZtylV2N6g`wcQ9Otp#K`!I))Vw0sC`Eb_n(oXZR z(QEY z!ei2{RLaX%;dsH5Q(BVvJzB$rY8tg|6n4%hqDSBWl|=#@Cq^dX*ybe0t(~iz4+rNV z0Z>zrm==>O^(Xok?vxHZg2%x>5vTd(GQH#alE?XqzZEOioJ(5DG!*8oV-a4aztB7; zP&P>RGu(JFcj^U#D2Og%F<)93mv$<(e4Tcf8=W@gLHpohPOCx(=$l*dt5wU*j%O2f zwR<4PEu$4yLVgvNujV7UFrT9t2V zgolNq+&ypEinnDA=4~{XI6d`E1h2{u{4D1!?X?NUC;eMNE;fC%3@Uw=hjsYj&BG$t z$5FcU zizx&Kh2zk4v`u`P32wS_E2^p_mF+_yh@^G0DV`RR**Ec}$7vyZjb!L%mp7E&{>@D; zQpLmRmk<^C^c+!DLGJ5z*5$><#M9)+Ry&pT;pe-N7DM%|pnXJS7tOAdwl1ofqre@! zQ+=NW>Wk`>&JFru&Zuns+rB5>X0p^w{kGZ-i~9^nVt5~UJiB>@_M@Tlyzr9XQJYf8 z=|o4{l-1OzUyUJOl;LXu2f2hvmZUe_K|q{J`1SJ5?}ae6S%RVcPDH|50h!2@qZ|GA zf@Dkk+`V@X)z(_uFHTf&@r8D?Tyu_>cbPP_R08vTMm&U7YNP5Z$EEfLurQNQ#g|UU zvRM9JLFZBQ{r@ihIyjmIyM|R~)*Wp=uHclk7#YIDx_-;;r%_jKHWd2btr93bk1o|! z1~W#6bi$bhi^TQ9V_Egij$J39_SzzOZ|{cS!RS=D&?j^aj#AyluDS{?;#QWRkGVJckRLUja~V~m0%*&SZ6Q7CkKP6$(_e3fa=1(@ju!+ zP(|c=kI~VzS|~k0dTqyVULPK0)DM!cj$IQp5hYEx*8xI@y#OY>1o=)L^x7@0DirU{ zeWlhj8UE6LNHC7(wzNg}?N$y|ao7BbxhwZzESgi$j(Yy^D!`#uR(jXe&OSV%gf7Ee(w}4T{@WOO zTU+u=kfcpTXP_U*?(@Mm2E0x1@fUe(Ziq7H?xNtZsuu2KJmLQoHounp%&^Hlzi02o z@p%F@xjGQE7Rfe8?-VS-CLVgDavt!Z#ND$wm7xmxWLU8FK(dX2t)pYIgwaUv-k`1H zMX*@~MY7A;uQQ$;S1ft}9>P*vE*2lT*5TqewhWMK+09HT2ye2_(T;iWl1eW^P!rAR z-g}r?Ym!eA8YS<+FzZBy8j6n^Ll`w#R5oU_#>p&3g74xN{bg&TaFkler`6$u#kAS9 zfCDW#pV2gNSsZ@CPLlxc;XTUc^cd!o(~_pG^CY9?&Kc4pkjmOxrrQ}x4{u18zT=rb z$N`M-Ov)+b$mK9(-V#GumIQIZ5yzY&c%XzrP>MI^y&=FB9A8Mdg4nZ-hf%a$WdG)5SvU`v#bJevIyO(L63hg{0gzW7Vrt*rRgu!t2D z4_a%f43Mqbo9ht&Z^$Da7k$g2Tw_Ma%JT~#U285R?VRzwVO9HpRr==O7dH)!VfY90 zigsEWmT=w)m3y?~vVKxwl!UdQ-+7{)-(5tARO_mOey$wKy`#Z}a>}W>`Oq+HaMhpJ zbaDlD#c{QsRkvwS55qbWd6qpM*N}fQ$(Gj|1)ymp$5P`n&~UN2<{oqTT#@OBA^A*!o$x(f7%2GuFE$eFc=`{@^Fcxozj#VnG!S0L7*pV%%S4L z%utL`I=-o3-lujh?>PwJY(&@x`61d+iTV9YssIGXi|Qe(DTCNjbVe2lno6jf94o_Y zlAizx@Y`424NUofwfgcR=_HgVWY+;&EJCYd${Lq+|C4a_2gCpT zK_EH-EdIpmj;7V}iEOt&y<;&QS7@MR%#>m%Ipy2Mcv`_6T3i3KUY$Y@2`Pky7tpj4k>g zWjryUGS11??+kapF_{iVlLCor+a*)JxORL7A_Wk_B^5lZ!<-zj)3|lAMf%AmjBNR$ z5=k-^LeT`xk%dwTESD6zX2GE*kdYJ#$qX>g*Qez3CKQna(45uwdAC4cC7>f4eMiz;Ug>sO0J z)MhehPwet={llP`&UW0nmM%{h%92qlF;*3>Vn%2Wm|USEM^58V)PYfM#208#UtTSb zGD%Gs93AWLs2S#@;a{{K>ZY^{$l#j?O*Sbd0OT91D+^W_I(!sSFT8y)Z{|8%kD~{f zChjT=UOSQTfb)Cxy@6X!`gnw6PLU-VSbbaTz|`d5DtC16DFU$m;z-0mw24Eq#zlJT zqLn8LH!1)ti_y*%6wO|XoAURWIonXQIGs#4lIoztPn7)@SVYrSHvt_0>u1LQNxD6> zJ+`BGwK6{E#Db2@u^bm?9*he#VwqfT0+|L?AU~TbUtw+;FypPC7lDa*W)oO6BE7J8 zW79c_QAKA`N)xI&=U#<4vY03Q3sn`cPxMW#{|-JaYi-8jAF1>pl}~8Ig8;%t5hcLRzhgLO73|9;Kpf4$VQp+j{bGIc0#+X3S91|2l&2&<8fJ z0-3_U=whQj!gn_SdOXC0mcF~)RRlDVpEftQA;ht5NU)2SRxe{Ru`IHjj7L4+A)1$Y zNtZ?|#3aZ_`r`b@0W|o-YwQ;QRLT4tYb~7#+!j%;00;x+@zAwOZ5mU~?}XA!ahsGs z={B`NP{4$Z(T$;QaXq&Rlc>zjDuh$J$)OXj@aok~z9CkpnxLw}xH)8w^{J0cxZc5_ zl4*z7(0|I1;E0}Pfj=;Oh>s`c#{A98kVki+=mE*hz<9F;7PR-bDwJ+buriNCB<67K z4nU($qvm1lEL0)7m-^0P8z-(9`M+|Il#VnU6wif7)s<*1T2tXT7!UO!>`U@zT2YSt zB;-d)<}%k=Wal5fZuIHv8+t7^g`GT}TH@hJCJB#rE*n+j)@jC=kI9A>(%$_wYwGYt zdekaeAQgfFO+ln2HOzN>a*Nb6Chzby4*4z^S75&+xt;yyFY>5=LwJlQe`+k2r`zNb zhS*hNxFKQSI{+ji7~eZYs_)&*$wv;zBhw0Dt9vs<-?*)PgMC{e7D{;Wm8Q{*ig!LL zO!}{wy(F{WFj@Hio|9|sHvY{4s}wMM<$68xXUUp1kElZ3B{OPt==im^pg=D>;Z}=s zlD&nbF!6HWKz~NtS^3KGrPh{(LJ`?*8If`C_jgY4^&RRx^%8w{$n@SCg=vf5P5UqO z2|kpVdN?^~HnxVa;1QB7|;b(8LQC__BsnC1{tF^i-S zPIP~Fm4*;?e47c11g4Ci6_rf1Nt9$tR8$ks*p2(DSz18L_f`Kj$Y8@?x=xNO7H@Y& ziVL6nH#MS7D2i4VOxOwz9QMD4D5lS)8`i?J*)sr0Sv${#h1br(v$@1Gf%YwDXb`=v zvJ_MF$!ZY{4`U3CkI=mWL~jy#;`Hp`Yj5i@#h-Ne6q!GxZAxX&Gtn7)4(e#gcj|1& ziK&Bw3YiwMA%f`lm=$tb00{7iQoZoi2x|Ay-S+qdmFcGZ=785ybwb9d9a}|x*^N@e z(y%!Qt=E>^7cP6c{!!mlP0=0T_W>{Gy7jnklu6E&mO5-ovI=1EG^BYTNvM30d|k&>BR0nY23UJ8H(D`c}y0^07&w zkDkV*Ah4|2;f$$Lv-fo+K(XN-`Ke%4#LN`JiI^5EgD#umf>q?57R1*2GGm%-qpKlX zw01SZHUXbk!r2&pD$yq(p|m1A>rPB~5K^H`=5{??bQ!2^oarFpn#3gZ@|MT-x7{`_ zw!_KN5@xm^;L%?Ay`98B5q<--BOK?!yX9=K4)iW}Y@}}ActrE20a#L4gC<^L%J#>y ziQ~YOoL`VnhCJ{WyUoUr`pWikDN6xdSES^Qz1`im2X%T89E8nx!#`Zw@iQWaLq-4R z&zlK~@#DRMt{|;-W8kKwIIhAiI~_ll4X%i1Q=87-R$-M6{6A&YRmsQ-&kPf_uu*g| zaQ@xNTH+P=W)Hpvk&x%5fr>REcF*aHwY&o_fqp>QB?bO)k43(|jNmR4N0u({E7^bH z>R9)F`Zv(Pkbp?#s@FPhFxHQw0gvs=B}TsL5ARGCV^p z!iQ{%qZN|8+}&?P6e2Qa`#VkF?pya(x&(ZU_-ce*pRcsxGk7fc?~fJ>Z1K#0rr@7#eCU zs6QH9`IwPt>jhb5y;k6b!}66`w|)k$<*$S^9-8mOSe?%1@nBKGlT=9EU7Rnl-qM!D zBkgB#OD$ChLXoJneS0nz@5iS8DK1_%1?Qo`p-P%s4DM%+Sf*^^iJZc%U)G~D&V`9M zZjQtouLUQD(;rV`YZVBEJzZ)}&;@8Xl;yNp^y7*nEC5U8o_p18D-Te39)}HwshJV` z-FJ+aaC(2C|ERnmeX~+z&LQCaeoEN7{pQEQi~#Af)?!pT|B|Up5O(>L9MvYQ@VfUu z6hv`zB|^*Vo`v0pPE7DR2El<99UHx#djVrm0OWJ{i5f=~$h(_qWi_0^ZtGKUMg`5f z+aj~T(L?1>5PTmKXgR01hl%`0+>Qt@51bo$O42W&wh{bNq??QqDgMTht0;l6J4WCj9S4n|3Lc**{AMnK;<(9hN{mO(oX8}kJz&V+bR3ae^N2Y}F8hponH8DMtm?v-$t%K-F!d19geJ$2KV*j{eo8Xhkc;xP#hY}tJ8%R%FCCdU zm-nUS#EH`^zHU(8)6Ht59bif@EP2By#E0cf z)IqDo+p=uF67E!h9;fx>bZiZe<9JF~7J^rRB&J4}wqYyNn;W5a+kGJ`93w5?K`LoV^tdq{fFjI4BZXpNLK*u>aIk};A4&E_r*9b9VmG| znydy^=W!VHCjfAu6w1#=#r~P7nmwOZ8>l-t0AD6ea{kJ>xx5i-e?S2Z-@%u&uuG=1vBzuqmoF=O%VU7(F{(kG+3x(VaCT& z<*vr6t$=itkR(+6B9?qT=~FZd71!dnE`8hzCxNfA31wK~iu;f4%E+U2MQ+{Nl7n1z(0~_Gc8(HhbKO9&bE%K_qR6B!cuWpF*4k< zwkC^5OY@y~p}4RRbtCBNN+u3F#JH8}$)!r%yihxh_AfYxf3F^I1!p|n) zRv5XbXbbkIppmOt_!q(c@3#q$uQ3!s{6E&-vCERKi54x}wrv|-c6HgdZQHhO+qT)I zE_Kwe&3lRbL|z8v0~1cQ$mo`*Y(0-nbSXDsYJ8mJz9UD$5E|D`pd)o`*d zefT@@`+2_5o?d>;_k7lWezl)Cgb>V*Bec`Yhw}QEI^%QTTkv|=+h1Fbz5CKn$U6;K zt`s7%3t$=fQDNz=Vi{A_TaI32%OF`h4(#8;*?+wbw$X>_Ji50UN*nS3MD1{P9_|XC zXC)jy9!g(_e!ei$bJ%bnF!N%>oXdw!T1nMlb{hKOyE$rxs$8`dwRS?N)IpMTFk05G zN2i`X_NHgT=+$VTdu=sZoyd8LQ4%g)iK+x6mhkP3w0#NhS$(jeTnkM$ap?4Wkv|as z6z!f1O9v@eVS-vvZ-&*HUO~0r44<#<3J)X(s?^J^1dwjad3^8m8)ZL;eqKqe8BdLz zah)BOQq9STms|m<*{%nNJo2p{#C)7pZ3nY!KM!Ba2+7*ha@6XEy5hF)|0*TK@TQ|_ zuOB*y=MT+l0CyAthSNe#+;=9{y6-mrf~nE$hvs!xBVNr0tCzVaYG2jpLieWOW%{EV zenhK3-gj=sg_6Xyt{cr>t!^~j=G#C~LNYj6p)D>T4vHt7bLrRISpyHStj@^E4(OWz z`lgCX-9xPBDTV5!p4%&CXWJqCFFmEK^vTu1d3&@lp-iw7@O$hJQZBHZ3JD1zC?)eZ zY}5+ZBysw~=JkHKzs~08jeU=K+4!7^0n}^oVC8wR6PwUGjglz=WUnJL1i9h=$SN)dTU7oH#MILQU3)z%D(h+R-V%mmw%G2* zoKlLPkpl!QWqyNp6PQRPLzRm-r-X7lQd`nZ(RXYZf7iQp^Q=lE)wN}W6+BRH< zhR_tA-^19QRX!^(PTAaK0A)O}5TMm9*rMq{XPlCCCjhe!D?b=-|IO01`h%LT>x16+ zvL?}3+oT$s9Q}F`?VX^>gv_e+_x80~8wm%_ z_@ZkC0N^(}Z(^{yge++FLt0N4HyXGxpTE=%0Rc5FCyY;>G_S)S{W#wp)bFR8_x4247uTFIE9{~E z3t~<`As)P3KU#n5+grzL-=oFWAGrPdn>ojE`KlxM&HJBx3~Xkjm7ZGv$c#hygPXIoGFyVt`c9SwSB zfIuR8o}PMa4H=|-W+Fnm?K)mT;T>?nq$}M4((*P{)4nfTMBFfSejo75)(hh2YZ$r4 z!wccw^zJ8l+nQUE)dhsgTB_knGvYt@{xA4%ho9*6mBaBk+iqm}snMagWW_c!_;X(E z2FGE&neIyzu51~FrG=osw$_WbzegWG(b%rHgYK(XY_y)S{20%+zT3weD+^&i+OLEp z@;}i&J`$KssTufunOT&TMTJHQ@AZoQP2t2d{+zaV$lSZ>sBwOOZeQFEcdL;c&WwVm z=SP;UFdDhrB5H}H+F8pEC3!CYYcSt0VJ$P-eu)3q2dSUR=US1msHV-gZ&ZwoY8^a% z6vl1`q}ttHSk(!kt^ZMubZ77QL3lIP7N)0iSu6aTDQK|JqGXJ?hIfu5L|5SN!nXlkO|PhQ)JxR}ok zeZ^wL$aerS!dcs&UAbuJw1PU(^#CrTXfu}3WPiTm33$A7PP}p^McL!(Brqv6CC)xG z!PtL{?hnbX;{ndo@PrT%DN?YVLi?(Va5Y%HJK5_N{3P%5bz|1^yj4tbLonKREGNY_ z^{}4SBfh~z=6~aorI!_Ru>oQD8LNFAOO;t@|KQ&8Sf=;*jO9lnuEI;$p@<`XjP{BH z9COs>9KM^J99B!$4yM!dLqxNg*M(K!ez43dfU!afif7k2nFfANZwT?&=j7lp76q9i zcKZAt!$>tWazZ{;AeDO5=#q{i_)-#V%yBORuH%aT+j*)YpoS{xSED;o7%m@QP+^dU zjtIm80$PcpPIe5JkpQW*Gw@e{hHbcYeJ_N%t{b&{o-$N)gYtQgGV|i`xbh2Mgfwxv zSaUS9v6_I|_6w7mUG-=UxpU%ajF_-Ef{p;;agIw`$(^Q@%u?qkOHQi^37(?~Y35@P~Oy9F^{|2GtY2Qm_$6Z*)1dTQY+LJ`|5)lgdFiyzHgNUZbXin4P$mzFs zJTk>R_;Wx(ZLfKJfv-1Y70m1nxl?R+>lplY75zwx1XEmC3fnDx0k9$D&hz$#y!QX5 z|NI_Ae?8)_-%k_di?#o{mfG_Bp|z;06>jJS?(ew(_xWg}yra+byG=j2+XNW76aKCn zRR%bOKOHOmcc`ffb8~qUi3)I4p^OzNgp?s1=Hk-mq7{qX^=c6SN3dVpT#g$%|B|q5 zc$DvXjNiP?9&aOQCIWrVut>4jMVyY2xs4|D0nbeQdYwh|i)qi=XEr}~5tVQ)F0BN{ z^!a+>GLHJ)VFDHJ^J28+@qshbyVvgVo}O;F8|}Wotp5uBK9(Qb|9RlfRW}f7U=98K z98I|Y#_u{Bk%|QF|KR0wGL;XE zT28XP8hmu&zW0giAIRuqM4K#{qNFFurzT${}DW(EE57){Q{hj(6GZo9gK~Qk!f!LT8%i&H6?KB zFhxh+CCS@BrmwdgRjWdo5D<`Jv(MFrXF3Sg_fWq9JXhY8=*-cL3AqKqw=l+6>R3){ec06Jwn$w3Im$ z*=$WXnHYenmJoGBZ-(z@C70ox376=06WB9Zm!Y%izm(rOvDDlG#1yOGevTZB6!O_oVBU=9S>A_yloYd?L8Zbv1d3t7O8>NzVVV8Ki7y>*QvC1K5CNLz zNT>)=(l`Oj>M;*>UnF&W)*L!Ch(Uu;9Q9tnbD{a8Ym8aNJuwG#(30|TV$RmEpN5d~ zUw>jd4*ys>Xd5LYaRbMl3b=#^23bZkIr?yz&E$}8W-TmXi<9k-T!zXx>T zG38huFqumSZn?)K$O#(RP-k$uWAwNn5a6IKMG4={h*8Z+gV9e&fVRrv13c92`0np1 z`1)*McmAqIE@i1b!ov-oxsb4Lgl}EK9UhmE3Q{VU{qNBKJB|CuY_hM$; zxC4cwbCjd^{O8_yNXS&{RI0V*_%J7SruALQsu2sun8;WOovp%iN2sqYLTr_{*yJGg zOB4azDgSxWgzN+SoNpow2-`v*pd zRTb{PmM-H(Q)om~Vt8H__)>b`knB44?73bz{fE-!^VoV)%i$|}-~hhfZ*lCJzwOvi z&XOO)OuCQ$JqLa_Y{70~7Ki>i#4-DRKl&>xf=2jIzH~*HaDpbt*U?e3wnq8F_X#Ip z1_y^=c+rRR`5kyvQDL1d!H5o}PqL((oNy=;Fl(jyf=W5h8Fn?Av^)H^?^)dfgeZAL zf_E)aFN?3cT~Tc>Y%PK5^$Bc3*6Db+q{;OXVky066~k}g^aUk^s^ngv4#V+=JY^za z@y&H`V2sQt=!a2)p&7?NLigk-lpPvR-{Z0g|GHd9_IY2vJ5tipi1}Ec1NB7d?~D9F zldX`E9vjpnTT(_wjammY#JJ1Ze@pt7%c{$IC6w6}s$qSB(`Y;o{3Zv=sqf4qug&H7 zY7yHACRaxY-1XJFuD=ojsya(mX(#Nhj#Qib9gKj;ytpXou&H{O4Hl0N@PA<;<-WqG zJkt8-wr;XbbD`E@l3j@x$0m-@DGuG~26&4E3!2?%29JdF?6}ur(`5cA54paRd_XU$ zV~c@KlL=NYvpML)(RVEzw5{|HtISrsb3l%tWh8Vhn=yNoN zf>&WDPPN5Y#fHFP&9}wSO{G>mZ~%%J=4A>~-0%Mj9mpjO3lB$y2EceF)0(?dBi8W- zXiVrxqLIkobNzeZNhC|2wiL>t(yQ9RqL!5!BC|6dF#leNCH(9*Jj@7X2Xs_D`1-vl z{TyGaBa&_ed0F8PV4M<9pNQ1R$a2%*ZgJz^7ecE2RDYf~jnrws>#D7B8`?|oa<_qX ze0PL>uI`h=;uo-F77o3@1_Ye6pVbZ))j5otXHfYVWT_^GUO#0`N?!jVflm>(zbPMr zo)}X$-a}$tUWrLYv{h3A`8TMSCCKg>f?Ow4m%MMnBOp|;&EPbtMyy{oNW<%*HBcGS z7}O_pNT4;QOKYe!Oq(TM;ELiCnj{Ty?3IKrGxc&2 zP72%zjU(>w=HL5o&!KM!wf{Kii&_o2BpG_!628BGEN*Uw$Jq&f0w>usdVLZIro;s$>je8R$Te~V zMl|uE-gp-X5sWMf^to9!Y}7R9@3k8?{yD81rn^>Y$@ug*A$T(;$a)VOK2{4f6RY0A zPuwt(p&Q%XTpg`M+xVPhEr-2WSoHW)Iem>sH;|VMXQtHhb&&Q75w8Q0>><5>1Ysq>P^D6SQ1qkz z9Bh{3aL|sN*Egb|qK0ck!!zHhjcDSrY?y8YDY(;WSxK`44%C2nLi+b>S@3fj_VkKy zh!RkCi*;g0H3-<o6GBz;gp@5UU}PV((i1wz`FfDk04q* z&O*syvF8(T-!oFgYHWxE0LA-pYPOR_Fez_y7BZ^~ukLu#bqqxX!R z|LXAKOob>IC+3`n1lx|XRC1V>!|S!~I&fz1_*DQyTSRT6yPLznm5az*uE2 zKd|W#MI(;9Zla<6d4d(^^<~mgK;sO9oXaW9av?1p8J&-m1bR8Y9=Gd;=!z0}HrY%k zfH_tt&taWIb<8zwI?|gF_2*3&w0m(c(GPaxcE?BTPU}HJ@E;9LqFtu$J zF*vb8XXDuGvxmCly+}EALsxSrWIKghn~7>ZLofRhQ8wPnugmA!MuY--IXlY)H;KEK zTWTT4S+JWg(PQIU7Bz1&^Agtvlf@MZ**mw$Yr{9P%woAYYfvBu?%dGDodmc!8gjlE zXSLB-0s#OH#gGA#hOEt|e(=rC9UTSQ(SDP&Jl@kG4@!!4bixG+@!GtO2aT|B+6{!5 z`3(v6Y^}}LaYqrE$uR9-s8u2Mt<@umctNNYK}E@SiDRK>+Urt~Yh|*Tiw7M&aqx2_ z;nK5R!)SHj4XGZL0k~s;Q(>W{-?3<_4JNxyTu0H&`V<;{vI^M_`2`Lk3LMGZgMUxS z#7bt0>myAxMW-6!z{<%P$66!If9rJ~=C z2n9#tojp6FS{fF9(jzeYuF@+`^c1_E8K?Q*sxcA$;3L)#(b``RKB@bHfODTssc>sCHKkM=Vti9gJbeuj>7vl5$8!JN zW-0(9!bRPuzD!@sUG@nrvu^rJ^#Lplbymhb*5PX3F{a(Js?p!31WWz6C!5QM*o&;O z&CI=onq#>Z4^$jW&r(J^mdh%$%q)gQ>+bBZJk1QR==swA7tZ5tb`s!z_wAS~>+zWR zt)ehO*;c4{@Jth}Dicqw$P33-G;3eNb;|KDo!^X9O}>~`tk2MgcVZzPq{t$(EGFc| zW{y-m{Zu;){y_YJKwaKg48M;96P*6AkUD0T+i97LT?=7&5Q+r@uVyM{!d=OCnOWO+~Pd9+moCu@s$el2Z4CZ`@a|%+8 zS{)%i0*&ljaaspSyxLO`$M-a$aYWOS@iR-r%Be$ zRBP5PPWa6WhwsFTON+%rgmyd1!JW(F2RX?nGJ$65p}UfPO+WH=q=VY88pLZapus-y zYOHC9--`Z~Uf5Yl(i&oGC{HrzH8XQX9^UKlQV|{6+-A&Mr9w+Sb}kc2ixG$Ul>yu* zBLEUq57|)(Sh-T4%Rmt&TW;&RnyfRM)H>dXZ_BUt{~KQ*6?T^cyZIWysoBy@0)nuV z3>jfPH7ccWGv&NX1P>ERb%ePh%NAjS?GSq?$4mYF6a1h>p^4NeLSaIs4ATlqNEWXNP-+h*!b`c&GV? z)(FlPEzr0(NpFXr2Vnq6_kZCoe0qxT-(=C=H|7pNC?^6sbR&b;6>mw|r;0vJ2wnrI zl0=E1^SwN;hx^B)aCT@M0|6zQgmW(T3iJ|FR=G(QZr;*Of`um;VG{(j%;pR zNhnBiBf;PbahnjvA9SB5+vl5L;Io%Nsls;|u=d&W7&$ihqcD*I45Mb-48l{tf?^Lk zW(<)9V;)kUF;=mp0HtGykdsNQzrQK#svYg{sxSML%?t;dfE5M@M>V<|wZbJwHnPJn ziln;eoqTyx0)`X1f*k}tqA8)YRM04$lUzo*)ea%Ip56A!iPXRk2~k1yS{(@#Sr9eC zKTI?BB44Sdb=k+}!*@~AoHZ(p!-`1(yj@;@oEp?soPIAK`jx#jTSobrDBj985WqPfsk++B(VsC}wl zyg+JFH8P=)7tGjyDZMh`>BrD;VPyz#P^ftlF4xRN#wkH7opJ#Rx6=xeR^R9fu;2#z zsmB>7adfV!do!1Drj3mHzaASRQ44L?45MF{QO zxQr+azBtIaST_>mraia)4O~(4IFt%fqVtfF#-g>0!E37K!9gnZEX{~Cc#U`fGRU(u zun8u}F$V+gaN{Tu1A{!eip>uRvHh-kTsNUNII|ijfa#jF&jjl~6A=|BR8fsh-&$HF zlT5L;h$z-&75KA4_y#}G^~v3G77R9Vla<&QbDM0>xJ;Urx9Bp&(oN-P=ORI3>5Ql5 zC5n4Ey{m3*j+hhqpLAs$4*-54QaDTUQJwYosQWwk4!N+hTy3srji^xk zyQK3HF+`QP7~=PdAoU-~5|shFa(o0{xkVQ~N2<~W!w-SSkoSMGlA$YSBC#RBQ#QXga0| zRcJay{@BYv%Gs+7!zuhu-J%?yZWOK{Gkh*43q)OPRMW^I(6Eso8Phc7M_4TK_~~6> zz1?m5;{ASD#=*H%_hsUK3iVpRd@QbCLzK~BGn2y(2e4R+|7HWgDfmYbg&!{UGK-3` z6n0$qgF^GPc&ugzXV=Pv|LQgLES-48+t#W!1_;sulr$;s+=W95=PI32mr#dIQSkJY z76X6NVx`ebX*5*ac#mrM#ygDGnMe~ERJ+I9M?ElFtzjsWU*J2tl+(z@49mYllr`dT zM15@Nnv{GsxV%v)Fpj7~<&+5@>M2OM0kAY!ZVt!wx_v^mx;Lyfo-cq)P|P$DO=baB zX0EL+@f#@nS;hF{Tk8*}dr@C2uC8z!nHdI_iw7WC7EVbVzai(q;+1W zA7ntToT#@pi_xN({TT3=bDxsS?YH87g|4^}8$eL}6R1n>kV90PlNy4o{hP$$TcY6gI*?PXQTJ8b($YLeZ>V{s{I0bf~VK&7AeMrd$i zs35P&Sg}ICtQ5%$SAS=A8@%vd*ck7p96Ik*)bea z5K_fXH;xoier`qAcw-`Xzh&R5q`!>2rv*y&H6B$07Z{2ycg!>)CRRlflIsxp_jYa=s#Z`a{Zs+~dKxPCad#71bLAkXi zd3IT6juSR>fb!WyqYPwkiLr4k5guKSjuMp8+0vmbOBu~^lQIk+gEDY(g=#@O7R@~t z`z<%Dn^MD@42!?gd->CyAz6~Owk>rULuRPVJhx)(uC-_b3~@=3nxjG}5eSR917J`I zNeLl*%*`*q$~w=d7_TB9;9veO4i7%_4;zm1ED?qz*s8*o#ZjA2ZlS7)Dpf|aY}vpk z=PuXE$LBwBHQ6BBo?c|<5Ujb_HeGR`BIv(FTY9B_yr#L^DKX3{rVOa@#iW?%Agc76 z&Yd;yFv%97A*fsOnjAywO+O7z6HQQx(|+iJT9VpQdI$ZDe0;ciN(+L6i1CSXb(9r1 zQdi1DOBQ+m@`c{UNLU=X27%Q+LsYP5r9%@;^mhW7ONc0g3qHtd1>WRE#yJ1ZQ)vVX z=RK+$bk{N4)f)z3?59eT&_v5{&4M4X$Bp(Jr62mpaLh8Lq~1gG4h z1ZlO6)fQVwrCQ)ctifZS7?Qo|t-{-&b$zNRePF;zNsZ*m&`m)-N)(zsYZOx2q7}>4 zsWPlG3ayu3fyiS#O1mQSH2tt|gni92W&z1zsbOYckKAPPx%SG4rFckl!@Lfdu6X)0 zDqNS3L&~_WXvBxj)sr@ z0sj7_!IhYhN|FW=TV@V%tRSy0JQQ22TjT+MB!eQL0-hWp*UU_b0^D18HY2=mip!K< z8KygYDC5i)Z#Zakaqhtli9_sh${A+pK~!WVv$MHegQoSJh3% z$j;7M3Ghd#NSH#D<2%E#SPYUfkyNF=G!G3i=;g~AC=FK$7I9f6+u^2sGQ8iEn59|p z&1};;VqN#s9209AY6=(Q1d?@dM{l>Rv|*F%GTR@E0}@hoH{hkj=m$6oyMx5es6Gr~ z;{yHM#vVc|^l`vuqTuZ86V)xQQIT#47hsnG{a}aX{w-K4Ogi@(F25nzk<3=J6lQ)jFg`STc*L6H_WVG;WnMnOulYR49KGK^%r1BKfpn z!u=`N5|OlD4=C0UL4qS<1#=Xkl^3rWDu?F2(5Y%`)ImcUxJfBVH9h|>_XnU0ppqRt z&k?jZo}Vf)=bfc9bu!^co1y)C)jUXPe~@73`uYHw(H{!|)51N_^9J{v= zyC+yTk*8b4>aZm3Id9Kxzd>o__%`(Nw@t<~94gC+$^aeRO2;6JIs$!BP-Vb|z)YPa zo?)a_=A1@&QiEaG;m8cYGUqVyAyFX&%WTir!vZYv;=|UNKkMIxiD_+588TztR>1kY znTM{tY-U3E{*X-xIVBY>bS<@wJ$p7OR960*uTxR6s;@GxG>ui`$*)!%4MFn7NE!L; zSkx0NvgsQOI>3Ba*ccV4f72AU%(2aU>04%KHcp06nq^qoyXht)C6DnUX_p98rkQ2d zo8AiafrWEA=WzOafXujjR$)k7NQSb;5b?YNYh@)Eygrlh$=>7b zU`Uc|EwM%iQJ7%y8dTN8Y=ft=;K^w2&z;qjNRc!|NC(^s&1fi+RSQxLl^(GsyH6of9gEO3OXLQV z4C}u@!y+|c?l4#ba7``~Inzf;v|kXNaXochq?(;g!Y%45$NhR5wK@3$uGf(Ou6bck z@S76CNpA%wuWXA-J?*OfI0mt%+soTG=G=cSqUT-zo9|*{Lz5g~6I2F=9R8YGj5;XA z$y*!)`nLJ7O^-CBg-^x97&R8fKgR?qPdY02g!3AhB%#STyl`vWPM=yUdxxyX>UJ4Y z0w6;V>#>n!m8YChqbMgd)-d5|DnEKQmQG&=1(9ht&y0uy^<7swKF26C87e^H61++w z22~rntaPYp@kN$hHhy0fK75<99;<6FL)qqs6Zo7(m;s`hhV*jI?02t^{F>(yo=LzN3$pmDXruCopXcBoowrsq#(0c z!@a$~Cj7Op5jIS-&+E1M{4XK3rCa2220$+jAaFs|0Hqij@yX0z_yM^~VU)xoH$%3) zitsZSfM;72Fxxj+mFwX_aD4zJ74`skVsk)#ru60&VsRETYU=h<@Ite~ObY+?^E?O2 zR7G=f@FOcn(TlkxMfwZqOcxBSWZhfWTho2JwXV0u)m2waML`FaMz=)fhW;@jBw^=* zEOXMt>ut`HHTYjS13At9*O@Uz{npEI@59_ro_1&HMZ2~8zz<%e&io3G`V#NihEK0c z@t=gVIco|cVg_k1w|+1mfjKzec2CAto6Y#jVy}Zm?eTmS8b<+=5mh3jPqT(wr!R(i zpAp?So{~TW4;MQ3FGftMW+G>@nO#kv&iFESCCKLmr>G$A^P@1WcLY$eIz^}$=@K~> zHNW{xKl%=;O~~9Uc#B%v^f195GnUBeBOMfV!l_G_{bbMT($zgqJ-0-*niEEPaK4OD z^XN69YTIG+pC0hU!oWQG6G~wnaF9W6NN;isJv)UXg42M$WDlp5Y1dG?0lcp!AZHP} zxG2w@)1f8^EIN6xFtK4yA~dH`EffQUBtL9*3F_{j^d)!25j%G50{hCq_~j^CikN}Z z!QN7yGw0BQeV$$VI!;}$#6RR0>1iN!2Y8U=*AFc9&I`KP>$$OVEwdY z$fWh7b6Ne){0y+n5>48ODgVAawHL${>y(ZpKycw&DH23iF$J7LRo14Byit-M`OrB} zRTppidCq#|!*elk=_vSZ#t<}Sa}}n1nTRM+lf@B%5Dy(0xHZ4u_!39UVb`QVe{K0a zjYh=6lz+TVV-!0t-hPvgW;C0CetQjL={Vnk|FF!0NtW6aXk(hRqVe7A@NoeN?#y%u?NsTt(PY9N5rv;Qa!Z|&_fVxn~*f&Y4faiv$- z+DPj=)M#kxEUhGLm9te+W_5fNJk zgBeQ_I>wSUq>M#mK%FWI6uZL_4zDHb;y-@2uch)b=8xXqWd^X5-^*w*NQIfywl z*O_Xz6Zzo9S~G85pGgYac4(-s7@u6^d*uyaP*M`gNX#u)boCS%2Ka(qb6iln+9==j*>{EGo5=;z|tjO?G;d z_)&wD-SAC7Nc7@C%NPrKn+Z5cO!9#jdQS<*iF%^ZF|?)W@PFN)yM{eL)$)L@+HS|^ zLc%0w;>5#3o)FQ}3q{$@w`F9I-p$Fgx?M63Ov!mOu~CBicU#hRiG_MRbNT;o@xc07 zE?v2e+LlSg7SOo61#|Q2#QC86M^WFpqU(A^6z;*`_!!tECVKj{>9&g|MWnn=fQ}963dKNCDAf;I7&WhHMQ>lu9=1*RwrNU&nP+Fj;aQTI2>w_hdoIReyzm_JGDDhg8lQ6h^YPr$fgJe>a$Pdc(YFZ zn%7>31TPz?UrDb`r%t&n64Td))Z3&6cYS=8psTFmGZ<3e+Z8 z{7j(kdIETfg}`~BbN(S6Fkm2qH+XIbc%4q$%n}29l>jYseFnYbK+YN^(HWx|_|6Xb zjQ$5oi3tu2+inq>v14OT91#qMIHOKU9y&6Gs_4{XpyxFrJ~l}di?HZ;Pc?k}dB&O* zscl=hT=7cYKj8c*4vPb_l^5`cif;-VxngL{1>3~Y*)%Fp8e*$W^-n50dZ zAb_NaJJ%N^w}aK4RCpq^}6sunLYh|LTl+fgBTqFn0NYt4ML0#~FMJ88jIT z+ZMA}!QB%H^Q_yT=(jW+{KiU34w_g1n%5d6gfCwPFCK+}^!2kZ{+xB)Qull-g!gXG zZ^`>spglZXLrdC*or9Ox)^UYE-TClhO|zd3UO6scw+nX24tpv5$hrMGJnS`p;I;$5 zMqm2wxqiJ~!g)~-n!oq`d!74gf1@{!SlsXD;r3mYFkPTX%Y3nco0x}2Zu`NVgTQO! z#*5DwtW+7Ib1Z+eHVB?Q3KH&e=Txx*gVhROvC5N>hKv~MU9Jy|3Foqd!(-2r`=T?S z1khy;Dc{wG>E0G{d+vqLf$i|oN%)v3P@}$^pf`3r%Mi$;*9G5*Nwpb+oFAr(L zg8hc=H9cXo;kQA^gLi4XeY?9Orgg*Y$jcqoatNlw?FR(HX7w^>9yAS|Jxl-2LcW3Y zHlq)UnY$(wMw>iMYz+U3tmDRdUTw7p6DicrC~S5i=K4cq+OH$;tf1anFrylM}W+S1^ei4iStr$QTmoVBQI`VR8D4FFKV?L zUg)C9_~nVcu$529Nsu3&wFIi0yRw#EjA>!;*k(tPCGLGqX@ps_jFx1N*X`x-B1sPkH8Cdh?m>fsH0}1 z{hcgd`>%Lz=X~J^^QX?KPxgtUT#+}1Ca4#(WjgbcJDh4k!k%Tu{0$Cni-Q|&wB%UUmFO~UNCu(qEaFG;-C?Sd*hA?4 zNwE?YmzK*eF*Y|v9RUX}@3yeah3^-eNq~z0il@9p<#Xwi6Hos}Z`|DMNy`#DK3&Za zZN9CLjtbSz$GGe5%a>=pv&Ru!5Bm!gL*KjfNY!LSurX7^4S2;aH?IDb!`|0W@0mh){?=)|BqE`V2MKXJE?hN59Dl_eMj#e4dX4kuEn*bW8{Tf_fy5JdIfzGx>Q4 zzHgtzbvcc6St&CdGi=HxUT|%3c_x@oZx~zB4B*D|Lk|yVGncwkX=z_9c+bE~KJhVO zZj>hz+)r{i!#IdPbi@M z5VPjY>u}j9s$P<5*j)+kH-2=}drpE?5{6Pt@u&F&pPS5oO#n+0?VCWK+g2<=BJkWU z9uvgOsF4J-&x^hEMBgvv3@#>M>3ILUcl3HWDgSP+2A++vE#UM`#?BgBjh7?1OE-jJ zLA&k+U%IO;g%;r7y*)tbg5PLmk1;QJo=v$Ltw$N(TeuGLJ+SfehK}SvVw;_O%I~u# z)gS5KkhD`qj^p?ML)_c;cG9AWQ3_~A+}O>dH-)-c;>d2`+Q< z*1siens{_Bd2{l`F2UPjCkd;WvlxHX$&aoddGOr`D~qUFFovC!92fu9BolncJ9-TJ z*wnxb%W>+*0&{ChM#Kdb3t-y!-9x0=@&p)*$zK#$%}w~5Hp`P?0MS9)#z|xZ^LXUR+nbtn8}XI)UOluBR@LC!zn3s z3tp-kZ6}-^Fg$|d`xr<)&C7{MVdL>PVbYpLG-UlkFxMtN_W1;d;SpKrHz(0CKj<3(V1@wxAUVpby2+aT}F<^}3$#lokRoaBpbE z>mSL;T%(`4NZcv*+x55(RFxFNeWLWjpQOa(#X09y^ZCcZd}cm9=-+qm)n)OsVrN=h zz}~2dowXDk4aoI8zWM3&+wq^$3`QVZ_LnuC4PvE4IAgJ=1%9Atqja!VX$f4zISk&* zHy5lu?AOl`jh}g0nameq4l!C19^@hZM%^CugmUF{!|&GY24_6_MZgunwP=R-^1cl9 z|DbJo+$n@?o;P~z5ybQv@=dMQ3D>P6N&##6bQ&;6AzqD^|9l16R1w@&Vn-}YEGRU= zvhL&rdQPwXlT3x4+)ggJ5G1r*I9F6si~2)ViiX!9csNR%b-A{l-k8C1sr+XD?B5d` znt}No|H(<{-O+@ad1k4Uoy0n7N;CLzLnqR2eKYtc(KjJ6OZ={s;Nqk&{KuyS>#ela z!YPb&%nb=pp~mA^ryHubLR+3upHgiEr003#%Jgr>DAnTidX)`~F9pR$e@C~Q`9`*C zBMvh15j;2D+|E75HxR#2;o!tZ$jeEnB;bUtO51FE!tsa2|7vLl)~DAANE#x2HCnZ? zp|uir62}NK_;r1Z|LZRhoV1ehW>r1&K{4a)c9(8Lg%Og`dia{2FQl~_!C+$&1=-P~ znCwEmipv9Pjv7Ze;c;J()$1EgVk#Q3SRtON7kp6v!@l3^b(gf>IuINLD0J~thmeNk z>ud7Q^V5@@dgFeS-k2W&mx4_=>+I~GJVYS!y_$9I+#7K;(d_xG#*B4d=3G;QC2fB*7+ z-Z;Tfh92{T_AQUVl=8vcc;0YuvheK{z3ZK!` z5!MndZad#2m|c$v5y(o4x9ytIk0O54J7U0?b|a1Qpn7eXlA)0C26D)u?C|b&MnG_W zf3PdM8CENL#nV|>d7eK+I|A(JrDn5&xvQ{t8>D_uKhk8f{kZS)yTOY0vZLflNM|XY zjKDjV9CGn(d;f00F*>2)_m0}>wEKP}=8niz>C0ZGgk6~B_Q$J%DYMwWD&;QopxwBD zj-8H&EP~hl%<$W(8PADon{xM|$gr>JyYhH{9p_$RdcalOK!C0^_Y_m1VMCN^eCyr~g!15Up0%tzYo#`^`ez?%!fcYVK@?{m*Q zUgzD3`R_kSB-5uMd}Y|<@cDzbNJpWrRxlOiS*u_X_6(#*Yu>$TzKtN-+9m!G#`6c< zrqy~}OwXQHFb;X&_=1_=OnSWUZ4&eLV6h(CK9qQUD{ihw83^8|9(u-#lY&F$D*c&4 zCV-{-;C#02@%S;*-`Lqf8Mizl4r`=L?lp zYigW@?HgGgGvNC??S-mXkNK@e;9yT?VugO+?h}frj&8Js??0TIUSjwL`7?&BPGQ&W z1s4|Ei5Ac2C+Ib1#V7IUmXp@eb{11%XH%?G3X@3er%?4r33)7Xf9eV3bDP#}$CfT2 zW=*5#qp7h0>E7Mn-(L!@*CTX;Hg9%zb{u{&n}Tp0Ha~p%I5c*Nwn03~NT#Y4>d$MD zw^0e@Q9dP&HCdj=nJK`0!RWf1ac}mgPPlqBylx)RN?#BR0$fN88>-bGhC-JU82Yij z{x`0hoq%Xj#V^Hc2)va|11s*ZPk!%2_4P;~8Fmg^Zm{IPk;Y(QAV8@Ik&G3|1x<)gs?aZIgOTC6HxYt28&lJs2PMk@rl+52FtdL z9VTL_W@tla_x${VoG~-?xyE+B(enD;Nwl0#4kpL-2Y0&E&@hjSXA8D+JT=Jq|pf**YZV3Rl^?@f<-#ES+uPe4 z|0-DHY}&n#qK~dQ#I(Tw_@%eDxJXRTlZ)Uw3!o8#B4hZZ8IdR-G(ql;EW5`@oDACE zjR@`xT{m%5Oi)%&B7Q)EB_bZhv=Ms2VaM)qZzC5Z7B`(jS+&BfUpyOb4mwT6TqLyL z03lp42Ib2}ifD5bbu{94l^Ij5DHfppgKgCZ3Ih3>^pj5@jE{EgG=YM-2)#3gBx%yd zLoo_g&`>zrk#s68A}V&-^~>PRt>9|G{fN?DSlp0AZbly*wEMwB6Ak=}jS=roUhWDm zmKysgA}bk-C?c8A|G403_`26jd`sDHV*}>ZtM_h#Y_fLw6=f=9&P89(>@}LYYyy1jx+Sbp-CiVTFWz2(6atxVM9bO4mld}l22*sT&aDmD96W{s-&qa?*9?WbkA^`X~{HcgR@M%CE4D<>{D`tOy)(wB8= zoo;vtZ2hp@vRAMkt|g(++CST~ozewRC5SPjtyIpvQuTb1xCbN*IzgE1+&MGmB-CSb zt8sAk?bu2Y<%Au6=(Z*wJQ{{@iXucMRO5v96R4IZBDkB4*d2UYJhdp%GhHkDHBvHb ztn1fhGAct-T)2Szldp%N=hI@S!G`I&(O$%@aPR9bY!MbCKHP0Whqna&cS6B5T&jES zOZg`LDIi?l*Doew!vWDPw^^wAdj9%`ja-XjcUsZm+Mw;HSf607hr&e*vPOV273>GM zUU`{hY%rYvd)=Y~bzc1=_6WAudtLK{RKO6qzPs82>z@#Eqd%tY2l@6tkaxD(jaJJ- z3Ie$tiMt2CEhS)RRK86heFWNnJ0>EoN^`76#>m)>sun9xWGer$U0CjNa{<)gEBQAP z2&RX+9PYk%^TeczY7W=IZUhJM~@H# zg93kqQ+E7w-d$ijQ`Fe;35Emrmx)PHGQ{`P#@eX|FJ_v<+qP8N^Ip&ACp#6nGTZD# zW;RZcK_uTdCqgp*X4t2s%-o`#e>NB^5eXJd@lPjxc%F?7CG{03(rR;Ir`aYcDpFj?)n1Q z1D>Ty-H9Y_>k${ZZQT-qPbcsF7j1ngoJ7vJ#Koc)_(h9bD)aH1{BT{83cZb%znI(x zrA&q#I!sv1?0EwJm0XAqK>HY@H(&9IDZ?G@!(+UFy%7%zf>C4hBc))4AY;lGn|neJ z!(qoRj{X?uDj|h4eeoQkvG9EE`=QlxFflW`FD3e1W)O)7OxW^5mw!UcIhy=49yU;V z7W0f-tFOoZoDESn5$7Ny4Ty<}5w?iWRYf6PqwXQyWD-~S%BRmQ$mCm8!xOzuAR#*h z2sCZEpNPZ_IZp9F?!}$C<*c{6-S6#NuchHn(N@4mnJaU}eXX2RXG3!Gzcb`AW z`fSIRUUka$Ygg#~mZR|R2eA()R&}GGcq18!*}Wh{5j1Sty8x~Xd`i<0Vx+H7Nn3d|AZ6UI4nj;&4{Jp z&5R`2dwl3uiQ#=w6mz>d0yrnxBn)~6&iUNpyhP8`0WUQ>wYNL6j(=0p}(#!|tH{n`a30oi1$&rPxE3P95dCws1i53Fk zUzG%Da1$aDPLqxa4-&B-V*DeojhhS)E=0I)w2AoFIX=LIeWvxMWq4u9~ksFPcWgyfJ zgjT;h7dk2T9=)KTu;;=uov`aJPa#7aG0jRCQmDL+N5u7n?28kPr$0;Zq zJWYw|_`od62ESxjhvjpk8y#TPYPW)0_tK02;A1L>i}WfHT^B6|&)q(wj1{6vawtZi z_*B(4qBDJVxsr@K90a;j(&9>7aLzaESGQUct zhnrUPLK|D92EjDyEw9ur`RfQrs6NXEReAjKC=(vSGKSqQ$7v;#wnL0v&9vmG?CSDb zxBs!t@|<;OzI7D!MJ7tnj%Qp^l@_a9i2Q*y{mQ(gvTcyQFdY|)#!ZwLxV>z!Rrtl5 zI`f_UaRh?5Sx?aYSu+&#OM*xR^+#zpN~YVTcn1lDrJ>m(b`X<=fg)H1Iuulth=@M| z>`Co`EHtD6nC{&bQ^<{~YpCuDu$BeU_K#sREnP6O0>>8ELjKEZB?_sTLL!BRguf z02R+@=b&GQcMBF5|A9ezg!U8Z^!-zm?M!=iep&i17!SGL@mAe&&mW)u5&pDjvqS{n zKRw-{++)RVKEGKLvBw`NI1Fs}MF`yu^{6|8YD8iO-M7&X&ASTXG)BQGS~#BD)?%Sq z6ay*JKwqfTR=8u1Vu{niQeMe|OXnDYCl}6;4<&xffaU7t}fFB|S?Zm4yy21AB z=d)Ey!X^3U5~VB}qa$G8LVJ(uo18t($ZDA2oB{daFr>Lx_4N=i!`>z`!SRSGHbLIX z&a$v{2;}`^Pamp>wU*hj*HIKqP&9;)F6{~paYQdP-m`1@L38CIU@bJu2{AyVex||m zXkob|jqHc?+GOD86pYZYwv2Hq&mcAoe0^Jfr)DL#-ZNxA5ezsJR`u3piG93Q^pSbk z5d$CWorti->N>CXH*R0kKPRGiJ&!ygLKanGpHCj?!aEtGnC+h<*iFc0Wv~rbVbhN1q0KcBjc- z$c+Pzx})f7)T6I1wr?Txi(MFx={BPrIrYNy?oQqu$bR;%Im-ac%qsn=)tI%N%fZhT zxPe7vO_c#+j9(bS7{5ZJk~B^=m^6{P`X>rT>zF(R|KO^-IRnP$O5Je@N04bG>xhYo z=iRr%77XS95`*4+-&47rQ?%3DJlX;`C7>xD_FoD*>@|e{MKao(o%x?ovf%O1ZMO7( zM#ba^uJyM;^jKPr%RkR3@Kg#!^hjo*{Z5~9D{Do&;ER?44qLp3C`o64_x^s z_p9vPjK@kyJ&tA;nCzqwXKInNX6m@V6@$Lu4K?@V@_>1g9&U2Sqqk*qGrFMWE>OiU zOE{ALKq{UiBYpP)Qied|us&qk4vv=I@uImDj~|}ddXy2iLI(b1fppOV07A*4`PB@v zX1YnIVi<(Xjua-z0+25d6|JnAB(YRU=fpf&v%TZgD7wz=lFJWH%&xn7j8eFNX_q3J zF&NJJJ)-B4q(I{Ov#+*~I^yC9wAkbL^JvyR1L?3fb#M-r5O@lPU~|d>Le$;>HqSYM zE8iM5*)6g|9^P5*etI5$>(e6gv_)*is+O6Y_&M@{d)E60Mb5I8{xvJn+m?wV&)>i;9XU_ET5WdEP_ zLMh^W~SbEO}QZ;%M+=k7_R zNPG|DPj)M?*?wmm8Q8dgSJ^qT!0-Su(mu_Ls1arTqHDU+!eVGeC@iLH*sUM+kRx6>K zgiz0n421|Q?vN)K`1510_U_&Hn4(-NjWTn$1X}Prq5m*u09h5EZ9Z8M z+Mp9Y^$hi-2<@})_ur6(gbAtSVyN_)ebKoJEtCr(zZg}W$;^y(II{Drc~>Cce@JuY zaXRv3$nFcScvD;CxF{;~ujL@?#e}Xm43B^c%*aX=a^RWN?ZUM8?N-PTO^>pX#w~7( z07ATC>F*T487%o)s!T?vhRQ^42&Gt~^iaQBIz5JEjVx<;QDNXi7x@GMddu?`o166q-&(kQ<9iQTIZ3hVH@^nnAA|%_cS*<{DJ}@o_I+I9RkZb(0VzSo}DSsm}L=*l4ZkJ6xpDp5;IQj+-&QdL))m|mSz zi!z~X>K|LbNba;XxZfG3rOsl^7TKE%3lyS0C9R8Q6vi!FajLK3usBnbY%Wcb$%^=% z8;8}QV{&o+i-IKJL@nCwNyWlS=JnLN5QePgyk|M@TNPqD`Wd2kYyFMF51T*|f=ER} zjb3b38oNy&Rs$WsSXWvx0@h(j3>{^RC9%`OV&OfFpK=b|RTk7^Lj}Jx=T`SbP*hj>W* zq;TtJBsA0|3@Z<~PKjETCICG3#=3vX&krLAh3_U}B6hqh^L7WE7K!&vV3VKXppm8W zxIw=4rI z;49O0$xuHQI!zvq{j)p3_ zc({!VhCwkT^5uI`alHFYn)vaK$g!02ZsTsa?iYD@{NgAIrQl&{3L})68wm@7LDRyq ztIl1+<1rpzS4*dyhBPW7=qh0QK8`vckj3LCj%^nB+};dsVB9x1K_EBmtHqBF=XJhM zv>Ly$BfHlCygv)(>~E2ZC?!Oz zjZnDKC>x(w;=J%h*}bDM4&``ZL#dn8aX22a4w_ll!jF)|cnvP7b&&TwW9Ckv_>k6X zQom=Q3~$D`7Vv8QWVo{DboO~AF2dEQh{HhP=QLI(&}!kp5&AeG^qoTu_&}AWJ5bb{ zKbYg?N5%w5l^nFozMB;f6Ejob5eMJ^cNmax_5{u|in5CjZ5Gqg(A|2>62B0eu-ZwE zD<4V=Jg)3!p{O&!_@p;QISlj1LLit(-rPc)VZN zwJq)x#Uu6czaI8ZsAwifIGs-nCgw26o<}JtX@DSM%ESK6>p$)t8AAs5*p|-zU~IFl zAL^M6YI!H){eHv)R%A}9wH7+F{f=qo*9Esztp)ST#ji*LGOy5qeEY?URU9{y`R1RZ z(q*)0Tq9RmkNWKcaP2a?7>c^ItuM;9Tg8Op0OKPgdChR>&&jv{VbDkn`0Jo&TMNjC zk~eUs$K%hny-_ngVMiA&h0`d}FL@l^JP)v|M)R}s#>4Y7=7!?POjYiD=Lq(_9l3U* z)8TkP27LfbvyZ4psgtw_McI{?3vDQSbK4Um9NCSu;5s2EZ6hiN$Z*N?;BC4?fl%L3 zZL3FhW5$Zu`2U12?qK%&MXZ7v=sybUAl;+EhNl_Vvi*lJDy^!&TaA4y3XKijZU|DA zBk${$!~1Q{uO8^l4U$gItL#5t;+>Pm-kNe2=xg{~I%Urz3``baYDVA7R@WQYEUvuW zdZWKp#0`H@LO*}9@6B1(&|wik#UJ6=A2Qz=ZeY(;TBH-r)uUD#>sU_JcFj(I-V*R` zb)bbAKSxGI4y49>8u*Qg*e@>HFvrLX9#N01!e7xdtQ$7|SM$j@QIXe?7~wzuuQe z>PivAE>>kazm`!<6tDseW}fsj^#2(hoA2Rve_OCW#rFYbA_tBz&4{Ul=#Q-05Q08) z5Bpyh{>2i`3Pq0B;3UywU$o#N<~McylDTn#jywaxpAw@v0OsAzb5HMfgW8#nXz6x9 z1S!P+cn9IIehn`4A6bI#mtD{o_)cuZZ6|7xtC8FDO4eOLfW*hc5v9S_3xR-ED>%9J z&WLs8xx?oG<8C=Gyp99F(Dw0JrB2O?(;c<7kaCpRW~4pK%MiupDE7`dp%x_IAIr)<88H8QF)@XuS-U z>FvJbZZ)@7I58K*%k5G$s0Mwf`iOrNC8!);=0UY+Tma|cJ0tx*EYaMib7N!BInpy^?xw2Nm`W7-QRBA#GwioV3i$BT ziU!PRB#q5!uKli&ix#T}yqur=7xe<&bG%^K$I|v+)%E&vM@IH@XZV6X+11)HV%vQT z@+OZ-8GVC6I0Zv7344&&?Z0FGTFV-^>pP^)R@@8<3hIT#VcMs8&*`ei^!o|g5h>U~hKa+Mf zJV98UsMX{3%ukv{;r+h%;PDmH!fgKKJ^6L-tlIqdED&xLa3z9QP5gbZ_89H>m-?ZJ z;KG@fk9O07_IWp=htFSqJ2PfRFeg?r#~VlXY1yMWhZ`XYBh2kuf}dN()qqQ70p-s6 zi9yhOaOo^Bt-ck*Dg}?bTp5bU*y!Lbcb4IAgyAU$HrbLpTa&8?M-~B$Jl62e2h_IL zF~{O{R-C%NH~vN3qDc9DO1pBI&mRxo@nT+Y%oh0`asd z@y-m>BN4l0-B<#hhy{Vh@pzcg=l7mil~09r+;F52@FE@tL=e$gk&9A-&lbWipE&wW zi}Jw>E?6?a{f77@3LR);318utKFFaV+Dc0Ov68WkdZf%I78HR=oh9?;xAU$zHqH?$R7_wn0F@=lEky6sIRgrVtw4(&tnN{4 zXwNk|p`Si0;@LIzl#3E$dUAhkA!O5<0DV1o%)^zwndT*2KHUvlx{v6EUb~&JV}ESK{QBB#92_e5$mV zsLZZhY=$yXBc|JaYLDw9%sG9%0Q<8eNBjG2gEyBk>Bhia+s@OvjomsP17^2=DcSK` zYlo?1VgDtGgc9s*3R0dJ7M-_VQLIFK8AhngNyYGU>2C*SIG=VdLI$7Pp2pbV<%318 z2Sf4!7p*^VI?r`pC@x*eH^pA#P0Ex)XO08(xLYLpDJ}HHa4_XjP`ZrY5MkvFjp} zP2|iNhMX&f2KQ%(^6AO(>crP<=dnCMYA}rjAx~_FNL#+ol}U@q>6^mWm70k{H$12O zHTe>_JNWNMw4ob1V08**m(Bv`<3%R@(?bTNPqY6d|2si=?rv&9BEAQ1@VIM;s`de9 z_ZUeb&E zi}%xXaz{DfcwVRT9Wb$~yAhGm+2rx0&#JUJ9}8;TJ#1}1EsncZkdo83C1?3}Q+Kh* zdBJb~Wdl%X0s|fhN)=?p{EKKdm^@c;Wb){Jc5T_8{lR>9>Q#HUq0#u_<9l}ou{$F1 zN$paP8S=)r9zzXfxk9w^_8zcSR{ZH+qZO0=TWhSR3H@Y$r`9hLA__qpqcx5`HTLu8 zjdy*lFL0VneX0+js8-O_jix{4fg9zXGRrQL<{^>Ax<7t&vH2GB?%ULe%MYyMI8~72 z$7C22e&euZzei+4m#1K>wJ;Z zaIy!CMhF?z_F)Mcl1RXx#x0hK0ZLC<*eLKsCG?{sP^rTH=7c~IY;FnusD^hfv$hEs z1ID*A;jifBEo$sTdH7QG(DYqoX9Cu@1LqTI(5}{k`Qhp7CR&I84#rMjb+KQjU%H`e zw&p;&FBzO%>z372i*M)Sm)X0k0B+*mGHBR1n)+m>hZPWhVJ89y<$~%QKXFtS7xQg) zcn&i^fq_&Elg7Bi#Uj8Kd{HZxtOLVAaCiRC%ocnxTVqrJ4{6qyFBCkCsFfLFB$OzV z1K8T`pd;2_)*lgjUs9vfLR>VrM-3^rH(QvX1t0ZSQHpc>Qj*VQNMK>)q=oJz`<5Gn z5tzo4Oek@KTqr^)Oel2aa|_ZQHX+SJC4iy$GLFvsaX3J_8~eOy>s?ldm-a%vkA~A~ z@Pu6_3Mpg}sgm#oHMO12NEg^w|({+S{W-VDRRBUO*JQ`wF32aRCb1_z=pV{;}~NyqX%A|1O6@cZ?x~( z7?$88jt&nAEE=^Ca*ltcT{rwMDo?xB9t3B1?Yp#|onTt~HjJ*NSB+L1Ts5B#yzD#U zyHeVc{bAX#0;30!MKOf_x;^euYyt2UU1kL6J)Gw@YK=OVMT5@@`@2_jeUcXTADo-s z%;#~oL!2F%jw?aW`%hlqfgL6ALpjB;O46^)lBJ*t{9*gY8`p!jX>cR?rq)!ROGXd( zzvE4HBYL)c7_Yk@aEc_YPT}_^p*R^#d*nHm3`>o}_Amt8SnxZ%>8;?aS3Br24Mpwy zeV%s$E`}c^y<2QtV0oN_xZMX+1`FM_vdC&6BXRRfsgh}5zcIc4#U``oS15-8iY&xJ zA%Fv_1OjfT&}&xSJYEz6Usljr{38b?<)Ha6vBS5_14=z7ymZ^~kYHjYq}3aD0xmS; zuRESo8p*aVDF4i+`V(*53Q|~RH4p5E50e!pUmYl_b7Qi_~vn#zy|tJ1Npa#9)W7DzWQC{wOjlA{m_V zc^VDA2Da*o%gAA$w>iOGJ1`6=bO*+RTpND}1tir3w>#2yznPI9tu9JU$Vt>u{QPC# zji?NNaR+kszE^s|^tRk%Eat|BCOkKKN&u-pMw2WxJ}IW-@gt&rH*45b`0m=i*j(@- z=}$a-Cp4$xxQNQmT&ukkj=;M?s!5P+%ZaRVI1dZ{LD*C1#9^2eO4eW&U74zyn3)UZ zyOIbha@<8tCd|NgjLW9vi`5;@q6NH?WnjW4KXQOCrMZF6q17N61u287=#4C#gj{;y z9w^1$yyF@mXA}u9DKPnzNbDIW;DS+MfTTUOmIIB1hZR&AMCJuToQRa4KxKxQklmDl zl?pX4Kn_1Vc~TWjc1SE)K?jBfg)|RFegOY@GddMtf7U)HVrE5cZ}^>1=*AA`MRJ&w zoEQ!Hujp+WZWz#+k)-)gV7Q;Xjmg!JI$HakdcL4+RJR#`D^z;4GqBaYiYW6Z{vVO+ z?Qf5}9W;CaK5W`;FYKrmf{+xOC{_Wr@kR%vlR!eFeO9W6;m0eE#%7KI4fT*V!aJWY zoFNQ&enwO+Yn-{(9a}+Xwd(-djE|@_XiDg;sdEN|F9L>yBvI)AIVBe!yS8U!x$Df} z=~S6WlL2H5B&qioA_^LejA@R=^s}%xFd_zK2qzx3{N%zQSt>Y+#wILed<^Onm*sbFFG!C;-B;NFg(i!{$t|U^GM{eHhQtYX;%)EVz8l2B+Uo7mu6C;T))4T zMGn&z4-)e&Tkrb`=H*DYjp9-Fq|%1m^_BU%wUWY0I7s#WIN(S;DW)}Nb1*={U1?dk zW;@IWAs|G>XY~A=UY*3Zu(AVR?1Oqp`&D!)<{@@_mlZK!7SP-Voo$Be4LX2~jZGI( zn*x`g;FQdfhS+c1L9F2tu1^vj%8y+9@$m3NRCHGo!qmb#U}yx97|k$<1(IIg1s((PW8eS@UvBuOu^_*O zKy6Zp) z>3z{V%sPVu?N$yRc_s3y9ni0RPklC@zEl7!48WOpo8d~>oZe}5JxSql)lT0NES@LW z&9{9sZ?^B;)$hGZjSV76GZ9WQljo6D#{uLEpSWp%=L%W`&fdL}WAG#R)dcmJ{R}l} zs?SB3mwA#SL2n%u`skS@q5`xEN7tmxTUrE=$1b(bR=L9m5(B^0)ugJ~%Uc_Aif zBO+CkQ2`bKj__%0l1IVEegyj9*aKlURVzXFUta6#2etWlIbMP(xe_>2A)wyZ+So{F zB)X#hwUjj_azASigwN1nBBf#pFF^JEcqsL?rHG)*z1*=s6~1h*e}ZCve0(ap6`Vkq zuArA%@4h*^tK;dWxeqx4dBn*dmE$uHA0FagB%{VbUnaD6-cs=&NTY7V*l|BWOoF^O z9XlvC?h3;L7T^j#TXS-4ClwDUo1(a&d3x4oZdzj|QmkYY53tFGAZ6Is$^im%?kK;z zF`A`g{QONGSVCaFwx2`i@5wtJ3N2E0;K$6!gNm$6j6XkB2}WW%7VAl4uKxvIfFWtN&RMH-$dk!s)#==X?r8aS`T1Z-$ zz$I6|uBun|k1Bs6i#(yp{B3&MpQ5sW59(3Gg=r=X zlERq>oo-j5Tg!?YY^8b$3r^4-om_^>m*P=7Tddh!nc5)8hn9OnN>>&E7qoAdL6Drj?t>p_{8_7l3HuABIm$Z+$*59 z6R&E6s%%j;+*}$^{^vu9pBdn|ZQP<}bl!wIo6&>*D$fB!aacyP8TEmo4|kgBw*Cls zQ()zX9BShX2g9`>fu@U-bGp^Qr-!F1w-pul;gzvkcW5m_-at@4;6+&m<;&7nWva>M z`k@|sMcXup4)ukp=ePtG`~|VUzWfM8%}aX$xE@^n4H+1gVr_{P5|%br3|IRR_gf?z zO>RdyL?RHV;^l3BOw(JeFrDI(jm37ZTV2ATHo}J z<~SzbJ&CJWajwSjaD5iUXQxD`zn~R)LKG%IitPqw*)*o&2dUYMd6?_?U4@P3X1~gff(R6`{M6zcozgSmqOv<*-Q^W#pL}SXd1$ z?IkLwql>x^93BVrRT$xSdX>nEi;0O@q;M|DDVV{#Jc~#prZ@OS0xEb=6W?R|4;_Q% z_?vk9sei_VD~d@-u<(-#G1NQ>42z{G+fWQv8hhc!#Bbya%Bh7a%!0x9nq%WZnE2Z~p- z)oGfRO$~icOQMiQmQ-`XpXBzQdwC7O;wpVfbT%&v9`y+6)&S5Bg=}Acx0PG?thdCE z=IwXeEoZa{q(o$bo*!!t+Gh!Se7h;Ob7y?Ka|v|@$3`Ch`~j;&eWHvdy#J0Z9@W*3 z{Cm?GV3cbDxWWjo_~2`Ned?2~-43f`rR(IS9C{RMB}moocMP*_R_NAXXow*_3LkkSlGDlb$TGB&TDZntiJxlcm@2z32G0M}> zU}o_jH*4AKG+|C{))3L4lR`X;mwo?*v_0trN*;9KS5m6CG^OSw1h}guG_I}%x^$jL zT^Hu7SO8ut<}e3-bFPM z)$It4?pSjr7h=xVVT&tSvJXb2qO*+uaNo=*3=C$-g2NniTcH@He*+B+U-7Nk>B!_u zQ}gL{d;y+5Q3MoG=w?y!8O{q<&QhR@&S=6g7u@%fd7R-|#Qk@H zDx5kFlj0LP<`eMfZ56aMWJrI3@vFZ$vboGECDB)M;pc5}K-EJps3a7h43=c`C7h;3 zn<2j9>yYx$On$*B8CS0&$`X^Czb{SjZ62|evdOb#7Z5Yy1oAG2I>DQC6eiZIj8@FccLKoLYm8v1IMtQH(&MP$B!LF-sh-& zr+y>m0i&k7_{LzouYm5^mEZZpc_KGeFZw4u|MPR?P>zqgPV2KO8ByYu%>gG_v8UU( zNetacj$clVmVf4N9fDtSuPk^O(p{!zHJ5<%?V+2M>FqGyPp>OfOCUIwPD>g(Uf<|t zAIGBJiI}l#_S*=|206f})Q2z9P(ML5{ZabolS?uQc$y+XR5TK*%dmpRdPUvsKuaA( z51bq1jK_YmSPBV<{FBf`%3mszA zFc&XQo#mF^U`#7qn(pv~g;=@IZk^WF!8(gK0yRAkq<&cA_@58nPw^FiPlmT0!KF44 zm-?>v$Me(MLzcw^&~%dEY}WwDEf_Ent?Yv--sCrQP+wS6jSY!vA@RKBlEdeRDz{t< zhXI!SS$;;tCzTFmb#PkNNFULXcV`9cRGVD{nXpZ#IjPYVLDsi@kc7ZV-wDUM&49-1 zJ1BpUN=)x?MDwYE`z37yL}`c&H{)YS!rhMSkTQ#NF&!%&qoB{-`4xj#_jl&obwNW9 zEjAJok-Yo*f{$JTJd3B1qXW+Zn;o3a_iGzE{U$u^C;Sk77aSAzOs3BT_Hdb*(A;ty z!tPIyalHCbX}~~tivHfi6g(COk?vvM7g4$kJeQhk^!YlE1#i_X7=#R}RxSpaMV266 zcKMS6JSNTq$NtuH8nNps5|~${QM7Fn&@K?20aQ${aON*ii01X6=k`2dSgA%x$wHw& z(Z1;3chqk@LSEb+_miggH2s9PrHCZx;84fKN{NqkFKtW6Cor^Z8rUd^YvM>m*Zt0X8o<6-ok@PuHDl= zva!MR*xccG(&Te{rEQ-lvkZzF_AELRaNw{pwGfZ-_?frKkOsg0rlqQ`Uf5objzF-O z`I#99_tsAOavEiiwC(rvy5~f}~j$6FoJq)bMy6e*BIJ{T1m19_XB~6La(Un?`BJ zY;bex$isOKxr$dK4xzAmLQf#XkBegSjmoUs1@gSbFTgy<8~Nh?0tpW}Y&P$Q*dQ4( zGY6VeDWOr4gkk@wfK+T$VEq%){R6y`h6@#KdC+e&F~d3J%gt6;j1ESg3|gfXLrKYB z#5h{`;V`fg22yB=C$4etR*T+r7BCwZS>~NEelI3fym>7L9`j zAE)_JGuKh|1CaUu3^>c9t{6=(Y2&$NQG1MynMhP3CCqJwl$s26dPDTL}zW+H!(! zM_x6p#zt_}0%GmC$iuex3t` z0mR7oS?CcXHJ2fCU4_a@)dL$lhX-rikoEjM$hHvHn2R$ny_{SqYo8+rQ<}Mk(SGp9 z^fM{V4!Q}ve{vj^A&Oa$tQ6~07q0!+$|QY$W6Q|MAhF-V=(wLLy{uj_HFD%vwStik zU9}zt^cZW@Iz)!zUjsJ7WF0=sHaS6_KOgwzas}`g2)m&3Vb+6uSYI)7{IwZ=iR}Fy zP^MNF8J?EIkG&*-E?aAXg^a>MPGiRjaM|7hPUR3}hWbj^&wmB#r-iDa>S@P^xu;JcwGT@)d}DD7U#Ir(TarN`E1Az5zgAEPiq8 zyunO*U%PESp8D1C*COpqdm_a7G-AZqURlgH7yc_mUs`IKZCTlkfxPZ5s5ag0+|Fkw z+>8096AS8#cq9~zy|DK~scaz>K~*ePYX?;nc!yIvBp7%)a=slbw9lVhjo6wH>|MC= zjwm4Yx}%5o7U-HO${yLY^+auZd* zfncyuh6d%>Q$43Mqt^JXp>V!AaNKPMJqP=(gvDm79r2O=%3`IZw@itIh7U==$#YFt zVUe@3*Q8k*o<5e-ghT|zi-eaFql>W<%&+}{L5GD5vR+vEl(iL;1QnI}HA8T(rlV#A z+z}e;{`Y`-VL)@abVk|6HT^n8e&dEsomTaA4}V?RdKHIQ@T32{O5mq>%B=N8z4Hu% z1MP-XTt@D8BxE1O200cV(ab@wWJtXMx5v>fkKD( zEA>Km5v)U+$2$%LCve4|irBm=f`2E%Se!u}-hB{_M0T*y>~w=fynz4ZD!<2JU*)cT z8+PUs{=nN~7_-AUzQ<{;-_TT&B#F+MbHnK?*Q|pL;_=KYTCO)UruKui0eK5qL`@tt zF6MLq`-2tV!=KNnX8tkOb(Wabd&z&luBS3Qnxu~+`%RlxSz zmapKDQ{Y45g5B(*UEOX{6Zwmo7do^}5?Mv34Wq^IF)942bZ!TXp9!^9>L63D*zHKF zNTdXJn*PFk#5o~DV|Q!;h;Hfc^B^ly$TL^}4|o6EpFS!BNS~r7PB^RZQ>zW$49E1E zvGCf?`J;F-+O-+s7CBX`o58e%?-Ic1w zouts75OA6VD*1{rncIPP!D$=9$_C2sMMg})gSDYfU-Z5d7#s~Z=omy{4EuyfB_20- zkbrYJw$gEFK}Ro=!VQ*|_$yMyqW(7XXtmJ}wW2Da_2?-xURpVlViK`wqa}Zn5;}=8 zMmVby>RVSkTsciQN_cDFYZl>HqZ)y*hOU1d)OA&PHke)#r=Get;Mxau5r*4nfGxgW z#bZ_CLK6xxxw)WW%zqvNG}6kY%;Hka!XF0bF@hO$G{?2fie#e*jt~C-UpBa@oXAAZgPVmO#7U`IC)*{RUGsZUT6dZXADQcV8$z zIMwt<5d6lojCk)^SA(+iE)z4@BG-_1c zj5$WF7coIak@zQZpFb^mBf6tWwhwv|NL4y?D^65YVt2f>?U54Oq*^G#~`!0lXBrgVGR zlMC`rck=(SMQGR$;&2NM;x*AvcF_a z8~RwwC+xFcYQ)lwK;RHM|82kXv%w56qJjIVD&K3bNpK#hwOLQBqD<1=nxK7g83t36 zYp;=IuUBnnz^N?Zu+IT&o%@*A%ljpXH;*4iyVv?Xz6JnddkfY7B)1b*dYfXfS%S~! zLR6}IkND;41Ce87GvW*}I%M5+aFWta#PU4b<9zz=<@iiH!CFzsGuUDETB_jF9>j73T0*F z{L!Sv!rq9FB{5>*Qm<=LNnqVPFrQ-k9x+Rd#N5!le>=@AWs@HgACY+^{sUCq_s7I) z%=*go6kQ3T$-Yhh9KQet?pSeH#)!Xx9ogx)98nRdi@3g@sY!T|#F`0|^g%472@)J9 zr3G+{(9bcl0uZJW25f7~r+SUECv^nMiPfb;5W>}bYtt{QdeZr)8~kuSzXs-Lxxu8B zK}Hqege6)G%DZ~63+cHuN>bM09dNw)$CLw;6%khh#a<6nP#8}3=aH)<*3pN@XuG@zfV}Zb!9WW z+lN0@f*|G*0J&f0vzee3m$XLZsMZXNt)q)3WdFg=o0h-MiCPF@-m6{`+_N4r0LvO<{@{kN3r&^KsITf^t!?jAPv z%_*1Zbz*zB4;&qHiK_Iv;MlcL$Dh5!bm5(Jd>VLjXmD*Q;fJr{9BPl+l0fqlCsN>4n#ER>Obsr z)L1O7gn?DA8eK+^av*!B7&JjFbfBV%)gEWte*Ny}uBZ7$Pz#JG2P3hKL!}^BB;#yn{78@)L>1s-o=&IaZH{^MII7tEmX+!^=HJ&UzSW;^4iX!mhHyE zbW0?jyFr=i%xQE&acp*)QDY#$Reqrs2`f86Ac%*TYG`QtLj8$x0^Fp<2sNE_bIFfx zwSsOA=l$^8xI$ni=c4{Zq2F2R^uWNxs6%?@;~pNY=-xyEjF19 ze*`zf_*(mN?ypz0oS8f^dHfQnp!8>~QE(13@9 n!pCapar`ITDI^_8%JrfC$CJPp32H?O3_t)pN6clNQ$iB}ZmXF2 literal 0 HcmV?d00001 diff --git a/doc/Images/Fig2a.png b/doc/Images/Fig2a.png new file mode 100644 index 0000000000000000000000000000000000000000..62a7c68f2823b27a717d1765fdf1cd378ebaf2f1 GIT binary patch literal 32564 zcmW)obzD>5AI7Cy8l-EC8eK}KGzflyjS_HlgLICPkQ&{nguPez6KJW0pj5x@HvhW46-^N zd2{c%X}$^Dx<7yO;O##CVu!fk`bvyYRh>5&iVerNFdF}wwn}0ps zIJ9QTbACnU*pM;0&VT)asJ{D(&g+UX%4<$R|Dp9y@dEU>f6L@o{N}B@`G3n`!n*9; zvmZ9)@86z!s#_pk?@t9EJdb~phWZ_+7T!WP%{y@~Z_DJ)zt(?mzr30V4T%$en0UCI z_%4k(%|2MJ$_k`3b>^m=hS=q&122#<4$lDQ*qE?45v_M#g*VIV)YX3dw$>XNoyR>y zcxw;$my)aEbjehw*PrFDKeIRE85zO-lS)eqOzsT;Pk>d=78*0ubN7MBPleeK2~8Fco^vh4%9=Ww785ZK{Bc_Y1c zHU7Qz8Yepn)+O~sIQSRe8@jfiByJ1gS*sV{8c90NE8R-Y+wt3Wb=>HKPU7Dz`%!06 zDH@3j{ph^?B}i)Va6QizS{LgQUKo0RUBeKzTQdiZ$~x_a7VQJ?k-1n^%HNcTwWHvpz@hW|ozVN8 z+}K^W`)xP4lM9^v%0_A9rKJ=C1j%05i|xxe?F$)mvPcD9w2;3hKp#y&`OU~>=fhoY zY^>IiVRe9^BL}egYjupbq^wam0=kyHFndT^Aym5)3l@V_DQI4*$(^SB*O@k7-I4Kd zO!`pcxi2}l^7l6rguozYAv??7jPOGH;J5!gW34^rNZ9$!Nm6J~{2^(`h4k)<+3B0R zJ!4}DQ}M^Ih(R2jtSNRJ-IXO0*7!g1Pw{fl=vb~=QtPvOT|Tsh@Z^?YE@}xhLyy0x z$@0z$7I+TxM5xJMPelyK9(pi@GCgf2;QoGuWxb-4G@eXdG1zCzIm_kqchQjreQXy@ zVUO0%)AZ`)JQ0D_K3RFkHw~o0zm>mIXD=Yf1@Rc&754ts9Ch537RP-~lycn6@3m6H zpo0Y~AX;~8sgUsIR4yN2Y3INAPIXGtTOOg%zE1?&@elV$54y3Maa*FoYz#lDvb z#VlaAWOs+P;=F5lNb|fY)`dOr70_pO$cNcQ2Xl-p9yo~|XHT9?k%R!V136g-%QUr7 zNcoG}B$u|9w*KS-KeNElOW#n*=aB9SWo<}SJzdyzzkT`wT z8>eM`0TZsRe=>6V4{HaQhe+%#lw=(=!@uLn|Lg9qYCV)%aIC0AEjU%T_aIHMv~`Tc zc+HvL1%9D;U9h02y%JU={_!|(M~17POztFB4^VAXDFspv5oS;K4I4AdoMlgF2TMmFj93x|hl__Ob1g?Gb8$)(q2A#*8AO;}833`j*u_1av8}VB zRBC`Gd>qVh8iwEUGNxV%m+#v`2z0z6irgx0l+zJGW!PU4_$k!)7kUqW^>&Zw70$l7 zKgjNC=xGvChyV3uNV)#7S=;uwl$a}C{ko<+i;jU*;#Daa0ki*`bTp;xgIYU%Eo?Yd z@Ks2e%dc^*>9!dBPYfez?NTmG(0s%4;A}yCOjwO z@*0AP^rCxbx-see_xb}zxdrWE~EDEL<_I9$>yIq+w@Gkjz8c0kk&x? z8`r)pk}O`*RBTVPMeJkfzg`yLrMpEMjRUC3hn48?aYR8^kC$B`IS$8K_P{Sa%F?ePbPIphXH zO=&{v?``u{i#tnmT~qkj#J<^%6pu|F-^f+KwoCKh((X?~!)M9D*_4WX7vz2no~aFS zf9rmMbVBKv(*&$VAyaWA<^ZZ?)$YSv#Xt&Ep+%j5)HnHBYl@a9)0EZ}a>XO>OPe^s zOoyZ&0puI#ssBycJR@=m{rDNQ_*J=W#2toP^I61m?-0z9E~;CYR2bayWS&I^?zeb4 z=bSf;`(qok@~B%AzlJ*iiU&wFKIi>P#(^i=8>C&<=l&fDM}6h(tmeBx^X z234d8KDBMAr9=%k)Cg5Tp7tyfr>K(HD>UECW)}34=&63S(#pFih{zfxb%KJo9ZZ=(erYmzgy{%XNM>-@q@n;n`K8$`*|<6q z9-NCth5;-r+aJiyq(P`mumRrF$IZP#ad) zs_v@qpdB5fMU!KuK@E7K^8!$HR8EozLVjo#9Jf|6+^jv{VWYlJ4F6G4Y2q-XjyK;)hKdVLir)M}rFl z2K2fHy?zO&cA1E_hmCZbSi7KlivK*-nwUEy-jzWwH##Je5aMMsQ1pA*11p~X8{s5> zZB(%qM7zHAQSNFK?SB1M<-tT&A%w^O{9&T*^#Hp*J72^;-(}lA#S$xD0ZxXd?gw`n zY+0u-Rt89W2I`u1uN0_bi19LZo=!;Zx`XP$2#>Uk1CSl}@QgVl>P(yt8s%rT9pez~0!e6jh z&iWaI4tdxT?JyMQ`H|_F=f>W4Wi~*r;w>JL6%~+;Z!D+3glbhH%Gqe^8?XC=%a5(> zWJuhEo#8VYSPJwPI~ac-GY9unVG^4?9-kl`SujIU8zS}-$`5%m_(k^>h&TTBGW6kc zn%N-opyr(ok*SGv_ba^1mDyf*Q)vi%r5;(k zpzV%yokAViMRi|qix2wtN=8(&%;W`e4%nhmhBEfET~5j{_uw=FCW96e6XXRn>YYH- zdiYp>2=GiuM;FUDeh3o<$+3z2po#skXG17g{3NSNv?;(0Q|CU(I@ z`s!O34K4Yrt<=}y{RCWj$#GU{R#cE!Esqs1*>Qu+t>>UsIWX&VIH)#3YrG3U+e#G< z)~M!mUSA$N!4GSd&6W}N$&@$ogERVjl?>?UzOu=9C~rZ#=0f$>5# zKCDxeCA|J#sC^((wpcr|FXAVIr2lEC&hDM~rln$915{`2zx`soHF zGDN|t#Whe5a@C-Ilv42m%JJTr<~|w#iBWSf>Y#WPIc~!XG{QtQq z_XtLX_-Qq+I!+_5+`u}8sOSj@?*#+s>W z_3Fv2(8cr^7emEYCMxQ2>!P7NWWfNw-S2Z2nF^L{ybpHjyB+Alrlr}q=sESVS<_P? zJW`qCuD_z3;SE}^%Rr~Rd43zQLrlM~T$bv2Y!Ln|>MunudE5qFo{2zWc_41v-EG?) zn+BGxjnxODrLK2FWX0!VXK zdRgv|XGV1D7Oi%tX_$x-ziHT;JePvZB)u;Jn6h%Olxw*Xy3!sNmtv~GDk{}&PBfVs&V0;W3E6352jEug^9=R*t8}DcT z26})o$AT_9jb55LTDRcGOXqHP+#I%+&1oR`?Q`z-&SG@hoXM+rJ)@S1&0MLr4$q)p zkoo=}=b<(zOCQNNBd$~&hx9P@ztVs%n{AIZ!?&ulDU<**OsDt;5$(FV7>Mx3fB10~ z)he$*lGv(RDz8em2nLX^g3G+p(!sJXfqaFR=F73+KR#QEG}BD@LlV?>FaW;_TJ#F^ z9PV);4et%Y`&(Dft7a}ydDTiS&HkDWlo9Rd8YgAg(r3kcUW}H<$!z|9J9HvBes@xt zbme)EAFBxAS3B+9dL!=lW@+(RJ28ti0jC8P-heRp zHKO%xlB`6qZ}1xHApta@O>5`R-Tg~-UYW`kuWM|tuw?f2rVpBD5=pET7eXKgvZk?$ zA_XS*E{q{2`vP(cvuF@#f1a^ahFyA~;gIo14l~Yx^2-7~{*L+!PI`#%YQ&TLk+y9q z&kud2BZ_q7<3&w189T7?RO+>FLw4-a#^F9$=TL*wGDrTNxS=_XSao7o36y03Xzno#M-~_hNtn*K1!dL#v-;e_aXrdWr=I z?qxtCJ%yHbA3y8~aw){5A#Ys4=G5WtGMFs2PdWa}NblT9^RB8uosg9|DgH;}l&?^i zJ0Z?dOyR*km+!O0lb^X@94s85vrd_&p8IN-w@E~nZ(^(=ZF{E)T|+RV7iF*co7|qZ zE#aS~6a8O$&5m8d!krk%c67Gg)e_;7n_a2BpX;7lK1+_@GW=G&_qJp(?xL8cCp;aa z4`rqJL1Zcg14D<5($auSt4dUvJzPQHqg+)4c1>ho8kiT?N?*$LTM-!Y89$CeC0cvs z^ebskR{uJq;cljw?3!Yr2Y;m7U5d-bLBZ)fN;7N@+0qsD1`S2C%+oA0Ie+|?)|#CHomaD0^sP%{$vucQ$#rYPD)J*yj} zCNT@lpcB!Na9Mg61*Qrgaag74`hrccrWLVa>we~?8F23Bztx9}ZKI!lQGf}J3F9<> zj+bGu>$hmL8@Q5W)u6A=W%tbF_I&eSRS@Hvp=MDsTYnsKA;*>RJ(%Ts@_lLhw{MHK zx!vMb9tmSn1wYUQjhVR!h0G|)yh=CsSg*tTCi7%NY3>y2{{0ZNv$xJ`+&sLlY20F9 zkhu5Cq9@%o*w%MZh;C9GtTjh>Wh0w7EQ5O*NxUa5!{Q2Y5*hh}l3|hvP&7hdE2V!6 zEt6j1fjs9Nv3h2I9NBR8RoQt&CwXIxmwZ_n@EM=XBU{Emn>_4{$2&!cj0$xU1~%&j zaaT#1$9a`EBf3y0O*`OZ1PhV(!m6=Z@-jC_3H!CDi>Lo}>r+fZ*8Kr1)!LWI?K|RM z8L+I#R2o}X{z#;zd7uqOf7b>;0oO(;S&V+#_FBC%hpAV;QiW-rc{guzdb2}f7GK~& z?ZCFbgKq2PcKg-y38g7jMPX4bB9B@TnKBA)_0+0Pfo>B3mj8?Blb)t24J12$GLDtz zB*ijV09Q1Ovj{fD+=6W_csDNev z4~#U(l0r$1*?;T1^iqVAZ#55tdZ*luEgcd*YBxN@ZkAbRX}BCVM>-NWACbE(W9^ZS zxHp8o(B4%w+eXgZfPR=sW>0e^Gwf*&Dbj4!1Jc#(2IFW1j`Kx*$rNS%VaFp-8|U`( zxvvew0D77?JZ#H-IKO0FCBiLX&#SGV;n*M{dRZdE#iv+KgOMt)fUJrZQosbQ1EOa9 z$qdI#ZQFUV;W4}CWkDn*_1*&>m67>In$r?##Z+H<)Vf5d_J^1@2z2hwbu3}65`caa zzOhCKu5l_dN`InG3ycl%53#QGuhMS<%NFz(4?XcliiC0}&VA9Pf^#mZ3m-8=Vj|Qm zsF*2!5^M5aO?xMELj9jwjcOYBgAx$ zm70QVr={j#ekwy$Xr<%d+%s^Kg|j`MMdC z>amlKO~4oT_L$sjWNYF8i7D)Oo-Wi zBUjiigyUU?cHUiRd1dVM1R^H(j`NzDz^4W2Gf<~R4|H+z%Y<{7s^oK8cS}``YkxlZ zyw4Ap`9(jTMa4r_p6}`oPj#iA@#dl*_J5^5!f-^g!Rl*=jPVw+7vY^yo(~ZOU9-Qo zGg#$5nyg}0Q#=t-gd^CY!sY*r$+B(|M@X#FmOXJt;^KK&ri1yqdu&}^FgsPelgTlb zqhWjH*rFBqi&q(hJpoExI|Epfov>Tur%`&1$kv4IdpXT1R@iCyA0ED5o1}q2GUv+d zS|{Xgh+Ts=8Msh9&BNqctNZ8@8Z;{oO9K-+Vg<{qhV>7#`veV9&24b2dgoh_n2yZx z@%flUd0HGVbC1YK!^<_j+?Et+48jMmJYu=0tpN}^?8<)=2+0Bkwsoz*A7Zd}H+xWt zk=8P)D;%4U=D$R~DdP9A??R%g{j&ojvFc+%HVn^!H> zd-X_a&N<0H?Uyg01G586VxaU+9oGNC$7D;UGPh?y5l(YK00kb!;Qo534HMelMxbOy z=zKu82LYXXhSfQQeJKVGV=*uavqY;Zaecs;hk)RImsz195P`W&Y1F0=7;3 zE6Yrz@#WBsL$nZPNziIRPA$}?#$YdSQJY9W5c@evQ%^E1Cz=AeZ@u6j$LX;@HJIJi z+w%I^=-VoVqw|*f^^beoo6J1TS3z8!6!V_T2H%niqLLkKfOjAa$F0hzL>eRKdNP7g^(e>fr8FqB28WR~bC9sCBvR8GI zL+BP#yIeMl%>ceLO{)K4HnO_tTh?ZWyGzCU)Sya1G)9|N5z)P&*7_t@yu zL1@a4RM^cvmt2YL7qz!0iHt41g11EKL;L%`?Gyx#=iTe*EBj2-lp|}vS20>r*I#lp zG~_}4i`~H=*b>8cx>g+9Peb0FUk@rx#0Igy*YtAANMZ2EC9NAqIMh(H$!WaAU*k$} zq373JPv8SZ(CmPxac`L>cHZOswZArFJ6{c|%05r2WcH@$jb!7e{qK#*_ z94tnq>|Hl3u&rsaW?ifqA#`ncEe-XG3)_LF*+B)AZc%pq{T7}$LmdrOl_~z;_8l4K zLJ}_xp0zl0wj8A%o=EJziq{B1USuYy7}cLTZ)E5;iQzkiSL|+a1ySL)cGK)J9r+E6 zVGdn#zzy7LOWj}d7g1O}ms{hf^atZ+@2_va{hHu;#^$)A7q58QJ2=q5#+5^txrR^* zZ}>^qthYfBfu5L2T0D>xP<5q+bBVFJJqq|_N4RsdUnaUsh{Am(8#apj)w z%99Y}iBIE-#r6Jq=x;D%za2mLG9PyfD?S;n5^d%=#K{o43mkkkCqk#foW4DVh%k-| z;u39DT8Kq-^}W41cFWZ55W{z)o!PS12Ga~rsj=kOx3#5yiAtqq?_v8;=IyYk8!;;u zJG9z2X2+c<@dDrVu$m&z_uD73MFfASwx)-Yi3Lv>zg+rC(|GJ8w++2677<$HggNeB zH3~1AdhlGjDp>M5;EQYBziRz@}TLGf|$Sq^PKRTWgt-_3Tf*$KJP} zGEEZ*!Tm1@H`02^cxh6e_Diy>-xJK}djh8$5&^kGgkNvz`dA`tV6&U@DAOmw?qiQO*>~ORSO_u{$|CaX;P-!Rz%*$z{?o0d(8pI#Y z2b;}zvva=j3u*#toxPn)(NgBtH&2*wQ8ZFGrWil3j8h|#On&2)HaP_Gp&3SRn&paN zOBPyYj2Tf6Hy1c&B$5mcjO+<;6LZ>(&yEm((?9S_ z$81g383KEvAr&71bz|g^5UdMRrT)ct$>C4Vn3sH#X{s`x4-36c&G11g@#~NZnj3We znz|ZMPlYKF6}a`xEadj0#O}xUqUI?ib!OwA@I6t$z4j;mT(8J^Kx9<0pLW<}g~?i) zl*tWy7rdN?WqO@w;5vc53bL}z8IxVR^`S%T1@MNOqCm9SBnQuh*eiCFL$s8n9Ympu z!~~5PzG@k?QnpP#DAC8vR88jF(g9=WU<0H3y?2flH7%*h-8n^^2Ptgcr)U7jM(EH0 zx3 zJxRwNa8Ya79fZ9I1abGNtTUtSnAp*BIydZk37xty&H+8SSWXqnG%)OynK45xHGpkW zI~OEgU8xk;6uZBCx#Uz$wPTeXR!m65H1*GXT4TV#yO4)HwGUWEN5eRlAh8yyFi%~O ze2gXdHq~*G;YPEaRvVC&myJ4cG0;FGt?NQ>S8p1Q~!mAg|di3tS=2XHk@kN zuT<9&?fqew-Oq0jOL)j1p2moA`8}&)J&5j^hEbPu8AiH5$bvluv28FN5{o=0*88{{ zj|*drT5Yq8%?3p=zaf$xB?L0c`Ni!Uai&m=;J*Ls5sAXCngDDGmhC3A2omO*OOuRD zkopfQqmE?pXt;e7Z+dy9ZjXBz1l4OL3%#Dwc^? zP3RrpP<=~c2YIcwoG>CvDD`B7WeX#oN^x^evf{dOv5HlaR{gYLGX*2Yf-hGjd61vs ztb#Jm zR{OYv;+LKO7QIEI$y=?xHWh=whvbYDA50id0iGSS*Ym8@Kbph!VIYLL?~1nnL{A$sd~7x93u2Mje3nZt{)H=O_~n9JA&xD=Fq`u48(ZX7f=wUqP-?m25poq4;y zPvtzz%gnbIhq-}IQi~T+A}MJ%o7b8X#be#{K!sA54EiQ^@J~=2>W*2$eteieCk<GNvFgn|2w%v`$L~NAZ{Jcvo z9b(s2F?sLwDGGG~E=Uz{&VE&|VyWsmTU&0gvI#=*l1b81&@Qv9#~G^or?__V=9p#{ zDZ@$!N&@AbSzqvveQzG>*Q>Pgr~uu%^d3q>&ZwG*hn_he*?P3=5joA6jG;wk#KE$* zL0T+I)XB*`1)0XFOTst1D-C4l6M`+@YQ#m8^thM_J%hf4Ji{*9inp3e{YU*XsM5*Y z=`7mIJCoksWlMJlk?w%aw~?mrjSkTT*Ol^gSbDO)hhU;2zYDY;!1qeQ+}@2l&spsW zBhO5*HHPE>ZB6XPI$rzq+`?3uD6eM{O@G1Q&B1;~TSG|FcI^T$MeA&(Yx=gt$4t@h z#o6&6$M*uXQpKlA zBk4qr@2XmlnX{}t(PXMjGX}~6r>#e4iw13i)Qw4K4b`0FQwZUwswSs>6l8~$>T=Y8 zYr??DIi1}_VurW{^0_7e_WppBq6z#hzMO_Gh{wpB2@Z#5HQ=z}REf-)n-b{8N(lx~$Fo~ZL85^HUYgsAIAM=Q3t>Hm&d4c8uxGqm2?NlCf7sUlxhF#iF z9LFdCL7MB~mz0R^X^ISRnvx`Cflxk>M*Jz^Z!)ARXma#!_T`=7YcgE;>*TbV5S)UN zz{QL8@O0oob5-j0oDG@oatGoEN|XV;VRteKjO>rMH2>u{_k~!GM*r#dYcMr3DPp1O znV{AF@--o#gt&_y63J<^62Dek!HrKlsMEywcCCNH*&TgwWWD0C48>0W`%h8W}m?QT4UHGydtY z?y(Nzw)un18N@x)WLHR~$f!~>2?c)Ds9Z+dn6j7b&5eilQ3!nF?HcD+6bXyCz1ugz zXIzXp=FQjtW{e>}?1sK-)oHAJ0`aW%Mme(SE+z5TfZZ{sYqTajW|GC@YF@g?rV5uY zF!tF4-kRMk&9C;T3h3r_GYRTPSo1`@Gs(1~?aJ|(;4~TY;y~@a+AI}AXFYqJX4LAH zjJjkcbf#()oz^ML=4oVUkRJpA{SD3BaFH3WspV4snx`OU>kf)o%?Ym=R(r16n(r_t z1FPDWL5AgZ+##JIPQiQ5y6U5SIt?~%l?HAt-puIZFMk&a^YKhL_{c&+P4Gz<`yfPU3!yGmd4pUt%$S`$7qtnbK;a?iD_n>UQ$r5Pz~KP39* zL^Q?%?ZRciqmG=-TA#&=KNt-WF8_8w&pcIerQPdtSTEEY=Vz6jN>$-hRwaHWc3~Pz z&v9Dt=He46OLk-&>lMM@e7;$q)u2icc_1cK*n6k`8cmu7h8Q(P}gVd|PzmIW?F?APeP%<1_t(`A0gjpTGU46vVWaBQ*XoWrSv?5l zJ9E17Ykw^GNxqK$L!{RZ2yI*!F!+_T!MB?h6tgA zUlRpo2R$|)6M(4O>&Xjd4L1B(N>DlKeAJ73X3N)j4Q0evp%5D83?`nYN}=4EgS@#p zOTeUqU;5?NV+|sDXNUCi zj$A*heQ(LbQVjoX|4NC!i0dmKfaKzC0QIYtO_wP^i2}XG&~*YT*$3ScCT9K|*N^JI z?gI%2tQzaAw`Yj3 zUR2+1U2&czBo$Wkm%{8nA@Gs_e|qbLg)kTJcS((Vego30DM}11SA%L2}epeTewuc`Ob5{*gd)s*(lh-0=+PV$9e^wgTa(uQZkSH<%xQTYoqssknWF~1MYm@;B>NZ-YDm?m zM?OcJ-eKw1J?TIdbPvTQoTUSYbW5-Ery)1u79DMu+3@XW;d9qAKxXA`NyAbOi^~C49u}gN@02c2lzV zx0u6q_41Yh)7>B%;c?l^<>Keaa?BA`uEDtTBI;NL8;2e-xfAXe71~G!--JWtoas_r~0&=^8AED1rw&Kz(se~DaMjZoIx$(kXl0) z?nyS#pw)#`*K~-9r20{mtN}YL5{2T_(U7303M`2He79RQ@=g9c$jFRAc>{M$$Hc?l z6{f0;j!OlUG%rGraal7UD{nzOIHL&hlYNvgzcs4`PCH@XO$XCM`KQ-VoR-)2QZKYv z)3*mWe?L3^%yP#)n>!c-Z+mq;`YAdsSlo6=ENYtu3)4<+q_y;i(t3|n; zwzF|9K*MkJ!_a$K{&9x=s~eN_90_`FTnIKpoengfPBEvZ|IPl@L88pbYtZCB~IYd1N14$of&3G9|6T{!jcvyzff2e>#Bf}6&0 z*DantCKLCjm`nC+`rm7XA{|!JjE)s2t+U18ov)eNMmVDf(q!ec9*hWP%O3VA?&SnaHQHNL577ih^&t7!B{OuP;YAm_J7kErzs zmRn0lm{HD=&UcZaI3hTwjk%{Pt`S~48{Z^z?3;XzH#uL?*BNg>cI4YJ|4h# zBEG|;J$YfI2y^}D3&n}i?Duy#HB!rkmf)m0vmcVXe8hwqz6X;!*`V_A(PtQw<{Pmu zSabcG69UWE8=$`Zrf3v1_431>C{05`2TWzN+b5lnm_*79 znW(LT3$`q=lAKsnimXZ|!SY>1xjZX_Rs98rR>l5EwD* zqW`&u91lbzzN~q-2V^KmhJQ;>@r>JJGcNiEfG^Ng!+s-?6d{QQC=Oydeqz2bEG<1Yln>_c0Q&R z8>&U&=VbWWxIcp2r_$%<#GoZ&k^M9!Y%nV@4>H^>c;9p=xU1TNosZ{8tPzb(b&`zn zlVxFON@vQsvNIAHo;gV3fe`vO+<78ZcRfr!t zEO)o7r{~(uiy_X*HU^;70(Xi5D!Wx*xUA_V8N>v+H~@*Kuy?+RcAFvcn_M`s$oR0o&R+_pvjzzbzR?<*VhlsmrZKC zbNSfU7<)Z31P3BP@1>WmHfO6nhBz~cAM*?;IqT>rR-$R;kE zzh_93jrF6TGWRBW-eg$5o@->jJ+PDi@o&seJ+uA(yt?zGobFv?6T19uIN6*3_g%6> zxM@hoRRoRz!z4Se#?;{Hn`whW=*BSXtbRfpR(yVNdX}S)9&)~pre*XUQ~#n<-qy!O zc(V5HlZtpHu9VkzcdJKttLx}e*vZi-mx#PNV7B7vQ?#|y{Wwj`Jn@=0_hni}<8F-3 z=+xgi0XCgR;9>L5x-S|!!L!Tr_zp%=kj>p~Br89qyB$Be9bbP;HqyLKM>JtIIG>tJ zA2w~S-=0nA4ZPTh;lQ2mxM;xRD)6%oU_$p2=2PuDDGqp<%rBk@O5Nvd8?W$<>e+);s_Am$ybhZE4mJP{! zb6`ymwz=KnlGjMEYo0BGO?Yie;v~f%rP9j}*9j9HEKM3Qd;`u)?#kkWvJm(WYyT3~ z)Wcb{d)BTi*XWl|Ga4Dq&T{6pYe9hSPhZ1t4$Y4OjF&RjAMQFIB>j#8*IJ_(UX>9Z zHr7qZNt5)ymu%nFdHr^@e1BZvv(8`~XGV8<;7_7AO*oL+rx3rZo5R4ho6-#3-sLcL z5ZP~O{C5_lP8W|67t8(1MrKBz1uIITt4DkxA!p9SX{jWIv_tPh?&;U#CYQF0hH4$= zYzgJdP}C(|Ne_j|=pzr_)Y40ttnh2^MmnfTsrvB_fgwCe_zHK{B@I@XR&YY7oDti9-WI_yAyL7IyP5+(~6CjM{5o5hg;{}`bNB~-zdT^f2qROcpI*pmDxPo0S$?W@PP&e(-c}Gg_ro7tU1)B{l#OUB1U+J4 zsXdR!3iRSi(l*)}cwEt;;&50;rZ%M`+0#XbvV1s#2n0jOFft%Kru}~d5rsOea|Zbc z3$%18J5Y=~?!uE3l9j2FB%QHiijYnz*@VS&W=HzN>o$@3w80j$#EBM1+Q`TYm!>2Twq-@=6{CWi_3XuwBq z7If%#_ihUh^CW}~H}*dT4a1y`w&1cezUF_TkXq*aLV>fb^yF}iY((GmkXW4GxBvW8 zSwR*2weHAa%kxJrPLDphP*i57g>-q2dDTuSu=+jWz2wgzT!6{%TQfbForlKU@s&Y9 z+fZick*uN4cVG3TFCBQ=P#T7c8p0!ojSYNkbTZY%tXY@W5FH|CPA3kRGPNxFt{)C> zs&1;6-ASIAt@3I3R8zW8;Irfqiva7=K`}mgAe_wiO=8<-tm)^Q$GuS|sz%H4bTVvJ zmAXcW7;+piLf3bn893=u74@0bjHs50e-d6|ia?mqA+6Y=Q-6c7v%qk~w?5S4;`0|T zO*HoD;;+n;A-xb!_dT?JC?r_dHcIQ|g*bGk{S1azig>P+6qiiv$;*_?TG) zP<#Hvj?t_Y{NzwH*G%7yTZ%kIb^+DfGa7Ks+=ey7ZbGOa9-$bgZz1U>+3hC@ZrTDp z&c9f-*&?OU;Pa46Gy)vB*lLw~hXx>d{guID7ZaFAn^l)LkU`Crvg9X%L~z@yF-;?c zh86bwcGE`S#SP~CEB-Tx@Bf#}mkQk00+Akx;*a?9nzmRZXx|CFFQHvrM8*Pmy#?S& zu4fehMIYFCR9LM`DKne7Vz=31y!eVi;4#L=PJ#h|u;TkY;<<@`?m&&&83+aMR600! za&)KGFea4SA1gWWGVE>LUfu`>9%C|yW#<*%{`dfu&&tFXVa{AACHEETv=QKhUv71{AT z=dEEU7ML%aOe8vA>-=)IQ#oWz#Cq57&r&n}{tou+#C+&@bwwv$EZ2)v;od;aOF#4f zU+I8YZKL>bu}dbO1vVSzx~;w(%y{KwLo^J|39#b+{sw>+ntCPR9I)w%6yYdZd!tnY z*>9QuYY)~*|Nmb*g`zWjB$~{IDeU~eO-$H0$K&w9#>`iHYziM+3Z1K_4#UlVj71tW z?(N83Hb-2x?Z{TT0BF(wX0DKII1v^pM$~YXN5V=XzdwUeH*lhlg69Ot?Yx-KxH)P= z({r)WS!iN>9v`_Ubg*C2TK9)6nW92N(n*?a$2?^~F;uKobZ2t1c@-*%ue~YRPx@V(ttQDM3gcmul(2K~}IvTFGUDTP8ZsJPuiM;ES(MD9o*bl+9FC}c-^ZLy8>f&C-7odZh& z`hd)*bHFx^{|oF8hAbLIBmVGN>pBT2Yb~3pHU;B>Obf9!Z?}cYqok9xKsPH9mJHo6 zIh&LFHB*{MYyNI({g+^ca=EwS=6|UJnn?Sqy^&oS!ihD-d`Y&XQgGFOVBRdPAbk{3?+dC?KH9YrN zjllN+Sf9zEhEFPY!-)jcYfH4XAG?BwW=0=o>=_QL9`8Cg)|FY%Nx$iga6$0JVnkBt z?WV)Xz5mbbi6C2ND|uWeJ)_5;FGw}w&}l0dUnGBLICh2}=j)!<$j$2Wg14i~0f;ZE zzF|MKw?Hhz@bCByF;YAk!C;`x*G+UvGRuZNLRCvYY`8I{5%NAAvd`@pu9v zhyeCmoD-eAL~N?43}(JpY4rpMH^d^$J@_nwNqb<9Os=0k#0n(8h40w z*e~po{#C!0i%BY)6qwe+?a}c9;MhBs;f1IJs|iWk3Gu3{wtsyuueMzQ%b%H=(g8Vu zOs1#zoK?10DRLX7ISAq^@YF}Z8Bx;ZrPWa3czE}I`pf5_6HGy1VOFwGun?&>mIG`6 zTC1!t?&vmtYup7C&9z!b!QVZTKh%p53fy`e2PaVb`>U-lC?S$a#Q*yB3Cr!QI#6Xu z2C#X4D)P_FFt3o`!1v9r#6tGOifB-<hNT5BTjf0`n%y_ z{k|2rqFfalWQWZ#(_OuLiw@xHER=yBolmjokD&yi9mdi>h^QllztHc&1n(Twe~9+W=Utc1H%Ue zKj7C)s-s_W)bje)tOmG+#90qAbQD#KGD<%JpsJY90mMM3`znNbRt9Q$1}w`#2jM!} zl8-pZa<&$ekqxv0ubM|3%sNw~ZEiH=AK8kFE z@{^j;s%595>&M-3o*+}&TW&{Kp+$9uDV}(@8$goJsUuVtHGczGzSe-@AO=fbKLsvT zrqeTf_|W_xj4&BrZpbruS@*ATG$Vu0W^fD>D=Gxp+wE2l0>MRxiXvOJy)ek1hHVQ` zWqtM58l*dP(Mh5Ol_7hirz#uH(T$P$KTI4V$+}{3Nc*B_-_BRwA`FKDzb*a~vyyVGc znfxaeBbsimT$VE@7NJ5m%M?Dd18{_`+;@1cqf@w>LVKdoQy7kT{YN%$HM zLOOFf*4Ie5<#bfl7sw$>SyllEPFwOH%83TNx;bB~5NZ7gDxlKPR46CrMGmh-nL<=E zS~JFDqN#t-Fg1ChT1xgW+1P|X1)0$bkx>qiiyrn1g9w9Hs6L2U7l9+sUz zmE!I5#m^{#8)s|Hl{!Af03`C0A#$pC43Z_$*#FV8aOZ@0*-AxwFI~x>I7 z&&&r~rlANC*36lg`d%1!C*L;y{>l>&0*4qkW)P5szu52?&pb zz~nmLHj@dGyGY(3resJ@kq&k7X;Beu#v?mUX@A0$k2!2yPG!Be`yGmSE(r zNE$2VUdETOxsVWMy>`%O9F#c&PW zG#5P;p1q#rvo9Lj!B3!JPN(`qE~f*;qIh0zuc=y@Yvoq6>&X_ok?~5_q{u*4J8Kmp ztpoaKD3X^Czx+`p-sHkp$p%fzNU|EZ;H&Kh+N6HUf!$h{>Q`fS=DY*3_g{518xZ&C z?keccel+Yd=Rc%Q8W@;!EhwN~41i6*)s%_1w;7&uN=D4^Re1(07x6nP@dR%fLg?;j z$LiA$R?&yUhlyVIn8W2=u}|=IQcOx#VnogyW0?~4^l4KbSdX4yq=j7%vxYItAjbp4ltf`sX7;&Aac%f z`S!s2(*`9N89pbQpoH5cGW?Jam-QTfu{nV92Ngs*eG>472&O&`mk>fKAI0P|QkNnj zAcW}f^Ui1xGXCjDNH!goH0k^1d(LIu>oV;7?sw$$I^*%O+CJYw37HH5<}FVqBk%ij z8_t@24=xtJCxqTxJh+Vs2zcpr1H-G;7^2LWQidVXIQ7lM|_w#uiIJ z*~Cu%Zq=Ge(JH$FV$>=1+GG8nx_s{7H@zp%jP#X9%01C@sL15mh2gyIy8PVgNb%8n z+sbK-95_K7Id1LbwNNcCez=wWBKeDVWtsI{4N{8xpF|~Jg9QgOIBHR)@q))Yi{tSo z@6Ou#i4}ftmR;L@k(tJyAR(`MCFM}nUgpVhGn=IN_qVtn<@fh&J(e&IL3Oq?LhpyQU3&Rm^1 zZg<^D@%fDD-|w-cx{{hSO4e)W{#P45_ObbVcLq$KXcz&NnDR-m`)U2*bry0$cw^)a z{kXp~3*PU)9W{5>u~HXEsnlD2AnNm}LM5^qfgWdJK;vl8J`GnHwBMw z$%UFi8IEw#qqH)0sfXx{OqY!b*!a|}pJP=-NUIPv%#)@&O@0JQ(R{U61_SDv;{ixj z#`y!86+Ph432oSZYAF21<8AE`;6OGWiSzBv&L{#sDe#|_ZmVnNaTRrz46e~iNepA| zdS!If{Y$4f>x`f!c$r&R2yDdo6Dx!DRtr{wPC}+vw0f0p%2|m4x1wFsb_mTA4I}IX z83%Z-YT#ZSQ2F1xwH&+f6AwJ$MG7o^Oq^dk4Zitu_3EwOW?k~%b#}Ur$H3!&1dBn& zEJj|VG`0kcm)T=ZmZNO+YvYRlZf7cge?}PuG(SF7<&H$3jC;EjQ2v-r^hBxetxWa8 z2T+70DI;KHR)NHjR#v5mrHqLq~7tQb?+w#$-uW}YH z&-VAb9&|33{iyolKU{Mtb>jNguYM2*?P3QFb0f%%X|ggpT^f2<$=$f9zTEB?^<`r1 zcPm-#;4pa3ZUTN6Z}n$AoQ$ooUJww^qpXf7uKaVmZqC(GpgYv)s&@pDf{@pII!0t1 zp5RCIEL7P?EJ(-;nW%^uH9A#u{I|8$1vKkyvYJLzmf=^U$7Z`?3){x(g`%q~qcxRH zl4z;(dz?O%k|6?%3RV=*W!7gi6JF6?BM;63h(cV0!eq|tv|8*-^VI??n>Xs0OwhC# zIYQd>#~}@+7c1GrM?<^X>#WKXkI+95bp8XhFdxOC=L-&%=^{EI@(D^$3DH=u0}U#% z_WQX69qY>CG=GvXdGHhWO%1A;lnY-j5tf7ly~95Ui&O3 zPbWq0FlX{)mxA`td8alE+4-ohWFSU+jT-Y-0&p($l!Rc0x=I4~;e2EBrXeEPvi7du zJpm)=Dx2GTK-1w3Sdh->$?8+?cijetM+wHY#{v#Ux$xY6nRNz?w*+j@FZ zIyuJg@lF6?0GivIOQK_&*%^AIjqyV~9`nauWhcgx1+~$;Qvb)&v6#eS=9(3QjyL<948w zK)*4tMbzy70=E=u{AgQMQLF@Vfv?7^aI|HU&$$*}g1f$IW)vef7RxMkSTdcYZ(mO( ztfglL`GVm>y8<*^V7w zAU#D+lISmlqn_72R~(v0jF-Z)*sM&&{V`>#QX_qE^}3y5m#z^%F4hs0^V$*7`S0Ph zuY>#RjNw)i(uwrNquEc^SBc?76oDV%Y%~!ro*Bg?MByq*fuoE{oY>^xcsZ8pEaog$ zmD*tQyY3lx7Vs#tNL?j9DTph};-Ocz6-HbjZaP^7BR=@~=~Q9pA$eCFl$~C70T?PO zz||e&Ni?4Cm8<-*$6BljQvr`t3M%OW_#>DxKfgE%$+L~?lqCO4QxtlWD`=vp#8a|* zsn*de7qV0cCz(wID#QbZ_3jxIr+Sy{-mp0n_V0o1w9D=+3Ws`0;0j*nk6>Zk3D!v} ziY;i*R;m=g8m77uYu@2L7G29*cc)AgL`7XC|cE6s8RkD+ao5hT&BB&1Q{`_=e}RHMI+Bc`W^4 zhmvMChfpV{VNvX^1FcDIYM9FYz^9C@@K`x|^vnP_?9c{r5qj$ywBA@Qlr%3#lI!4d zy8p^*>i0D704#jFViMDE3ST^@Ea$%a-soTV7d_&Ls9h<;EH~EX$i{HQvDx=CI4kYGo+iqL6ZW{TN^ffmIofFC~Tpxnen2=!Y z#k=oruDypeUCk_(#!4q7>xB9WeKcvbC zzTlo(KKwxdZ7gheY5#18y=s3Qlx1gu`w5fUr^NXQBE0=9`F#v%h$ONuHFt@hh>i`R z31Rpkn4hfS)GY5JMWtK7PC!NFjH5g*UdzcP^_nM``-?`)`WlI5)t9$qoGha zvVf(CCUIk}H`TCR>?TZY3u2rtP1fX}|8*r>gsn=L5i;&@%uCF<<9^mBc${-5hrV-Btc;eJrgX0BPG55e9coOe#7T5Q z(Dh4KF_c=n#Vqt?8MXm1MWu&I?sdT7VB;`B>nK^wWWslM)L z@-{|Jm{$DM+sjXbL|(63YX#*n_t(j;lXLKM!dDdhAq ziNH|!PfvN20_;-(ysQv`t!=Sa)03+a#8OfxAUIgrdVkC^Rkh;td0Fq11xz*P18?POnViR zXfM>P&RNR|DiqM-G{N;UY+bjjCczKdtbVZaM|DgSgbdy=NhW6o64X+|* zdT5lXn_(@%oE)kFo@ubQU~`@eLoS~$D8r<%>$uYh607f^Y$kSqA6tRLpGpF-A46nOPS8%F}6;m)b)%FzX$ z^B@!C4hXm}5k&5?019h;6RbU(1#vr3%-*ONOjvJ5|AnwyvU0C7Qr}W=e%y`GjbCl^ zVF9m#D&bd-K!MDF-|^+&>5JNRwvF%d%^~BMRsq1WBu?!zVVR>uCO>RNBChC`#if|4 z@(Z`?8011yZxwc;{@P)zLY)paBH)zR{(7(emQ#w+4*Qa(BS9oiorSM@IcJjxT!=AZ z|FP#6c!HC|%ivZ!i^T|(d0rvHkK+Bq+Rq7I%r}qXzvJLjy?CSnuP}P?I3f0Ny+g+* z^f6ThWc0W|uMrL(fxX+Zb?r!5Q>egS-p^W8TX*C!D)4P}!*G#RPtM-B!%IWLg&tNX zlHzgme{c`$AU8JmzAr5VV^$;5(ML3k`qg}{efE~yF3vC9hw{H0UGpz;WQr0(^&EhgQRtW_%;)EM z3wv^Fz2AtItO5}EWsa}dmZY%%kN!OeuGs>N+>7|1-5 zJMSBx+kuUmeL+v9f2qy>5>Y^CGkf51f*HTaE%U|Ged?J;^6YDBYcNS~(p)VQ>hMUu zg}jpkU;90sWVW?_o<=;>ZT{JU51ooMqqkYXj)Wh6_*XkEJTtXLK9fl64Wkk+Kl!lX z%ywG45;JoQJ;eJtCK}@65$QW+zT~+fL*t(F^&}@~oRuE35v;3G-yz&F@(tYk>O_8D zg{_{`-IFr#8h660O@90`IdZ!&ht7=a*bk^0+2hwd4!?;lfZs<)ehyc+oH`G6y>tty z#>rquTl6u{!`0@FB;e+dzs6h5qo6C~^r#dcv!Q`;VtuA{p)+aZrZ)|&xZ1rAYk=V5 zy3msRG^8%h6aLl+49a7ptnwa}OXTI|++9*)qVJ zaO}JB*?_V#bBegX*~OFjrTi6AhKsLfziN_8*F;45mn!AHYkJupFx)HEM62O_9cDv< z-k{NuPMXfyn3`Kt?|o$WB=oUvfRt$QPW>qzvQAsS^Jn_rB7em8^u*6 znyV$AWB~X@hVgrt3(cym^*dvX@E+no&?)NSVmOEAG-8=X{S-Gij!JIpC;vx~HJL^q$Ts$cdlu#l< z-BHknk6D<0k%0p#-;!28V6yGASSkZnF_nI-d~!wb07;=IB~^bd;K;r=BMp`)ECjcf<;FS25cs@W*d)j<3L z7Z(bngJ;Z`n?9~|#!-&QSgAKoABu=7_5T_ch1%H#kqW2Mv~Oz<`)OauH7Ay)7zXgx z%Z{dGlmyS*qgy9)N$Y{<1ef3R7)`4DdA|*Qe=uWP|5Ds|pl-rUh{*PR@?5#J_$2ec zaqvcC)!B#*00ltkD?Q!IQ}7;ly~kLhuARUvS#BspSZCtJW2ycY>ZA)Bk5jUTAhiO= z*)`VT^#oKLlX&BuE>tm#${bBgm~Q-I2QMWZW+*`8M7d+^GZFXXw~jfKU&#O(UpJMd zk;_#YNsP}zqe`=PpnIoBSwU6{`_0NJp|=`(IBmosqK=G+(##w;<)6xf;s}w^h&XfG zy>e1EV$LiDcX_6kE4R;?w{vQQ<+P(r>2Bhl(F3Xyp1kO@eJlMu49q@lvf&Pb*>22N zcm(Js?ol|eIp`(yIDA;dq$=k=Z`)M!K*Wbtl5uqG3)x3o@eGJOIj+F7M%5(En1kTEYi#t)8J! zDTW!7dkl|23;6{axle2aZynYQHk8-T^jWMTO>>!=h@vq+4EC7VJo%t$gd6^g@4cbS z?R>I{5TSWOfnKt&>&JjKzK(gAxEqUurTztZ!kgKvF5MZ6aIG-KAH@;+@8b7r7sJ2l zV{0!^uj5Gl)7HnWz0&^nq+@R|ke4*5nBXS9{OE7SiK7=Y?@={{m>CC2db9r^v?tAr z+oSpLnz^ei|K&T+qBF+A_s|LFPTZM%zL!?nj(k%IP@D^oQxZ#R++IziQ0CtrT;q>C zFzh3j%Owe@n31i^Kv{&$a(Qmi)?RyCZW;fQF;hok%W|2S!j2~Bi;et?@WgMhLUwD{ zD6;m;styPagxXZLJivsBLUyL*E%+sQy8=4-?N?A?jA&a~;Sx~CN_C)(d#r<^h zI!=Bq+ZL&*px-a}YkFwWWBKWMa~LqUJniE2#DdfBloTCwYfdBwS{=U^lgb3_Q?WGF zbnD_|S&2`rGl2Gy&Qnaa(D2_Gna@{#e#L*4(P3qfFcXmTCxL*|L)Olg?a5^7;hPfP zHvUvZY~H9+1;avag5hVoEIg|4;PmnUb0#?Sl^fH5{=9m$1kcMCqLHGh{x38I^%0?T zCWy#vik!Kd!8J0-r}~-e)Qoc5^-<=4l=S)hdS2N#01mdLj7UOCLYh6qvN%c}|gce0QJbJ^WBHfw111{%n?vLss@6gxIrM3k|jF zDZhWsWEB~2dXJt-qOU)RoO&j`Gg5_p=`qnU=!U7@+8}Dw5}V&;o}aZX`GVu6K7B&s z|`n&8Krf6nWmzNA0a@| zVqEN8{VFo1qw~ zMl=5KxBpc1Dv+#PK_H%~>jEMpJmipkiirHo_0d;YmGnZ^aQ-MjJhVGgzV7_3hk+&{ zHm3Y;0^1hOM-F*a@f*jvhN%8g?if7aiW$)LmF2&A{qR76zKe0_vUebeRE39zvdCd1 z0Oook#mVV~pdg5k8A2{!v3%*(cX?$6K)LmhO8p^zb=UMyBIRx_G&9JMIWH6k2h=eQ zQW5-2aPa4(`t*PtAcr@tNTh_;A}xn>NSVt?6IH#LR*tFVpST{HV-z_q>L|EKxoU~{3(+axf5`whJD;!~du5&O zMxIw0?>$o1&f}+DW}eUljnRZmNY8}cuu5FkeZ+^6Cwpkd56ZpeNE?es|ExyjLrR`T zDLk3i$yS45DFI4G4)~pBh)6jg4#SJx&DKUx>cI4M83F`82ROD)Gyg|ZgT&S<9jq5u zD_%C*F^*44CRm6X;fZ&}*{C^KNY!H(R@Bv@>oi>AZt|hi%I>*dGp^q8S6r9ee>JBL z@3Q5oC;kEN^V|_(Twqs}rdz?vx7Q8&!I|zdU1cfyR0l1KUYPd>7lVmyTwUvm!)5!3 zfQuUz8KR1mU!cA@DDbt?X1kA!J#K8~$=h#3Di>zq4MB%)?S~D!80dDTRj$kWqI-mw zY1+jE4wcb?5ODVsLrBpys9SAL@CE+-tC)5!A9)&X10BcCUf)i{HP{JECw0var7C~D*3Qy%Em+ZG;Sm|~ zdvPHLdtL2uU^S(_d9j1xSbg>LP*pU-TkSIRf~tDK^nFJ_hs0QCjY>Xrn^--|IP~#` zbe(?=2HAeSEvS)pzbBYSc(tYwquj<&SOOXriTn4`UjKvrVPKW>pc@Cx56aP0MD{&F zT7DN9NI->rGU6APpNh&<>Dsffab~n}Qz7}WyG>{@vLy^oz^XM)wiCKj=sy#`G5Td2Q1r!r|3-P%^v>l1 zQRZ3jOke*_>&AtV&7|7uaqZVuTSFG*t)ERZf(oDSHp6qvpM8K!oXF+`t_B8YZW$Xp zv|mw_Hho&SXORF=9wn!LH;+B&@-Am6-=FAG8NB0&-CjJZ4EN}r zFic&~?yxS2xiX(mBIVKG@yK!3O|6L+q;HTe-DjKw`2YMS8RBSb>n&PUjEakmm_K@| zjuBGneSWAVNj<%wS%LM$HY5p;kHq{wr!J4%Ws%h<2rH7EecJ$YvVr=@|^j`s3MfBM^|ea zVx;Z}!@?9K{OIX1Mi@r|$Yj<>Q=(N>Vw#V8P4p~89;;2pmd0hAOm<_p@q{fb5!16KKlP`oPV8A@146^AoG#NTl73{;aXGpA$Z~qA+qX z#mlsF#+i6Am!0oq)zImyEIyD}(cqo@{6Hg#j*58D#B0{h-U4&5RJ>8nZh-R+rRaK^ zdnyyA&K?&QP<8dEHX@YrySm&2IRx9^jkR0{tF(kdCTrQ0}Bx-QLPCmJeY*LSkfJ z`K6zdC6nD|Cnsx9#Vc0v7~nS!9#&%=JPvj9sxYG3o82jm(ua$@_=s#ldc)Q?Isw}W(c5DD+uQFcs9OEN3l%injX+0+tcre6hQQY6%PGVO~v5vwSly*rd} zJc{i|rp*4?EG*f5#XL&b^2F3I2s`T?CiZd=;tzO9dS*bS94lTGbCG!OQ7TPFh6c&u z5RR!IGB%2%ad6dAeeTDS9^@htqN)iVbwBt!^+s3NSE(IxztZN(2$0EP{FEze&4fk* zw99Tm7^&NrT$nI}jC$un)p<;3$eB-p6QDKTpwlDy-1P)GjF0A?TWjpGQrvv(S&I zhYV}1Ky|H-9P-+3q5R>w{q*eSNbMuHl@QS`~*Q^`o}DMo4Kia2L2X0*n|y^fCbT) zT-OicJfrzzn=TZ_kSue@u!E9iCwb*!HGo;k%Q`dPt&?%rhC02se%T{*$#|FPKBF(n zb-;<3h5%X%&ts_n8q_KB5@9JO3zz~{s{_j{W?%>3V#^+@u=KEypA`3dKSn|}KGn*{ zBKwAX3cK}OF~EN8f9p>z5~=#7*=^~@udCGBq6CJ;p8^&Ajv1Dv4mnKw{nsn0(B^Xd zX>{&mXJ;5soyU~Ag^cU5o;$@w?hC8I_}G%?M$w2e>^?@9OJ*9!u7Kl<|v5(Y{#}>AJ^;=|-jWv45pVgz|SG*w{0@2>1 zkfiWoN=Nb%ha9=D`ck-SFSi)hO{f!^0A`XfO8mVmh!ivoXB2a0WlcOHYBax)%ixDc z+K-^tcOaKpC(`m0^3G2<=*~($L5)UcUnE4M^`KH9aMTb@jh}jjbGL@g259=NcSU0{m!$7~plFXKV z&XRUJ7Ql=>Il((MB8MB~G9>txF|oI!d;&?1x$^61>3BBAHgZlO?SHZQ&X46QuSRK@b?1e@DS*`jKOXTU)J*=g;3!78O?&Y3evq-8L=nmSw6|X&h09cNmY1%mRt$fx>VJ{n-N#M z%)QHofs119m3B8Hz+Q!~E|2ru)r@1Zx|MHh#%m2HL04&+XGYAr%h&_i7L)khdn^L4 zEgK=qz+lM43ul5#j5HNq!^_Sx_O9Cp7~ZbG_%07Hdxe_dkiq$LYLURUpQa#o9b5(B z*Pf|xr8!p@yqCF}I??fiz?JHduzmzf=47V>{{5@CDeWVfeU~!Nnh*!{ z6aRhgbJb|qgY1MSz)1oo@SN%gwv3ihKOwW7tn=I%fH#W>ZFmFE(uq#!l+w>GFp(T1 z!|!rCFZWio{S>~Xw_FcI;kD`G#a&RIT+F*siL?v9K94spbNuB|Yy!l44WMMy24lLp zrr$?P$K$abN}H+{t-p6FDHgkCW(aZg@C@4y7Xf5j%&7g(a^>Z#wbX=~-+PjMIji51 zu9>Zl&IGanlVpn>oU=Nfh49c*U)p0AJy`i(_+)5OZ}lV_350!E3o7->PY3-(xL*3a z9xC}H!;G7%L6MKiU`h08UJsbHKW~1(;?( z#JK(LR&>HqctRZfum`j5@lkEFjYgt+D&;M+>`lJM^ug50XxJ7C%`UDgwaoD?k8=QY zvCSH0t5_L5M3{F(;4Bfmp8*_;hKDz{r;^Lz*DYcW{NZr8-$XgT##8`CZPce&_h8xNKSRB8!x(l9j|Tv7UI9p52vdCc7zZ~q zfP1>B4*@d9NMeSqf})D0u!XUSWXd08bp=|5vp_ZYo65|9o@@m+dM$PwPB#f4WE7AH zOC{r$FE|52NWhnn`Qd9BYPzcxbtsRheaeiKxDt6P=V35(I6 zI}?ZPioLH?wByb`LIL5r;!ar>w1vg)i$ovxSV`JfTW)|1N-5{QG!|9eRP>o)q2Tj` zt1jx^vnZa3GvG!E_7|w1O!VzX!y$Mtb67RN5<@TQ3k=fU+Xq9tv9j-4alW~!83I1+ zK2<)=6&Oc}KpugvcsBraR20AU(!)kSA~Nx0=7SARhL*LTK|uT!m(#LB3xe^4%el|S zyeHO`aTvs<_OjoQftH;uOsz0ZqkWAtLUWTdR6;h6noQq?Jp$-*Wp6Ejw-T@n2v9!- z*l!>3Qxziq?EHr@z5%bl08r_AmK-Aob6s^CBSfLlS2rNs9Ux9r%bml9l@=fZqczjJ z7)!9x4gZ%v7xlEXg*FBc7p4a>^9U*$rCV8GTK@zv({T=|Iyy6ByhS<{S`DMt)veBd z!(0lD=haFUS}69*5r6!dsWXr|>61E{)Uw-xiD$HU(sf#B{tu|xef>Rrr4&iV4&=k! zV6u;(C+x)abN}~K1ZyS`h^Z@nKnM`>$vz2=mm+g~{Jt(D=anX`oGPK~#ZSK0RXlNJ zfF5huBl)&l(G=B_J=eXhu6clh6GVhX5Zxtjm}fCjJeq~&?3?6!A(CtgK2@W6$Au(R z%8Na5jh=TaMqbkVf%fp_bLrjm z>d)U7LU&t5S}p8`I4;7eyDeM+zk8Q%URBb4AlI(*yoMmESOyz~#~C+}!CFvCFC}M6 z-t(q@Yn~t2&KnGi&!%O_OsUjbsfmhuz=4UPEiiowyc~Ghmx*MA2QTiE`@DZpw^y^K zt^-)rlCN4KWZPn7sK&nNU|=@A_38Rno^C!eIUnN@R$g}rb^rBc;+yF2Jcr-_AL;1^ z`G6W&R6jJY)tBVU^jJ+=28DH>$Ls!EW=pK%6Lo-q^F-_6;w#7k=r>36fkY#nhz#0* zfA<@Tc$a`Hqlh(2n8oSZiFaFic{9K?m*3C_o(+sAqT(T?QixRUrT|EN0Hx%txo=zd zKWZLnvuND-KUBNy+QD=C`%{1o4RWnDS#vl;*@?vAhTOM>$Xc5m6nQ6@tgt*bw=~nz;^!u zm53Ajk4$DbC+=BY>`T@ea8p!FZc%#cpHLh*W`=LK+!Sj7emviS4Se*}vjK?G2Oza{ ziRsmcflej4jze>Y6O__P%^it9_b6*i=dJ(eDIJ%TtWFa2`)PoB-di(-s-j{6D3M?| z|92oS*A@{B(T!&qxMjg}llTgVCZBepi9zK7V)R91ie(?8Nj%&CYF-b~LE{n-)WaL zZ#aoF>9cH-4K1j2Keg|ol2?gcfTL(75lx2}R@4Cs!crv07)icCqdyvaETpX{If8O0 zKqYfN1?E8di{lR$v)^a``lNS&HlP-2b=(oEa!oXkx5*Rk2Ynw%l;4m3hR(MSuP0~s?ezPtfz7(lfgcb)c*$BWH9 z2UxE!Ewo^mwMs&*wO!bEdL3`IEFg=_>02`J87HWcfm_+}upSRMj^QEjwG&WQ5+D`| z@~1Z=Zhn&7ncakam$H3L=hLE2vEU92#z%}vAf>IE`dxzkfu2OEku~-Zi z91(hF*q>e=Xroec37!k-%4#DXH11`b*ukCP9GZ0OYqaodA1X`0hQ$l8)L;o(w6Zvu z72XHBXHPjs_f`rLqCyQiWPE@Xh}{VQIxXd0?Tsy+$&iK!(IvQ|h~c=dv>%Av%*Amc zS9;1rWxUA-s?+KC`yG23fs2!i$5D6-U@d}bawHmOsXp2AU+OY<4|$9Qsa%ZoQgQPj zT1+ityTJ6^K%N8%)cBgT?^<8$qaRhJX3X?{XE@c2cexX((%MX0~fvhZ)#5{HRDIHbhXb^B7eG45{yQPdo;)!H@M zA~z=!%JENqbS%1X;+SbxlVay=%WooVDm3Hs(jFmO411HTGtA1u`q_U28+0~>m^qf& zN}akIh9P2pBz&8jJR@AKMD&K_NqJ&&n~jTW1RcBjT;O%UsX9N!MANhkA48tWi|zEA z+YTNmnlT5~T&^t|D|0*m8V!>^EAvu2_h4KUzv+~~pN2~MAJL4veOm&IVv4RA>IYnQ zyXETpz(&h_JFhN!T#sQfYW4Ys-7XLs6!pA@HWD*iE_dNC_d}T&2p8pry3H6*vT=Ly z(?%UF6K0c#grc_s^0^8GS;V#5QrEhdD`2HkCNoJNaB&Mlf;AejyO8m1EJGdcW?&UW zLA;tEO)roJtI_`exekSW83z*x4RK~p*Mg0v%{oB6&kJcMs=r6lpLkg~DsbnUHgC zk)*!P(J6)}<;aYXZ{OCRKA5T(z%OhEW`eWE<1>&Ksekl(p5KTAxE}C_Fxw!{*(XRY zmDLO^2w@U}yJiazCcxB^`_Bbm9Wl#=CA&2Ip+!;E(1qpl@0<)a5&UuHvV8e_R0Qxy&7XC*=#-30vH%6Z zAesCesJa!Z!u$hiTFOl=*M`)dd-`_%d;F@c#H|GH7~2v};OK;CYM^`$51I5R| zQP~fNM1+UaIetPH2Zy!%-#tuc-{q=#gM93S$TJLawX?|JfmriIGCO%uCxkpn)#T^wMZo&Hr-K3zdme2WkK1;wuK1@lJ zyRxFG$@P4_D*zs?hShvBKNXyOI``S#Ubb$VE9=zF#V|9w?^2@;LVZsN##dF)-yoh4t910I#rn;(XgUKi%zA+JJ8OGoSxNB63!VZ`L6DRd^Isw!%{-xiR8dx23 zt+ngS-tzUemZ%`)2fB!cpc4zYUtS(e5j#=6ab5rKl`X&BW}q`0!B^%*`}o33CF7^# z+VRrNmG-*21RtwzO|efnHH%aTul)6xDwK+3!O`yy8mw1_bQraoY{xR7hog1)Oaq4d zBtDOJ;%5g%*;zt9&vBH}m5Xu%@N{WA9D~{CoI>e?uc6-(kf3$SrN|)5Kl#J zM- z60w`0V)PD`Xj03hz>wCOjys3udK?raTg+5^PWD=H7tW`4FUVe|NnkIM+3X5HaN_gf zB}hz%C7NS)?BN?$Ksy?w&QADUYcjgn>`)Z=1)i092L)lL>V3?{t8X>;U>hW#O!U1% ziW2a+rqSTGn)lTL+hH@zm6ykGIfv2nnT3lo*!pz2+24Gn#ffR5)5pint+{ZLR2a5z z=OY^4E{^48h_rTxC)9(-VRvMv>?{4GfQe&hg@VB(7E@NX`(epmw$F2KF}!hT;M0oB zn%A7ROZDpvdA2BMNdXl97!_agD>`~^6Uzeq>y(-=>0DhIzr4YIYvRhsz=E`4v82 zO(pi6XDDH-Kl=MpF+4@X9T5jUi{5`Z!U0V`+P2=G#&r0t2pIiO9dkMb~gv*}uCF9jZg5GCo!JxBg znS9?`?_Li}U#hpnGbs_JBunorxBIrs85(VP`pz0v=zJl-k-7a+@wN?zlJdMnsfev2 zqg>V+*C`A6BS%9YbY65xBASG=fwk*G{`IN>GarRR;5yXU6Z4HCPZ3ON7&f&I47wD1 zFq0<%{))e&bXb%%*ib+X%3ir@tlZGud7t0%t{v@U1WYrPH3gw z67 zD85KZOEW6GcisFImO{SwZp>CY<2@pLtrHn^;D)v-%kyIJJiTgYt@LT}G_@LWVy?W*X)Kv2!;KrFe41jV0=@3@SMwWb*FTWstVl~INw zY!+%h5lSGMlmsAPXt2vO^9aX!`ax-emY+dWGh_m{o2g`XBsEV>{K)?cogKsoHk24W zYF-XLRjKd&Rvue@6r+00FAX-sKGf?~4L0{nv!$OM_od5-cox*=jgkfGQ~k}uAL9q+ zgdUD-lURd)e~FL(k)}IKaT`C=<`$^ORbwLhr4SpnhiL~?6Xj9*za-L+%Nu+!0U84 zT_l$c0O1B40k(k9%iVY;Xw!k9|H0X$)GeVRSM9>yoUYKv8Ra3e7v17x*T5NhbAMZW zm!bElM?nPMax??#@Ob+t>7))D^>sEt6-J%5rdXRRj-&wLaiJG}*|ON4Y-KzDO3lkS z%L~!hOcgL`Rhe$n-pGX!vYEU==$;U{4u?J24a--$wI-2w=P7yOk#tN<-sfEi3di?H zKRY*qi7H!9TV5W=UjmzTA2;#*ytG>!g+--j2NL?Ch%FaMye@x-kb2`Icw8U5?Pa<= z-}Vc2oHQ&=Ovoj#d7QPY;$35^iE^jeP;ib#+m<|*$Qo?^+l!|7*~L_Nh{$9MM45U? zSQ#65lphOmVI?K$(1@|>2H~J3<~;cC%!(J&ILzY=QmGf)JsM@CE88ZJ^HGr7uoIHr zl7}u{WT)0w>1EhS(2o%EI?{|doi5)!C)l2t3Oy{GG_FNi->kTh>Iqy7kb^xV&QZkb z0;oe5j5`6-s*DEI~3XYh+FNe}NLIxO@U4=0sO?17zk>HGYc+S-td*!4H= z&)fJog-oN&N)+L4;cI`J0djQs0`mc)8vMA!|NP#3Ibs%pnL*1N5onllf4Z8Bjp|vf zHVjb?IRN#?V*C+a;Fw7H)v}z=y%LN2x*vl3)nRW5RR41S_mHLx$7~kd@(HSrTr7%3 z`Ldk%1vcLEzjOAMV%Wg&xfKn0k2ThDS-|XdsFZ!b)cLdo{v}@MwhNh{S)Q#XfEY*_0{L9{mknOkfpV?m**Q@&p*8}H?5rytv-+aK9Af! zf4OBvTYh?9m0fo}tenA%-EZP`^4P9_3@{mTQ$(HlTlTYqu;jOVqd-vig2MQ)D!w0` zXgb|Sys$Yh+o5Y*uHDfvI+&E+_^HCGJYax6pyq>`gG;< zdNEAA&r8nWOsu~WZ@NxZPDFB&Ke6~7tJH1C%Lf?`% z5m}??D#9HF@F42^LK4|7NY*J;y~47_uZ#9NHiSV?(-FL!5M6sZb?LktwYhNgxuEuW zZ3`h4frg}e3~R1lJaoQ1_*|R%T>D{o!fXlt36t~O7bWfH<9qU4T3WsSw^@kRZu{^$ z&f%H@6_VSoDr|NT6fTB!pV!5N}IT zmWNXlCs!K8=M1Vd$%rQAQ`OM0AgGnypBc;K%M;+&^;-vb8!B9jo2kd8*dlPZ9iQ=j zngd4If)j$O$T0k2yib?rwyT46SA>p)-xYJWvS~9s+H;%ycyhW#DKgoF^5p4eEn4Xp z44d`xa_8f4XR+GPmP0zKD7XN+fvpG3!+#~b_BZRM?dinEba@?KEwS1a!AK?}b8KVf zzdz%kXSua-w;z1*xs>p^taNEVij!s%mJEe$`uAdByQ~eud+)Mm+mUl&hQ^Mi{ww`` zSn8gP_-YIePV}5b7fUW|0ipZf(T4%o#P&DiUZ)NjIn)L}B1vqHbVy(k9xe~^maokq z`$LKJZ_olzX@gFT@(CFw%f8_D%?FPwitQDCNl!b=OXs3`V?3J0X^AUG4Az28izXHg zwj|Mxg|TcCfUbb^u^K^;I%wz3lohUuglB7=y6KsGE~@7C{wTN%F_ppMj3vH12fWV5 zJT9kNBa`PlFO&32#E4RpjCv{j>ug;%q3Ik(kO=(~l+dHq)k`amxX&G)(iG`t_G_ z?K4!4HPxO!jD5Gz*FwcbY3BkfSr|tx)>6KIWj+6eEjNX_rZW7oTJCO|eNS*%lZGs)%SL&}5AKZq@}!^B<6ycLNF6ZsrZ6 zbKi7a9?i+68yg!-CovEJAj2>=JzH&eS}j(0zzo^*Co1>@dbZ%RLN$`@ZExPI_`Kqq zf3-{c@`rS$*SWCV2&a~(9)>L$ra&6zx2&&{`oi(xGbBy1?ee*tX0RDc-Krq; zBtk+<<@!SG-nI;FFp%v-U}9h2toaCKQK0RVqV#puxoRZVh_N9v$Q7T(rDt|J{%mVo zmcNjQNNJh(w+aQ6XivxWf0ppbJG?htAM>($$v;`@O@s(ulF=ZoVgju>@|x}I!kI`^ zbh21AOp`Qo`#a>j`UylSc!ZE5D`JL@J%KYM8)5@WMDK^CwT{!4Bf*>I@#>64q{t;V zfaamTfVHUkWc0bdUZWQLgCz4cz*1~$V?~l^|Dbug8*9YD4L9k0?fGPlWAl~Mb2xpa zyfFKU$IYoqBVoLeW6hKsWkq~y7crl+nug4eok%46DUmTx%sSP<*km1dT5p!PNWi`; z8&de)?5{E3nQvBn{i!1wjH=#bt3N4U2}oqVJngV)V$FtHi=#Q9CDy;U=_jhNF0`xT^K_MwiMB05e`0HaUqst*3ed9bn)Nh*^zl8)-FMwb z|9)6L#(7dY>wSvI+Q92Py8N1O{5{X8tADWYk9;)xN;~}lP!#_CI~09i-{ax>92P14yrgdLH)?0U;VJ%B%W;=m_?pp0a)o3;m?#K%^-WZ; zXFwMaKWqRbbx=%ium%F(0r*7O-*toa|NLa+CRHnkQ2sF_Gcg!Dz^9=hv1K$A)Mad- z<3V20a!Kt8I|Bj%bTgMHA`XAvu?k-Z1`|uDmr`VnRRr1Td@vou8!A4spFkW**%YuI zg}9r+>CYRfP8Ts@;M9p)Z2w~nL$>*~)UM)}$`Jv~8tpGnXLCG;`%L^f$c^B}PDI!( zc5cT>E1v>eg?j_%LoD%b44z`R#iU~!^S@(EwnSU{dbhs8%1Rm&xF4I8sU>pHwQ?E5qA0~&bRk*c*-P?f`mbb8F zr{ORigB1J$R!aP?y$T6HgRIvn3~#Z*Mp+fPb5=aYND3lARFUIdE@-QoI4pY|ngkD~73n{q>E38AHBq%XJqDo3Y8 z`&KU=YY#e%JxrEiojQw1dA>{x5%@!YyW!8Nh3>-G2x{)cuCXr{L}^<*Zmi!JsHM>= za1Q&GqL~77#eVzqZ-aBd32?2Z6dc)Zx=_BRj-o5HY1H)$;4 zPy$uxc0B->sH}W~j+VA-BEMHFG5TM(R44Y%)_;Dyv(mO66WMI0qw$=8dfi{1s~k#@ zdL^hV$HjSE9nPRgowl9{8YxSV5A+LOv+8xf7lABqtV|cp?@`LXh6v$@?+-I~W?(C$ z@FBFtM7JA81RQ(bZmVN&p(&~)#D2GWY+%W0wX2avW7z1uRU!ee9Z=g zf`6L&JRRx2fOcD~o-ke0+5{0$nBn-g*0Cb7>DaRRbQYfxh~$=6VO0stgYT!R_JZ_ z%Jj839W~_xw`K`gJM5RsK&)U0Jys1R7zbd?%6a{>16yIESqebs$RIupS`&IDw(pqx zEdjQP8*s)m_qo>T+WAW$O$i3H#+snp)QG`9mV%It7?e0I(GKus_O}%Y1f1#~7UeL@ ziAP0>;-T`?G>sdIkt1C277t-XQid2i`tyiX}F_}$I{a4 zGe?+#6nRSLs=(XOx!H)#&CPSX_S*%3n59oZte4%xcuC|=i28Bv)`U1Cm$#mEJX|l? zxOBX2z&dAxrzR*b#62elt}E;p%CAiJJgVy~c%v>;xR{89ZmFqD-1;a|RPK9YnYM*l ziF4{A@<ASF(8Wga-Smj?$TLiSw9oB6b!#0!P$Xd$QuM zG7gFoC3_Yi)Du#IW)nHUZ3r=UKd$bV!Jw&9unvjNd>m(W@MoJ<9Y!E8Y$E>^oH7 z&E0Q}D<*|!oiE<`&v)ZK=%erYJVC2tsIbmU3AZ|&E_rPXd}3hS_F8{DUaUU~pmk$v zxLNf$t4F(Eyp+FfG9JdwD;<(&C_Ac;z8!s9O=6W;s?cmCbohNNd^>x7LmtPjk(kg^ z#YFr8wl-Fa{oo^2UTxs}kw}2K!1U0{r~I4vRi|XQOB~&s36hUF1l$}t&0+l42rPDx zS8AhlZhSt^o{feRzettHg9z32Uv1hbzH|imxo?J1=U|@2bgTVU;@<+Em><0G>tOfm zDIL;YqJ1o%l?n=-DiI5Y1#ZM>8qDi=uDnSZ@Gmddks-ln#Kb85vLI0OBD4_ z{SZiC;Cl0BddvA1JAGc>l8eJYT7>Zl?@O#Yk1AWMM(SZCxb#4aJe9iAX?NeqA<%CM z3`Lum&T;18v6^QIoeq`wC@1&pTqM8sC|YXStC{Fc9O{=^Nknyd;~-YN(OVSC4FAkQ z0ypM73KLW|dSW4bXb~!&LEwB}_;F^=jP}L{?E3dpe?tR=Tj0h;^n1f`HW>M75fqWC z)6|3Qua6h1jOAfJ^em^OjRmpA!&JAm@iPdUg}y~?hAJ_T@;VMoq<`91kgf@NZx4J3 zdUNLHZLA1Gn8b{)T6RIwpuOy3{#W83J6uQRYHQo|3kL@$3~9B&n^?)8 zYtkFo^YgsPp}`7y;?1qn-N~|dc6seagIJ72ZXN^BjQ2+p8YoZGHC(w$KQF&<*BK67 z4ox6&!qjBAepD%a`x(CAlhMeRWiVv0owyJmP*YV++Pju-j7$le`1T}sEuQ)5wpT8c zdx1`?HZW%%_xisN{2~^6c=|^wd80B6=b&wkV$Zc&2I0&zydlPi78*}99 zWC?GqZVStrDTN2`!kEbpsz1d(&TqIsFO7FD#!_#(cU6ruv^) zbz_R!f+(D262l{;G?G)G?4@Z*+yB8z@`im$(`d9_sWBdg7(4@?d^t3i9M$-9eV84c zI@+TAwMG0gULIQg9u7h}w+-&ML-dZsR9?^9s;rO~N@6_;KbJx5-2z9skZ2MCi-Kw_ znxqPW4Q|B0rF;8U{vE(e(01EN{0inwoC;`%fK05qbUtW*uj3!Z@;6yyBhT!um67Qa zcmbwSR2IJ*kbs*_c3ZBZsTe9qb!kdwy}W<&ef{6wcDEX%Z{E=XLlJ}W@(pHevDd@( zXH&(*_;9n2fm~E$kEs;Uwh|4U3zV|2Kb_e`i@x4p50FQ%(^lJW3}%cipwly+lDY{>Jk>KX=$_~ulXK4YvJ(dLc2wOSj1+<}C-#sqodO8< zMuS>sccvdO|k5+66L z-ClQ}Rqep%^!I0TSg&)$bhWmRmA~^O92~5}?W{VJ!&Q@FboDlQ9rt0<_=z3*>F!Gm zROW-%v=#2#FDIoR(LHDe(USY<>F8uLc-|hW){=bQn-!M)iY%JN4I5{i$jxE>+WcGI zOTJtDKse{OMUE9|9f?eBRlDKNnWqd$0($so$$WGk<+T@48)FFSq&aQ7VL$pf7u%IC zy*_J~3j23EcJbVKSTXV$3R1Bl84TkvrB+(=`f@e#7I2v&B&%K7jw*EqKbogQ&VvVl zMa_B&EK7d$WaLp3CRbkMD(T^25m6F1Tat;_dZE7}HdG+$lWK+m>WYd6CkK-S-zm(u zNms0}!1(azF_cD^!HGseRh5b)?udPw4vJ4v(98XOSUk*x_1ia_!AA6zvHu9xK7GgC zh#jL4D3kMZL^B*vKz1XFQ#FU+_qXMwKe6Q_o6~YGYosUlbLR(ts?vZd|7+5fW9KII zOevid>T&7{m;~-Hs3ao$;7<57b=p5yxHvK#(ar|Olb1nLKn>$TQwnM}-cuJFLAb3U zY!Ilu*enI!oHG5cUW}1m9)B(!8|+gA0lUVom#B#78&j$X!T=KkRI|z2w9*e+QiDeh zv`{|LK|c{34w}~tNbt>db;_Q#PX0?(Z&D?>A7&{Um>9z2m2_c{NyB*j^nQB=Nn3nE zv?&hqT-R%LJ~1-vv9wU0fI?|L!}`)8&mA&`Oj%q(Q2miOP*>$st)B31BqO+MjR3{m z#TOfnD&P7NXZ>lXlqLP2Pk4mSt%W^mGTWCw-T7BSAePV*5>DWpg53cO28=3lCF}&5 znT13l2(WGfioQce#L*mVXG&Fe>j3T;6wVg)IL(Ojq$CvuksbNIx_DIlEJ)F{Iud82 zXg8EV%U@!(49s<-C_yGhrJgAQ?1m_xuV>%4z*vIbaIRb(+nWG=D?B_L9vw0dhHOB! zIXM4Vrr-^9#3drPvI@u@R%SzJrt{&M2VQ_0o@og@&j5yV4mc-WyS$|Q5r_JNJEvs0G=msLxA85 zfHsT`sKxuD&W(Im8JbpO4~^bEOQ=RC5ZJ+*V20p&d%gk2?ZGy0HA&-&ZM+9{c6(7J?U9@t*DfLT0f z+1ny@v9tuv`a_!whBUAQwfs(p%5Bnotsh%pT62tGHC1B+Dip5lV;TXeGbe z3rk|ugxBXk1FAZK1Vi95hD~*zKqr>^xH|-E3@IPQH_u4xLe8yQ*pCpJ(tc>q;>qIg z1++yC8G3<^H3YRQ11zN5udeswZkxZ5vHpUt| zy$oy}2XD_SkniOC4!Ob)s$4g>U^DFAbo}`70i%0UKZNIvdr{_z!~E=z#|^#lECF(a zT4hFb|M%4R-rUN|$e+IdIp64U*vfG(KONlAN_2ssUeo~s0Ey%PB)jv{y1EUm*1wa^ z7%q=a(KODwK4)3EW`o$Y$RCqwBTvnYh|jq^TZC-7s>LD!^(}fZ8}6|PnlwpKZ*V1C z0yX4ahIXqn=f$7Dn^p;f!d(nnK+g5TjFmncdNte0D}iJ|LJ^@P`d%qNSYeyCKYgGR zeG-ah2e+kz3RMdw)JBA~&jJh`fiAWO5)(R*tL7}(;*N&AMk5F^3t);4K(PXqyB+|8 zPMuloLN6aHnf&Js7W?f1$#^RLj4{M_D0)>+-=ggnXykZY0exlCZuxvX(Dm3pM63ny?i1i6uYt$|(EJ(=V^RfXY?@n2 zHE4Mort>LW-!#r$^!8}|muW64^*^CEh;QggN=n|oalFNOZfa4+`m3)o{ym_epa29t z4u9u`lyAclVf?f1Wrjb7iomBky?)mxRWi?Z%ku4Xrx216$l=yC6*Nxyh}tRPmxABl z4lg&iD3mrpzk`I}76T>%7(`rFpq^WQj8V?!YBpMfLZxA1N`f=&i*UQ$2w{f~a9^IT zXx{5-*^jE{GbOlaY*7F&EFIQ=`kv=AiO$_%P!LAeyJ7DJ3bfp+;^G0B5vix@eVO@K z>pB@ZWO=vlE^KO9$CJgYBu#658O0aVjdJ(>+z{)g4S3VlmtgF#@2U8REoS zjxh~m!~-;&RmRL$9qf9acP@S?^fr&orWg^9ieHMY5K2jevm5Ke_>+HQ9gMXKnc@c{ zauq9IRjAh%`n)`E^+kFBQI5x`K@rmau6nOco-qs^v3Lw|4R(P0ODY>2ax>s2>-wwjZkXn2_U_{^>z4js!TG#C{BKPt%GMyCEL zeemZebRC)R5P zoD5lde!_o1$<1ZoLDg9w06gmYbj1TGU~-8lkV(^j~r#%hwLb!NkV{E{}XQ^?Sr!qx5A+w41 zIN4pKP+hT?tEq|C?m&qMlT4ilk4E=O4)Lnc2+ zh{jgh7dVVkLh@Xmn>mJe@2|ra_+sJ&8YL*UHifkpUD-mqvl$8qDruPG(d;+y2e;?g z&+Xw5tBN>ttmv(Iu#VDLyHp_t&{n*b@c+j&>gEKCGKj&&*Pw;*}FSr_Mt=U=J8Fyl+&6T zwkpF@V2wK&TUEJMj4kiv^x0N{4RrlJN4NpMu~LIaDK_AnSRvWt3H(MeSL{if9M_Iy z*_NKIb8cKFt6}i`6=K)smkb^S5p)7JF|#Nl?x@~3tswCf6itOrDmWrYz5>c~$CJkJ z%`Oc=f47n$&wQnJE8l5T0Kv%Jw^V+GZN2okK_0h@e0BXGUl_s(mej*q8XGA7YID5a zz}R-FPA^0hPcX^>-{v2p2PRaM{ioIPWOgviJyNObONItCT(O5_2=4X3Zy)1;b$c z?BL2xwe}7=4^R2GISbTPz&b$gQS~=#RfE{*hx)}v)tY)}B>Zai1u1ZXD@ub*8g*vC zcKl!ioTculzo5qC0pTjv4`MD@YnF!pK!>u^*QHXXy0IRBA&7{4_riJLKxc|>PXt&` zY=9eMd?~(|Xc=xtpd3tG>Q7Y^+nCyPAs_GC2$7C3Eva(SZ#!x8j1{%!kh_XX<+y1X0xv%NhNWX ztj9O|Z1t7xj+T=Vp(owNl{!CWOi_}51&rO^H8|55c8wv)#>>Wlm|>E7pUFj3Tz{wq zS0*#UXkJ*Kf39dqIOH#WVTTGm0y>jUo7$ZLM_}~@Jv0L$9Kv4)17{#ZCW{85mcUCo zIJ@8L6Fw;p20;Mb5;cIJBSWJ%2rya4BSM00Vcm%3!I~^MpEHp-{koT9|7SB8TY4+9+dVe;1hvp|ICqcEGEI0oJ zveJb2sbJHQAU(u^bOzt<^4LcaW%{?nxImF0B4xKf9Exg%Mm?|!hxIkFlBsVQuSur< za=oWXpoJ$BBKsU0&^(|O+)b@o6GUs7{)tKZk&B@yXs0TY)~M?r_zsz?^+%HmWnlSW z{tE-+Bu4+gOauR)Wsj-9oxI$wmE;obQAnhTJ6Ovs796Kn9;)D)a(ikRMu~r_m;Hpk zo4?3Yyr~+$kt=Sf;&Si#QZZN-oJg5ZDB8kc^A=~O^c^%M{&4fZEZ?%yNFtl^n(rHd zb@kHOHsg^`obm-Np=DE-3mTQYtO|!t6+|=bHuaM(pX--9XWb6+REAnci#3K)GIOX% zYA%)K8;*<;wOnSirc>^DEk=H~0l<(gHJyc1qa#s5iV;+8Z=6+iI#&HQYgOduTKfAU zb$3iF&hFd2t-#3)Pp9k+p>=-N?vF#I6Tecvsb+dk8&kqEixytoFEdLlGAYs0AB z_74aSiUPIPU+M5IAwv-@94c%|KkiHG4v%^A+^q?fy^`6f^UrV^EOinE8Ed=TooDAh z?@}tD6`y`wzz(jp$YaG3-ED(aS!4KHcyN-|SY~nGw;g=&Kv`-hsgHvk8vrd4mw{E0 zFx|~1EGqCXqw}=Bf)Io#Tc$K4Fd(U}r$H_zOZ`@-05Km{8my@QYN;qUh(#f>F z!&dTSbQ#?=pL<~8;3TJ(Tf24V5bxm0RCb`vhIq!odp6QY2W!UnkbtK3FtdN;#4#}c zMpZXa^^HUNg1vid--*TAPjTrh9mVl6N1-0)OlghWe~WaYj)yk$(~^W2n~k%fbX>5O zT~wu?{kl-sq(RH0^%{xuRP_k=VCwK&)rL45bC|`)D^%sH_p^M*gWp+(ICn?B6QUK2 zmzKF}R@$LdaL(JWNFOd7w8+f36{{8xbY&=W9&tKS!CD}Q@tRHYmHpsv(^m>`NiF^D zeHFy1Dd-?oT0QMo#hek}VUwwuzcgr7wVHXw5$E>3xVEg1rBIpkq(q@ypgVPGBjOSs z+B%DBLV{Lm&04#2=^VLay!vzZRq0KfyR*r4vj)hRX#;}bHWRs3%vH3g#d7e!t_zK` zfi0<)w@Y#6<0$SSm)&j#Z=Q8*pp zvFc+Lf(p}$ls^qX>4GmmCD=*bHlJhzU9}AXsqmN{MWa>zZFILrAGaG2>!}<9qvD4$%a^(ows?_lePz#gA{aQAZZ@q zbqc2UKUxLN1NycI6%M2P>gpZ<%=6aSQvft-{Aro&f)p_6lq$GWb7)eH zAo{`^ap)i?vO|Mpqo(~B)sD?i66VRR;gI4b&j_{3##3W#E^p`)qI+jjbEUgm+M?h| z^ldq=ix@g)Kzds}A?lvV=_HM3RV)xz-*VL5aO3k6vOnv0BUty>&6S5OJtafY5>_}a zUG1qBMT?u2WmMCl$N`H*$%Rk4p%eo!T+|Ek7miG0RwTy0jOE+4%=&Len{3nKf^@H^ z!w-oQr323X!xqV>A{=QOoyjVmZFYV6tTElp>IvmE5A>S~yPI*l=j z2yx6bC~rwOfJFs9{4u+$F2D0}-jY?*CIWW#80bhcUMiW-(9N<+$InIMOCvMeHOUsGYb85VAy1;TtjuHQBAoz;rIRAZppx8J~^*rJ^$JO>+3t`q4tMG`N*gZ~#!L=hbxDP=2gicm2m69r-gb$PV@ z&bory8UG&DQU6LDxi?g7oNUGX0 zvz*0P0T@2RxKI_jxx&jDx2naiU^Pkim9wq_1sg2pbTY!hqdaoH!&~w(i*pk{zWz!* zLmfh5Hj`pj1O2*hp31yTgRxxjQgZYfmvXB?GvY2LYMIPB4v4ymm@VB}*{sW?F~1Shgq5%@1jLyMcfWk%5B)EJ-Ae-F`NxI-E?@H#xel2 zjm3ud{Jq0?`hz<2nb>nIN{GD{5oT`s+7kQ#N`b~sbVK;^c_ekzgu$dN#m2ior3hxY ztYQ%IHbD&QB+V>+?MTt(nxgm-Ro`$Rho@v^3Ac{-n2y53EGJItb!dFIA4|rA7Lmx4 z9wtrIsCHYXu{7o^X1$_jcn~W{JsSZdoU?PJ^eLA~5MgK?sZYK?Nu=qp51Oy5X8ER{XK@0&g3BkC3lx&6B?FsX@$x z{GHN1+;lXr_bfY-4u**{EF>amK^|cN#jL<1{l%TisM|o{*G$NGT5LIm`#heM*@Ory zt_|_X>>2<5HYR@p@A&}L4baD3fZ*{9x`_80;=l7qbYzTQuer37mu+X0Lm{w2j8W{r z#(06rqwjir6ecP>$2ilxw^>-vn#nLkqD4TT@T(i;nr;HP%rKo3JTqs(&&HFCup4t@ z^D|9$cVzWk|77MRT1`@cK5*8aS5y=Gap#HJGLq90n7UBJhiZm~^k$Cgrc+PXC~nMU zGFOt3ciD&mbbVP#K{t|HAN@5K><+ZPe|a*r)6sZ?0UZtpMEz;KIE~qkV!Tw1oyvoZ z#{-KrRzD~YgPb%xOrke`7PXKS{xX0g)KKKubh zR*&U;hM_J=+9(n581Z4$AR)ces=`LxHVctF72KEw;rWeoIlRAht?*80QjR7vm@xGW zfYT)i(>(fvO#yWISdvX8$!Ri#}+HRP0MX~XB#De^kShW$rW+(m5exJA_G#fw2dP`=rz z4yyg%5I+`L^B;i!(UJBKd6032iB5-q7WluIMe_e&X0e%-Dk!d@UagrRNv6MoaTAeT zHoQ|hZAZOIiDe1v5eU&>`9Iev|G3&od=%B@h|&bE@lcm&FI*;Xos}tqxD&Fn z_U(<*+u?Svw?kDdNh2x}dq$dJQLGeq9}+ZX@L!Z?^tvlG>WT$w?!Bs6RFrN3wdwAf z#8pz{y=1@nz03hpl&rV)*bE90YHqL)P|6I}?%dUvULC|H-UK^8|}9 z)>TewR?RdIM4Nc2_*<_I_lX{Pcq{!f4zy%mij4aHn26~`TNFqq8ithc^$=7le4E0U zr%=gssONrTny2L%$C$QTz9u&~e>6`=r;Qmkmrc5f`Y9J`C;|m`Vz@8#5NCeWj7qOB zn~a0^e5_!q#Vg|R8fHaKoD;Qo2VKtKpT@pd+SauhRKF=Dd7?E0DALG5{{VmA7=L`? zSVRROZ!!XTl=c)kXi`GR2@1#^HPEbYSPAlvwMFGlP*$ASf$`O8O$FAo5q=R&k1(bm z^qMaD`WfpmN3~v^F$H|MCdIR;VW@wN$g7MojA32uagP~OD+}$79JBU{fbhSou+%x@ zlzz@fPF1dpXGh|sf=}DXCq`yCClX}2nz`aQ&~~ON$K-US5jBspLnOTxV_5T0N*{6@ zWvUX?)J~VEMyN&6tFy1J@3P|Rh1i6WngVynd`_);t=;!1n9(-cJWW*5JQexjjiopC6ZxcsDd zus%&sMo~`Mq#8gL#w?gAuJEwDlG=6J?Rr`VEiUBSlx$IQjPi!=&%Pxwt7<9@egC2A zQro%(63_88aIsZ|7cb#v01n6KaeX?I$IpgP^)nr51byJf=S)5%xi<2FhfR4PX}`ICJP7Pcj?lOi10LFJu%}V- z0=uat0l)vdIgwcWC^U6;m;S55+-JR!Va>e8@qc$L#V$YEm6#9z?o+Bge>5peul`-E zMx*~<3_xw0uVMXJfq4Tim`@`I z)=b{)JPf-m?nG^NcFc(%0b;W~H(HfR6LqjmiMZhZxHxj7O_Kzaa)G8G>NvC+!QJ2R z1u>gwN~(vmn7vRWjJtpHNc`bzRPj;-tC33-y#R*-B^$G>52;SV%w{0dQr@AhUq->VnZDWr1A;SsmKqzECr|<@jxd1{% zb05`dJG>nd(6hkMsBSl=?54sIb4NA(D3G*%?VzkA>fLztjI>3dcmYH%>(m&x8i>)C zRN)bU1PKoh4caAn`)_0{=gAf=C+D|O8=7IV-~Q6`dUsK>u$CajLgTD^X+?xQ$*iU{ za+}?7x956!bekIG3XIlM04b^a)RCa#oO>~`&?929N@FcsZde~B=+?RGuv8i`5z?t6 z08Z})<#(m(l0Vp|GTZf$Ax5C5X-hy}0mwM3usQGFaQsiQP;D>#u@0}418%f9{LWEJ(!Bp;ImP-T z@-X3r`pA_RJV76-DXJA~47tg@CV(QjnbDuOY4kVtNS^4&<_HWXW;OV=v~2-L?UDkp zUz&x(coR-4r=rh>2a>LV;4M3phemy}Cbhz|68iYV&Q6x>%AK>*092$cH#KW>4w~}y zM}hdEY;=?K0#7%`cNf=C#;l^iG3=^dxuIX|M^YPf*)4H$V^AcmY~Y%ok|>e+;n&_~uK(ey%4 zFaC42z4|SOQ%A@4isp2&k@vXA;hdZp{mJCs$i7XLzQ~KIYlL4o+rn(bOV1`m^oGT9 z2kW^bfC3ex2LWdreg1Ra?7DkRsf{a2%oob~L7quzrG)Jq@oHvJeF0L;0-JIMMH-?T zE*`_1LiBcN&9VPo5Rt%>lz4uNIcR>&vqUQ9pun<>ibW%s6Ssgt!e55#~8)lRY$d>$LK}Jd0eHA;YV~Q z+~Ce${G-$~vJ4=G>NBO)C}b$;?SD+8T=zFtP{}iHNr9g330#|g#?@x?`}f$w_}%dS zs6i$`OebBCLGj-zdJtwS1VSulv6zq`rqwX@wkcJZCf?3UteNnP%PY!HcOc!&0>=_M z{{vE_i7(C*!gz_sA5%Ze!LcKKYGwKMi_9OWrD-wDd;N&ZoH2CpAL_Z`c+C?gJ_(P} zjYK)sA;g-Hp2gu_y=}QZ#gfd%x&JJDt-p;%XLt@rrGOj#JyLfOy?Zt^ULAFprlGJamIfxk1|{xBs)G%-$a!vjo=z z@3lGbEwjv;qJBWk`KhehUl3aFiU&*O+wFGhb=l=&vRwGh-tm`D!X^mmP`z=*foqnlI z6hOt(N5SI?xugNUt(|o8e^#C(C`E7~S9v%h`S6W~i2doH)r~rP!hKzBLIHxi_Vz4%Kg1sIh?7x)83Dh^LjN?o# z+Vgjw9$Sps&pa8+WP`Iw_EAoRVvCWkhvN@|D==lZR_xTs&E*P)MFfmwE; zB*s;d!->73VSd=PP3dvjO^?L>PKjZmaGLo4e8%|8p6+>y?_A}u);a5D@L2K2h#@c0 z_9-bIiI~?>iiIM`M3*o|Nn!FLsoAEcZ%WwOWXoj90q{@_llRErHHMBrGrDEXqCT~V zs99Gl1U1%2na3;*lTE?$A~$8~jb=79c5vXoadDoblaBX@3G{&VGH(;2?@tD|joq$U z65cMGVcxC}3-O3yXJpib-FHM+6qOt@!V?c06f0W$xU(ZV-U-`RjF>i=rlE z|4ZRaQydc(11L&nQ<`lyGjU8pbK|{K(=7zvBqsBbzNSHC6Kw7)ZIC9y8do`~o8bj_ zGSV7#Blb6g(j6q-q=})}Zxb#(jXfx6t;z=E_5b9ajO)b;R_{BmzU_zB`+2}HrckGo z?q#DcFBhWt)()_Dc zgJoX+$%DGX68hz0uTz*Cx*o$~xS8e*cUC_|+IoPM@j?F~|H=PUwI5-1py;`rg6^-{ zPVgUouJa0v;CzL}rn&N5t zp4a5x78kn8za9SK`oe#0NB|$s_k%B#9*0Lu3*fEyo&BX19c0fuP?37FhU>VdS-#B% z4v1O2q0dEy@g=-bf}F8EN)-2WSTQ~+Up;xDsVD0Tinx3ME~x$33u=x2&JROD|5v$= zy)~X3KMDT!Ahpi>!~!wHIQP@#^y@G>F({?{{F1%Ly>J4Ll$n_}ZQbrLlG3yt)qTX}j%69pk=5>DAoogzCkgtqq#@-VN^}Ztsv}e+< z^18!KJ_$H5+Mqsl!_N2_O0-RmOy{fI%9@xh6mC3C6Hq6k3aQsuImOt`=2FfS%~~=E zVo%9djNt5q;HdI&>4F~Q`z9nK>+d(4hzzQd6wV3?lp=|O0+ANSs zt~P*q*j1a?1aDqqulW+w%~b-J6J`cMh^wO3_^Oz0+2elDOTRQ_L!CJ^6En0M!ypqA$C+BW;*YXT9L-M~nvMKqnbc0sQJIw^ch zBIE>^r1!luEVlo$zv?{t_mJpIPS^UnSA0F_eOV2L`_#B+6xs%>R?J}!jP2cL-ek)r zpRl6kgNXGFwLKCQQ+CDoI!hy&+ddfHo#s?1CT3iT!J6bWp@#k*lVs=g>aLl-nGlfxCi+}J zK1dpV1<}NPo%JJP;8iI*JvH;fn>2y=Z6k*bU8%2#cv6igam_5Ek?viO`y-&SM@Z_%iW^1oY zuo6+?en1#8Tyt!Fa4JO#7A9leewX#QDIY&%Xz88PiE8-7IqsgKSo~ph+(p}aE%>^A z1O0v^i0vB#4t3GKci`=pBs(BzrLHVH@5{-n{HEw^{ehUw&$umXpW-0iE$ih3wLO6_ z@3!E$y{-YDy^;vtAS@wYr|0%HWreI8bcHMzJFr!iqJ_^rc397{kT`A2Qj-I)x5xO> zpHUseahVtH7)I_!Im>^7@@_QAz%1bJi~rgxyRdST2FJu}=qHM94)znxqgvmVi^QfHidtl5`L3xvwYSldbnOd*f|uBg5Bq{$DLX^dJzu8 zy^k`t7*j_qrqF)9dUA!$Q|oJ-wsB;3uiMraH;ZwywtuMcz*J73+g5_9+>fZHJw7&i z>FxgV?I&K@`=*r7<>~Lc6xN{yv{zP$<`l%Fn5L!CQvV*vjt_MDN#WD)j~x0LjC;uT z=RxI;_ueFE2kIBk8Y;f%(OP1RqZZK?!8bA4T%fj@Ysn;ie=l$VF3K#*O0V6zpSAj>u=K|EfeZS-BkP3oV(7O z^RfgHkm*AXv)F187DF8A6IaU=f5vnbh<@0n2o?k$iDIZ)1wC-guxaQ5dA7fCn|n8> z;T8HcRtuUf-}_g)1LRxL4&!hpTGcr4YGQg6+{5sJldb?%I9L+8&7inTDIRR_aoq~; z&PJ}j6HZ@klL1^9mI4S_2Nd;fhMz@Dxu7V5tPD+G?%O3(MRiZ$#PZFDS%>&HKey09 zcqLI^piOsGJK(c@_I_ipL-ekjS+7z)%^kP*&z;_b=rRFyr~H1x-u`cJiIGT5|SPEY;eou+DY;%FGnCaY>ccN%o>A`T(4hP8S>OAJLRZW*^*Zr;=q4ABc;{QAo znmF+)-ac$&ZtrkpN$92JWfiy5;BzNEC{e)#TK(GC!T)^AP5y!gIl^cn^lNiG|lYMUedC=q3ojTWuYp0z3{JZHfsUvp@_u`UXAI^;em-6Rt*orgMObX>wW`ZIOrb8 zGBh?lH|N*(qIhj%>ZxVzKBJpKfB*SUF`3}M$okqeO&iaI{uH%)RH-Iw3qO=gR#XkI zIX|zxn7m0V6wV;ySZeWmaGDy{?~@vXLovXxiwf$dx#mzzI`JL=v1%9k%<7eu*B(rT zmEic9g3w3iR)t&@yrEARb}Z3n4F`$PrQo-jxoL*0T9Uc%bJ%`ukZY2Mp403qRV&VHUIHPWs^|_*xyri4Vwh{yIFD{-OTkT#vtNp9Xvse2G zibo7CWt%!}bqV|3?(^5)t?lOLpLG7(5&Cmn)eSSo6_L^}KwEDke2ZVZ%OS_ z?K(#SJ=HG$ODLSse2)L?qzA%_3s$x{fZRM#0m-WPbcVWpb?bB!Sb1G9Cu=6q#m&K5gBS9N|=(PanKQN4g*z0o4YEnPIoOA(Na)*pKXn2za-(wyCBT`Oz@TdFV z3D9iwZf)Ni;G|diUAqy!7`8V9*JZT!Z5s~omX4#8E@0)ZN7E1Z#;$$w@>G1(^`P#_ z67B9GnGQ8>lGWoC*obT+pCIkQL7P}dd4t>Vg^>EA|4LxpZgteA7x$=pT7v5} zJNGjc;a0K~R{m~th873rj0s9W6d~0-Hj<6#_+l|#UNDSWOHe%>6rGrEB@^s0E)JIT z4@TVeacJOvEX+BS?Z_zUK;!I@jJbVuGgTOI1l>k+$FvZ+bHCJjvfo4w%-y|$J*$t; zCr02m3yA@nwE2tg!iKYK5dOBQo7?R^%GGs6t!{VsVth?V3{KIkOpVKP%=}&nm9get zUD%dA=Z-zPnskPAF0dvHD04s zma2dFxEw(2|8VC!=;7p{+SdC@<^kuF&0Fr`GoCz*53qRIaBujV#ag3%Z~Pom9ADNe zr%BUiH+d7M!>*q^4Sw}PW9{@#xl;}9E9Yq>2ilF)WP!JJ?6}XcDH50ydoYxeO<+C| z3vV<#i6!PPrf1}d8{yWgyZiF>HBMIs`Ct5(MMu=_XJb#8sh9Eou1=!oUF z%Y7JKRs-^@L;3op**w1L6={sD`)hgC=Fb`I7!ug}4Cf!`W~8NHQi-lUK14UVR|-T( zyodliirNeFRD(UROv7-9*XYHYv#KYC>p(k&bGzvET=k~Lqv@$fa&mLi>d>r>yM>{< zJk3hEIp$$uh8#b1(A?)sNuPC51&pqTyI<@P>hjN{%ukpni&r}jrV3W)T}*e&ZQGGj zbo~78?8g^s_3j^g_BQx=@Sel7A%_#pjASkut*FzL=0|tya|wzJ)*b|r{@c1 zo>2Y#=?USW!^hNY?)#fPo~!WhAC;J^uQ1yiv$@OVA2MSxaP!nuFEoMZ;gijr2*3FLjed{4 zCzsYs**Lp!qE|)Hu02;ZE*YR6O)ueEAE<_nzG*~vN5a+Nq7@ij!txXX9zrUyjKID zcD#Fz!j*iJ#Ia0Khdk^sz>=K_S>QqCq9RxMG;-sU2xRr|KK}c|p(QWZ(9b+g6cNO` z=4UA(o))v10&~mx9f6g^wK=7_7L}qb^gfG7;;p*ho|*aJ(R-~E7X}@&lGaiRNjq*88OfWzX!N#ZRN%rVv^s4&ozUr^83v}N5-MnoeP>B>Xd zR75#;by0aZM>p#R2f-U;3cyx5^_;>VB6uC}fPSh-%HEEq#Tl zpf7=62tSv&4C(S2V3~~Z0U6ozb&NYy24~(Fw4hH;+^%noK{|L6Z___ioOnhYUFd<6 z%$08V+#}wG34OI8^!z3xGkc=o*h-SB@q7DE9jP(s^r-%xsHGkgkbdRrIb!5$0)0L# zw0B@MJ)zH5a}iDT&JWAZjao15J*2^144K~s3wWgNws0_N#SV|1o7Hto1b7VSrhRtM zBpn29p)3Asuk*+8&O2{Xgxeu%vprgO8tpT3a(p}Py~CNG+4J4bF3QUA&F z3A1(P8=U{h+>WX{_I&Z7wXp2esHP!Z=a)Td@qUT`kIlo2KWcc~dUOOYrZto421Y`x zm%yG^V)&dd4;FKHi+NTJ6#hp%nS~X-j21!TgR*7L#j#NEDG9wL{`*cxPs%1~1L*@Q z&cYa!G&M^58wi(6kndA#n`6HGEgZJ@Vttj*8+w*@Ip4vqC*S;qCH+f%(7n*7=1SVZ zbUe4mynwc+7gudgPOx;T!Zh1|CPw-cz^v)P#7pG~El0ZF>{;!hTe+-gySzNE+rRDY z&z(^(cHYEJ4P@rV*=T6G=b|EWvog?o4-4fot*3J|7e|zuOSfV#*=?3|DRu2z9(d=n z(h2pQ{-z!=QHJDpStZ%0*q82_)_SvdDkMC`+>_m3-}-!C!Ug*LF7B9NM(bF@pNRt< z3rDv2&fc*qUD%pjw0sDlVYAf$|J^A31HiVKU$U#&zsLPB^P-&e@u-Wbe#M~N({C5c zmp^}8Ayt2l9u83zg zUcuL%AZcOO6x4yZsPx#DYr8*W{*8&ySVi9;p72Sl=vSe;6VnSUI(r%T%`D53IcYKBYcVfqu!h2eR?c%_zf3#&&8V+CT2!5l~r43phXn=ix zn)kkibFoGJ_4c5IJ&nFq)3EJ`76PV?NTv~$9O*!mQEjQtE*>$nPX~%! zF818I;e61&;U86NzVA3vJMaeGZ#-svy-(W z@^D~On0m+(quzgLjb?|}I^Wf5K93G3S*E#TVCHU^t3)IEbs6ipLA*?V2Q#5pa^3$3 zTbhoK>$5tsT8ZJ!<%T3>*vf%4v!dw7F|Ey^_vugXPW0Drw#qZtjGXW-UOq$56#*IT zFX&{niWg+Ac0nO<`EKEWrcJb9OQK2U>LY&(3;s$yDVp(FUFzR8UWaZeS(Lf<%g2?CHEMa$7Ec&kb1mYgd=E{?Qoq zkAYUt4xh|R&rSI3KbtmD)iq+64h?=%*6{n7sfcOB$M&qBe;Bq$!&dx4r2_`A zt})d!_oFc8VdvDtC~HE&?pc3PTzudvzX@3HCKYxZv1R5pt9gnrHlLj8LT__hDn}hG z=ySYD=v?Gf2#r<*hj*Cgri8&fEi5q**`&OyHtKg}Y_`UG2L1V02r)fE(IRyq69&g- zI(Nn8SwwV^o+_Jnq{I)Q3*_Umw=|ABkubBQ-cytBJ) zlhZal79Jyc%#|u>jHf3c<{K9hKbc$*hZ3wX`PsUp&TyNL2aB3Y9w=BAQ$+8PycxVY zh4D+n^+R)AK4Zj%*lXy3)1yrOIfjo7;El5SW|fevyXlNz^9NPM3v-{g1cfHYw%FX7 zn=QhDQoG;O-M7RtMpWL|!^-ywMQoK54wBd)&J z>o4FL+fAcqr(-v0j9Q0`_}- zvG}xz@bQP=0F^vX8ehscCR4v}cJkIPkZ(wzxsV6adJ3~NfYmnjqK6Gy5FVnr@x9>6 zSQ=mRnvB~=idXr$87QWzRfx=iJc=m&mOfh)N;8{9#}U=&FU@^aEPMd<=vm^gG6vtH zGUv{-)!najBCMlvX16K5++=h4{hM8>*$FM6^6=_bU2xVZ;(e~U>B>v`<=3@ncDgPo zGhv7PPEs;hgy+?=J#WTfEIvHY&$!lrVmk6ZdL);}*Dw}ykEXj%E635tUTaBRuDH%qH^Xd&`7v^v> z>Uz91r)o0O#-q6*%v-@af)hQd6es!`En1fwD!MV5wed;GEu}}0k+^~i=y#cye*@ZZ zZfc3wJSb{|cv{vaWL-bw9k7D)OEI^jecbtcSJK%n{Y2-y$M*JXA+={6R)zZyB3qBY zet6|~`M;)xjN|ykxBE5{a(dN420hbNhpcz2C8shDVFsq#w+%Kv(XhL1<$JdPtr9!p z-JlnGp_4Z8H6e3UJaFj6OXzQEJB1s1Z<4%K$rsyXo_OEt^}JEe$sZftf|z4XKhuB( z@s4hzn_%9wGBF>FJ(D9y9|TB0NheckG6i_eGarzv4g0F7>9b6Jno~))1!%&Vj?bUR z)dN(Cm*`Kpj$ruPiO5+Dh)UcaSV_~GLzkt);52hi<0SP@5tE=!tnE~So0@N#4A20FsZ=hK?OkL{chUO0Xbw-^@0}3 z&RQ_a!}g)@>p;jI-MVmWIJpOx8?hN|&EGBdA4f^gvSA}vt2m09g}R$<+n5FTSTMV^ z%?Unnycx&0Vomc}T^gI41Q^b+50&>l`dc^$rxfpqpUx8(dI}!v4x<`5db~JJY!ziC z#b;8le~=RWW6*UH;Ibnm72@k6scvrk^N zKUPcxW8?0gc>Z(g`G7q;t;>U#)tq^EPalG!3qhRae&eL)ioPdKBg$h_27Y^wSJiF3 zK5#o2sekJtGap)pl`3CIKFyDL)@=p7T^sN{WwcB5z$@63Mw|NxJmYlqm!Y1 z@7$@Dq1O?-&v$vU-3k+F&7^6Rj5~8QY(6M5w3cA;JpmGH4&-VQ6GE{!Q z2On;CEm!(w_HiAp6r9wuSJfLa$KU%5&yj8 zv^|w`Cm0jHMT=%|5BjOS`l{75?I_`sH0*qw(cecbgzvlH^CMAJ%S>Y3cXKzi9`TZo191=if{>Vu&MK^ES4riL2(&EVGd??OdAk8VN~_p_dh! zm-D0PTrKXbMN@`{E|M>$Qa<9#y3iRVEY<+A_PO!kK6u1nh>7>V;VFhI!)L zlESv;%?TNr+L4=^7&50K3oI4N{Cd<|@nB>8Qw>pXG^HBjjU19W`%Tq1JCeh^<;B-x z;Gk3XMJwrEUeLvMlQKn6sKeanVD6V@x6Lym1=BE&fx*vva}H14zyI*}mlGc85sv-) zgMS*B@d|?m_on86o?iG+eDUJJE%EsY<^3s}Yj;!i3m`_G+AoYovqP_=ltfP>%%&6LnL z%;hl0iJAzxdhTb}$2W~X`CDeKZckMH1k(lVCLZ;9&ef&Agpul=Wx3OpJprdWFs{p( zw6KtyE!m*ogZ7(wFOKzKn#$MEsa zB@f`(LVm_X8Di}j6_O&5W5!6VI?--1ragQH1$&G(?Yx&$v zM&tXFlGuv&w9(x&Wu}qe_|`avci?)On_SSc!|%Ez)Lm zDu6iQ@@?h@%b!~DY3OBv-qQMRfy@FlPq`ITyHK4`y*y^&Gtx?Hu8FXE5Eh8)FQrLq zOnI<8O(E|MuUF=hLi!}&VLST@&&|?FA4Xq5NSoTm5iJWf@if&a%Yhat$$16PwuNii)vfy7>kV3ma5K#jwdtHh zV=6VY`(!4FM$ml!_qD&a_5ZOi;rh1>y0^IB?ug6Jd6>r;cUlQTW&`EXdt=b(2_54W z-s-C-DuTo3ntorPJ1_3h#p&gWQr4I%ZfciIG2srvt;z@?|!#3wT?9Ib>7&h zgJe*hB(gIt%#2>Ec}nIB>Z5hPN!wHJ2I}OOCZ0hf@8|sBX>Pwr>kyNPH73%XG^iE3 zB30N|JPE>F2aiN7Pp0r1BGxC_xD3=bQOA)sW?pAfku6aLth^`FHT3}zf8hmZ6S2o9 z1J>R4wBlRT)M6>Vm;UC$+B$)G;_P6|2#&bT=dqE3dIB2Am{I}YqXHw%`C1;)zPLzq z3o-v|mpMSK__?B@+thcrnX~aOaN_$mnRkKFYhH6SIf-aXuh5IB%*{30pA7LufAnrg z1z!)Zf1d@6xQVIanlcH{NCZKm04)M-&g83F;zWfDh1wM?bX+r7DxkTL6CL9BheU9v*EW++41qGwz)FTv%Exzyy|lQ-aoE#A2(b(Eq^okujEtm%`UEw z?wRSn+~>7&`?``&0tDMXonEtx@lZ#9VX)22YIe>w)|6^hqbL}`F$MUsf{wso2J?Us zlfh}O(WvQ-VLiVPAyKi43X8LqGim+)>8N;T0=9Z3`Q`{TxBo?8nTmBxe}2Hz#GT1M zTfVT;ugJ0pMsm~QIz4R~>=}wVBLAEQsf`WoyDNO=1}5%qyMnA6?zX-bR2PUyt+ngB zeM33X^ZVj)X)`;{PiDB#fr594TASkhJ9D272k9otQ&Y$ooX5>XrD_r}K(%_>XRWD?TWFrv}MGTLn!_O`C|Oj^a?ha*dZqx(R{ zKiV`9@ywgUvkVN5(Pis%nI|dYY8D$gmpcyI%@*x}F5U;I_%fbS%`6!oK+0^Z?CCDK zsq6Lgfw$QkYRP&Uk+BEOz5CBKWxIvG7Hr62nnD8URa`Y|t6A^04pq(cX83iW;F zCC`*t>McIg78JXz1YwWp?4*qno$A^SU~zT-oRa7|eB`&Okb^sp^_q7aJKVng^|g*Q z^Bt(eZ$mB`{(XJ?H*6um8F%A3hjH;+a2B1x|&N(tOF0R0UDH8-qLt{a#7Y{ zVD*nI#wp_;^ty+7aE-KH>20Fx~`mv)c^!I)|$!2Zk*s8oPGf9n^1n-(0uxt=6zI?BNko?B7_8<*)H4NgXLY zyrWdB%;^E8CKJ!qAOc)ozd{rk{`vKV%jw(Ge)T#Wfc@k5_;(I>4n08sW_U0u>e7|# zrRGmU4ZOCD{)7WP&A6~*o8$8S^TgH3KjnWusZ!#4hwK@1fgn03ZJf4V?H!IFwd3lE zd8zI1dtZh98ZRlEym6XJA9|$**EMRnr)j;R-4gdmN1RaB`K~?FyNRVKgE5)c)exh5 zn|gzQSLR$uLYI{b>XURo7(G4VD?8gH>1)SXt;glb3B z*3R{&Y~oKMaxJ0Qf`5I4SbX>!(USg?MyS<*dBfM>n_62J8fFv@Vi;SfR=4SD612HN z|I;Louse1e)9<^}cOC_``+AI~R5)p>dvb}kv%z2c=?xnXW4XR7{pV6nKOnj;Y)ztU zT(8eIrsA@=24#=%%!cx3dti**8o)2~XJ!`ux(_ynDYqO_)@EEHpSkLT?uS?2X0LeYs7IU36JUvB8XV=xj!=DX~sA^*BYMy;iQhKH|Sr-OuREKDO6q zmx0A*N3PZ7t)I!bz4YM6O=FooN9~sPYqfj# z=pysRWM_MrdQ?#mLNrb-%qQb^QaPR*P7|2R6&K(^QSjaRjt zmda_JS_x^7^=qrtik(yyr&Y8^6)lM!6??DH)+tqDwzb7LsaZIUGrjL;6)!2t?KGfFSA)b35tUXIZmT zhp&B_fB)I&fx;$N*=}HWa`z|4&IpH>V`{h{^%Y-yHXMKueJ|d~rQsy{W$=c+;_Fkz ze*Mf}HNP|h9kE;Ap6%6PIc7KTEyu;n=wP+Xroe+TK%aB>KJe#V{a24T>VKj;Rz^rr z3oJg`e}&|z+FI|K3QDcO3#XC914n{=zo;axqZjwRNOx??(yaEYRDiWe%ju@DQIC-) zJVkW2?mj#{D%ls-a>HmgnbQz=dayI3fNk57BI<3W$u_EHjeb=MsE#+3EZ^>unYb-j zLTLQuga(_d4kbeSAN`<-hPw7-*2tp0gKW~GZIklwk9~u6^ibDf8C1*@xnFxI6J{eJ zY#5AD9n#iD_o<`m#(zVsQ&YX))F_oO3Y!X?)lc8$m*Wvs4pH?+f`myYSGz!9<%+bS z7{{xiZ*{y)(|t21D|DCLg5mk6_^sFmDXO1^pr)0kY~H{vnz?ah zzMHX^nYx42xt^(XX-MT#>cE3FiK%?=^|Rpe)8zz2;T^P(>y7RD?AFMP;`Pk%rXNjC zzp3uF|1;j3@~e-WEgLRT9*V!1ap$E1o!rUaXh=H*#Po+X{K@4BBADV zj7MPVxNoJ$r2pI1;V&cG5fzyAPi9q?gv8hev?h<&5w(e_@FnrCXV`e+{{MuQ-&b^+ zS)v?s6!Rt;N=Tx9bX=A%T-gxPO|w$-+hA$ zT-TSWd#!V#m2TuAc9a{LzRYnh#QF*+w2pPVNxxGySS-}yV6g4uEH9^DgkZqu(u(K< zj%wLsf*=nZ+mqznYS8zm)}*qA4L`Kiu&77785nV7e^dY6WK^L2(%)>)Qrg@L{Jmt; z$bNgb?)!VSzAF_5+$GMJxloA6-|wbOJ(=rWxUb8hY6GzqakeGajiC@Z(X2MLRezZS z^K@koC;hFtSGChEvxjeglN)UBx3=ePERVI2?6;UXKA*;*#=d_LGj+8K**ao!aaI}0 zB=~zGmRCd7PgP#`tQMdB`ZuI_A@?bwK=W4~Ti51+=JO}E0ceeK<7*=K{J`N4xi1LLW8UiHmy zX$;eKmCCc_g}Y7qvm(tsZwu1sP{6Sc=`n7o?kmkTDlNi0pZy3}w>dIy4mk)9n8M7M ztBLs7_t(~^x>+elu4LgI)6}cu;sj#FT?*WrA~%=@ZL`md$i1RY)-v!*fqDcUX$xvN zskR9N%q)3x!{_ISi+N>03%?(NOC^ok$R=L+7iIT2G!@@M#-Y%r0*He{`+X%bta2 zn2AOp<$Ju9^ViI^nwlte=fLDCle@mEK2HE6dgV!zzSx%GCb_xEeLI%5_b6xWTjbY~ z*X&=%1|M-@R?|qD!F%av9iKyoke)8e-_tMdr!_ik73_cU_uMbTQW{LEtW&;nx1i=P%jEWO)Iv^zEOmmcfzE!M zp>jZDfI|(Dq!*soK3xV&jFTjx6gdOVW51q3->(6WprCQvi~Jm_*r4_2dn?%YNc(Hc>4x`o{iCX`Nz}`;+8$8LiEesG72*zk zDE_nUW{PD2F;Uzxz8roP}1AT!o{_T}Oo z7l+d;ePb=h;H1Q!=E}2J8mg7m1o$KT&su@-1aai*vf(!_PQ#Kr;f4hSBh@22@EgUDU8u zSlSgvEv?HM@@dRh+(MoGe1$a$Xs5~aq^U1A)^-2r?XqU;HC@+VuQWC##&dSu@EOJ- z>BjZ5f@=}plmUd9VIWm&H5D-FA4URXiZQ8TJws|P{1^o-4lNa!B)EE{H;(;UJ#qEN zY%u#R{+mfdg#C6Z>w`zzR553+l!n<;R=~8{jFFm%mpv|3uJOy06%^uh9lTl*h#EYw zZ96;~k(@cAv39IXU|Ys98b9;)R?kjNg+0g@y*o_cfEY_fueO~0DgiYeZS0YLg;Yn^ z`&svI(tAq|jLkdV&h*&a1{c3SPR3CsDaUU)U9E8@sGw`@9Vz&;jzHS<*5mV}=84}f z|K4|jy{h|vZz#2yV9~S(M1e1lvns8UbO+3ATWg-0U%cBB7aG;t{~*dU>Xj6?eAfXz z-9VR+b=&QE&frmv_5Y9c+c2&^L7ly9-2al&d>oddyp>Y#G&KZZG*yzEIhUew;<-MaSIO)^B$=W;?*-(|%oP@`c=oI-M z5Y+%izHFr%owuQ9GzPTS&(Pl59{W~Uv7`ZxH>aVS_l6A7mC_Q^%0b3z$ZFVIxYhH{en4;6gC{AsQACZ06K)c86 zdb8@>@Mni-fGhE!^!2nF=Z%u-Os`q6tj_OlZ$HR=x_<9`WngH0=UXVQ>2mL%p6xA` z!~#F^qT#uVM*Z*F2JRWe7LhwlH=KM@&q>q~O^Q>@P6kx+_fI#FRlU+`W3Fmrp&)qK z{>)KLdVrt)%W;#>d!@wdPyP&;e?}ij9b=vY4rtO$ReQ0x{^s?RKy$L;eM>@b zpfSAT*D1;e)cXJH%fDZsLiGAggDfPGy=xB6tY)m~0+=itciO8$k=*^>fV z!(_9%yO(ef=>V8<*H;lP!MIe_8d~2PTzj%%KT>O|soW%9xpG6^b3eIpU5XH6?kS+k zwXQab^w4IX4EEICT6HQb?GXb>s}Og+Tu?)7=^sgTH?6>up-!xaO6rdu6UW13lrE{r zcs7wQ+A}lRk=JFw)`(Ywnc`fibus7_IFl4N7n_Bzh}tA&Q0-?z*&D}sI3*?ri(ign zABSthpvk+D9wHA1g(O8!?}uhIZHvkfU?PEosqK0OPPmN-#g|_GbBm#)@|t+?8EzB%*2NT z$D=d9mm18Tt^89i+6Xjwr*&dKYoPdKK&`pyR|fw`LbboS^U=Grd?RUi9ej5PJ*qd` z9D*}-v)f>P3@O%C-^c|L-CdMyarssLvpW5~z=@F2E!~0XS{G{0(0eTN??9V=-vWQa z^TX#j=s`q(gyKXKVa8gn`H;4$2ft(r3(ce(eQbNyt(;5#-QEE4m=3OSvlI%odKQp> zQw9YN<>UuZEosuf7pr`ZbD%#$r1riOP2hoKr@U(L3EDWE85_+JVSESxNDIgq zy7^X-YjFk57q#&5?)Kfs*nmL62o3jdzxsZu+4Sj5(jR;cKF(h7*59@q61*}^rG0)k z-yTjm@^$`i&i3gHoa!xYtUJ`eq9Tt+fyo@m@;yV%gg9WWfwfBWn?3y*{HD71R=`lL z>dw1I-lwsz6@wPiQs5zGcg?G_`D9j_o;c1@`TfT!$TG=3yXJAO{aOvD5ULU#LND1* z#70Z^{1$eSl!x|MFYIf*(cWaxR;D{Y>FbctCw>JaKoUqIzQY$*yq6~32q?Q4eaBou zh~+NixM{l2q9L=fi|P5KK}QW+5!u{d&9knM20pS3=pMu!-|UNpeVKer-IHqh^6 zslBv77k)53+8p}63o>=EPD|yd$TaQUv=aPHY)J@?x%D1Ph#(Wy1)sYzXoMp2rsAe{_FJD zk7pt}d{>tjL$zTqucu!&gr@tKm{DYgh|u+(3E9XS8PTM>xOgPS?37iDc=hlZAL{nH)|&JmM+sQIj_m&3+{{{oz` zlkYuBH&gVZc1y6CPMVET0!W;Jc##FISD&b!=3S47W+8<(|li+>3+ai@& z;d<4#>k5{%OV_QI_4jk&!AF0Hn9UnA>}Qh02apE8U$SE04#;Ueo{f>^6qXi7I& z#p8e|#y$yArTWb;jKu^|A3bmzC*m6k(TxjHGo}k^)J$WO(hMW?=J86vM0+C|Ui7s; zWtcG^CFDia+-p5!Ih^btKNCK&r(DYD6W_g4ucT-KqE-Ob7WcgjSE%vui{gCkVd+9> z<5}n4wr*oJz}(8DNH=9S?eCxgL55KoFy*tAKWpLb*tkpv6?bWD686d@zgHVT5T(3p zyN+Fnr+UsuZAv3#lm3#%k7qc`di}K57X2O z9Cmv-eoAjFf|%nEVX6Kcg5mlist3$rok(eeA=m&1wKwre$t?D)aql4H@CnY0g0;J8 zIOkQ7xgFmM+hB$proGZdDmdrJ(DR2PD#p`p=x~;kSBC`~VW~Y%|D87zouylcgbV<< zMM>7;AIai>QfgrUnLW0i;Dj*@Eup7?*j5E(wg+Os#+>Q$1QJ9&kHf&+d=V{Gq_Vpe zv@)W2BzGjuS?0fn2v>U<$X~qYD)ai)?tZQ#bR4vAvHuCieWL!*p>W*Lc3xK(gbv?j zLkKorQbh~Y(HZuJ`F;IN`H=hcoJ**!)@Ygfur6h2 zd0g=AbX?Aw^^AY_n&b+ZxA-zwBZ0p3*e7Ut%Fc+!i@?^mN}%xH3rm~iDWuwt6mCVr zSN>OwE|=YiijSmPSuduxjX}}yi=5Kh;uLpx?4iCPpsX=wS8eb|j@ac%)HkK3k-Txd zF>2~8EgI1^`fS$EB%$vlTqOfD_T-yuqf=_YQUcN(I(<61*DrJ8tFd({rpP2@Z!Tq; zu2jr(bP$;e(n72te7~LYE{~RMZCU!TCQk>X74s^h`J}%W1RwuEk5$X~VTJtU7A7sA41uH67sFk(*OM6hD<-H=BN(YG1(y8Y&(#|s&V>x|aQQpmkZ zc-2@-Lr6@65u!==3{Y!9su=ThHCvtCygw7{yH!5NFCfq=xFZHF# zj<@gUr|gCznX+FF#U|EGZ0Zcs8~)|+$)FfOl`9VaI8CdYmh?eG8|mle1xTFE0r`)< z*$PP-X%|X7+u=H)^MJqZz5_AJC4)Qgegb*AG*SHK(k}RwoNJn4sIxRHdxcnh^hf?i zeZJ|Uq*Cb%CNU+7nvV;!u(i|QBMO034szt1k-pO(Q()`a6AV3>$pejDXXFWS!V15L zVfKxuD@T>I*PP)clDEb~w2IV%cuSA9uxsox`MQXI+jq;HSDjHa#bHmjh#x~FM2;yYB?qQB7pTzI1rA@=1PI|>3P6h|qYgPDc&{^Y{j7hOw8_vGskOvY~f9< ze--hf;kJ@sYL|iR-{cfP;6;g{#VEw8&Q^7ccZC19vD%ZqtbfnLkxesfbKZ3RW%ki% z(3B6SHXDt$p^WnZ=-v7eo1@1;&h<7rvp)tuR;lo#4l;(&kI-@aUn#z0% zhDdRp*^h6)DsH?51wPmK#nmu&tUY}uE7|_1SHHx(lgXk=T8RKG|Ijf77Gg{2Boi+X zCAOvoXqNf3;k+|iumbJ$#f0tY8S1!W1pxNE1aOMfpnJ|O{}!&*_BNo{6UqV;s#ftW zV(T)SYTpjGHt?q|e!||Z2lY-%eJQeABm}~v>}4LM#@j`ILYG2dzjZ`L{$1rcNA=u) zyd|O#-Nhnhm4Kkm`|lZ$x$W+X=8Y>*Zf|=!dR7KC-TG?mgdlKqDaP|wV62Ov-g(_Z z*XVU-&sXSrZts`T@9!fqn+w+!ZCZDP6nu0n;MR^7%GL(LzCHhva7X{L`k_QhOpM^O zqcqtPgIXuYbTzlXucY29Y@cj2yQwE0%5$x3_ug`b3i`sN2esnSpG%^Pw_l<&{J)c) zq0a0KNs(!tDkcY@>grwaT_L!v4it39Nt|6e1CSWrLV4HHDi(m%2Y2v8ylMM^5kW%6 zrd9M#@HI?*d<34me6akgun0_?Qbt~@;J>00gf8C)Ia*ANWi)mZr*nlo^E zBN5-Nt!KFL-NI2yt~)V_7lW<)^knfKSt8o+r^T$B9Dnl=LLox5=%-xI!AAaOs~ zGnwRYu{DiB*9^y$B){T^vL;!lzRknOA`P>4XD)dsy|W^wqG)fJwJz*sK8^6Mz$ZYKgq7C-nd(NP&xm|V4R;)%MRV>h*(U$!+pdS<_%Kfju zSH;$Pyw`J${)_QaWcl^;7gaZGq*I=E`49(06pE!qV8w?@Rh_-*8oo1S?H*+naUwyP z<iE>1;v<~D74z;z+b`u2vC6zz9VIzT}wtiLNPsF zuUjF8$B;JP-aeKpxhVggX;O0_cT|7H_j{7c_22KaeVnH)Jkc?FY*+L#JfUCyJoF;( zzhcs1Nil5i;1z%wLC?;dE9}x|L+|l}yPU}mlZoS#3rZW3 zeXooFj*@90UL_YmgM4GHzHLuAo83=LZ;W_#@)X^_?#Jv_ohxE^v);T`bfJ`!5`KP6 zX|#OQkD{J@eZTj0f{RDkFo2R3D$eOd$=@qUF%hobf5CGTGaI0t(EeLV^i9J+@RiM1U#Q6QO53`js}hanrBN~Iuu^GZ z*_`MGzXt#!iOM3A+Z_{mbw-hrF-4&yfoGifp=N0j)v6MRN>x}?`b(}vl#$*SUmp7V z=|uhXTleP4;(=+1l`c+Z;z*_ZrsXp4aj-GY<=ApY0HO%*KeQ6z5&8^T3{-dx9x(es zN(*G&o&f$}jQq0pStUxN5`US8MBBSZ106yr*P^|Zw~5m0N@00Q6ctRB@uNt%<97Fl z=2v|pn6lhy%#$VwK^OwVs#alZueb?`*OswDVC69Qn zVM_yzv2%gVrTN0+p#!JdjmPejqmnVI4KIA?%2Cr^Z7n z+Q2$~JQ3*2kUMl##rV1mBI!* z4+1;cTv0Dm$Mp`#W@{xhk6N<|E)v6DDI(6Ah8D=>EF` z4ICrY(alZ)%l5gRTFu>NgnB-&Ya8o_=@nL*%QRc^kN-Fx7H|)pbQK4klhcnY-rIEv zt79PoMrtQ6wi_Xoe^@7WMr#Z6@{UtXgKICxPh>b%0|P!ej6X!5_Gpr##V#C;Bw-5; z&(hdMgw|15FWN#Vm3ypKEEC@jiGizL&w!&oXf1r;^Ksh-*m6&Ra|GO(B0`Qtk?D&o zIB(MA0o;QTrzgWa{y_p^S7Y^0Xd}<6zRAM^ z`(9U_U{0$HWZ^#-7Q*DU^z( z8$jXtKuVw|sh$q?cpp+PD&~9c);ih!GKOMio65o<6iR~P(m8@i1+iSBU$ATJ=)&4Z zipW39Piv42Iy&Zc_)JRGOlTI(t@4Y7_|>&^^KjJu2@un3l4ZM?nz1dWy6ANyL_bfX zIw9!fbAAAM+sym4%nZPP4X-9e=8XWbn!;I&A!n#}R^pGmk=z{qk+OU}%v6*?IjXGo zg=^VG=|N@*rp=4SyT8(NL8AL=@-}+54?|2UkF`1FD*N|vRpGoQ9tm#UTi~^W zjPFGeIKt+3lIMnVstrG?15X?2wh3e^Yzdj&R=bArblYI7jMFE6wZ_419EzS1J{npu zD)^DW0mZx4jSGG~43&V}94DDd&Ssi5pH}OD+&?t2LUwx6;?vl^ncNE}{zbg>aBJbu za~eX&wCOz$u>j8~r5K8U;2-~*@+qrEzXwaU{k?K^xy3yeC!$dK67n*ho2>BO_581+%J7|J)rPl}FM!CoP+ z&`5*X>&1@Jqi?@>7pP7#UQcS#tqN$}tu`|$N*P#ZslaySba}KZ6|)@x(o%PL9zfH1 zINOxm=%DQ!GB?Mfx}2yfDK4rSE)TDc+{qjHnPWfNQFq62;hQapde~Q)*kJT%LCq7Xi04tz+x{RctVbEEc>Durs1Oa9c zzL#zb*u$7sM(u3ux_C7u<M^qWUh|-$IRmR?6$k_`VYN#qj zK2vlANun2t+Kh)z?0-!By%;fS^g%IqHK(Vk$^eHBiPJ>xBW+*BEUhHyyHM_N+fd3n z)||F4u=V82_g1QX|4-jo6`%|Tx%kE&9iXX0qqvO&SLfykKuZM>1F1gSu<01MzO4gdSDvob|A-AEsKOBQ6g9m2hzYd88rWpo+p0e_tZX)&*7F)Fe+ahs3^R1v+ z0o-Xh(LMNCr=t;p;;X2GSHF@ofCHOj)d8XLLq(Fa(*r%7B>_FDKEY5nJ!sgShVf6P z^IG6(vTeT@rNnKnT_LgRykT}|K8~LQ9-ISh>}i#xlq?8nSU3ML_-x={J|CwiaW)tZ zq56rc5hP+tBC76)d}rD2&0L98ZK&+~JW#3yiQT!f z?xtRmYu!)7W6aAxF5M8VQn|_XJ`Q4V^)bK&uwG<)@U2i=GS27&+va^`!P2HXUE%`V zt$HGLe+*2Es+f)VJvTicm8kCJrM38R=JU*Wx1LTq5+Ag&yA}s-PT6k>SdgE6j!@nC z@v{2+Q9SgeUkM?x_+7kZYg)CNx3HW2ubOcjcu}%mB{nMVQn)nFq?CaTvtQ??Rs#S{ zXe^y-I*rj^5Rnh`wvh9A1ehGJ{fh32DjC?2r%Ok?`rL<@6+HWopEAa)Gh($IUFgYa zSjuLjcIoSGr86#~PSLKP%0)>%iA`iLzPcu92!)k6hM3xv2v+!*?_N%-qLtN1_h{n1 zA91Lwo2_}Zu(M-rQ!B!8!(hJLyUM`XZ!*2i&Z8)eaa6W$1pG~y-8|lJwSfkFb!4?BW9uwRNGqjzLC9tJ2w>SO= z)Uka?B6PrWyTI%1SY*T($mw;oY$*Ef2Sq`zwNbSQU1({cd`wW2r&bG!Hdx4z6h06s zu6D0p$l*Wn^l26A5~v3(J=Gt$`VLw~(=gV;P;p8eczQc-5{$7afQb+u=JdOmB+-r6 zjIQS#+zz~Dx-_J+GuX)&9FB&__`B@|V!sKid+nr{Cge(#l)a0PksI7`1>P7E6i4Q= zC|1C%w~UI1^rF`%7EqZ1a$SGb30!zM8XZr&6&5Bhep}%JU>$)NYCd566QecQvS6$3 z@0zalyt1*?B#__ID?(~iKv&$2_zo?tIv}F)En0OclLa87U+6-yPb?JutnVTxPX4*k zb?H;*)h6W^a)$SqXsM>J34L9#-By^0Fjl_h^Ifl+@d~4@TBk0~mPnAN^k5eRZQyLcG| z{jn2cIgEK!u`gyafIN`$tXSLxqddyew3#;DqFm-aDH0DFSK5E(@{Tc{;|H0blqf!G zQdxrqiXDM8rnmiAWiD0Z!nn{xIK_)n`f&_0O}{koIb-4;9TO$(H`?u$d2u+%tG-%v z7&OieQOY9;t2XdSud(9O25n0T;&Q`zydgVpvHehnOtLMR?QX+h8K=RzID5Jy($3H+ zFVa+Rh-N7$nh>3Z163XoCz1{ZZ#BKm5&}UjqEtdcF;S46=c*NW=?U6MM~XVELN)~Z zSVUb>Vf}hSQ8P8xbeN0dkkJ5BWl4M~xhKktyw?xx9eG_BzGkCc9Y%^oy}X{b0}ehD ztoqI$hfA%C%k;~4b{4=JO@y!PNOwNhN|xjngT0X-j)dZvyCGes>>`MNflG^VaSA+(SXUa}vZ;E1G zhvPRlgR>FSo(T_l&~4hwJ;#|9T}pB*eP8G?i83p;gtiV{)W%fnu$srnzTJK?3K&1G zD9s{!)eI+hRUZXTr73!|<_@c7$Bq62r_C}+XJ5Q2bOB%G&{tmtFY-N8wUb^`T+=!(PIsb1+Em^Qw#%V4+S~C}UrTlPAZt>p zt?vEEO5F-RhOWeu@AoX#{tY@Wa91dxji6*s9^^FSfhzbLUj`jnqM=D3C^%iR#A`T= zeIRJv(|)PUjyK?dEC|9{c8Q!EywLx%4cxqaH@THrX*+dzA)&NeRtN4M1PNxvU2PoP zO*dWU-64lc)eniED)B&FdHk5HvK4L_fQV1vv>2Yt*yRrM@_ zzYFp%cPklieqRr}RpRIdyN+SfaAe%(;Xmpkp0`6XC4qFY!1gxzVv`DXhZp0;app6$aClQ78@p2i2_tD*B2iUlR_IDqk zor$`f&O}>x*-vEdNKbN0x~lgMF*R({s55qAezddz;QsTEMWT)u0($gLi;=9MMA)94 zOyFzz{RRGz^01Q4CNI}GcYD5TPOMna3iG@&s`(!gr~kd_Ly4@7s7gX(?e)<)68mNY^Hp$WMN`x(&sO7}2YGE} z`^8(3mlTM(x^1$jqK?d(-Xz>hT~K)`?IXtWdmBtyx)}pIdCYlQk~BY|M6d2 znk0g)0%$VCR1MU{;_w@Lki@%$b! zWp+hN0=aK{m0cEkKUXZEjd#u|1*w}I8X7*- z`iHsZa2^PL;0VbZl5Cxeox116KlBf)=VIDo(CpL@)cZ!Ahs(++Y2i2bhwx~lY&0bM12YBtr zuq*At0w^#+hk%uGbS17`Zn;25r$l9rPJ3+PS+*hS(bxJ>zf9aurXek|@BI`w4S(@r*1IU2De7O4v{)SI&%5SUrEO-xjykPf&xYXf%$&m_L&ZsSKRYA+XA z+W>KwY8x(O{Vq<{WS3^y4kTL3BSeT|-N^L$-A7DoExNI@LRl5#gAMQmU_2rP0!m+M z(l@^x-p2mjOX-&4l5u)ame7E})Hd+Fom7&K^G(|=jTR(f#CQ52=VPY_!%7qyudCe3 zWwq)^O)nl1i)$F4b}kAZ%D#E!>*lZhb5=K$xfCK>AUVpTe#Gp1XxZ+se@H#{kTX}f z&HlglsMM<^^AVc1%a6cNb2%pmW|sWcxkyjT)JS+q9Af2%#_IJGq$xtRE~I#sX~P94 z=`N;ZdZhdyP2Cy_92vRj?XcWqKB+F~x016Zgd+NqZ?a`QY(2+O~)IroG<=YGqBX zmlk?waqweHUCLIh`XEA!Uae^OmiJnYXxDc|hsL3HWP6^gft`{{R<1A4T~7zAiBhdF zZbyoA&?p4C6KH2A6aU&AQNG=oNYE5dfOzc9Gcyo^!!fgg1A+?i)nZNku$bciI$`#H zm^5XJ?8JtTaKg9W>V%v!s#87LiFSz=e>ZHhdTATVtbL*?{H^|06M-b6Gp0mg@)Y|! z8-tOK*20MVGT{ZM4e(mWJpxV7CN;83_Jn#3{2($+ebK2;kRx!eyOj8M!LiUq#}>vXa6J(;MF+SZz-|TRMH6?7&X7_dH>OYRhRAYmAtXAS`Pb??}1` z+XUKs_SOLA=lAtspks&vo;?UN@CXS>7(wk7m{9H@86QdQTEFZjW=w~l0Xwb3j1s5v zvpw?gfa<%eP{+vdJ8DB=$?PJo))}V5or+9aqfne_f)ktmH#+8^D ze&$~57z-g6Hp&_n9i~aK7T(?@IZ&-L;I=KfdG6|E*{;^<%Jw1>A||zdhnM+~CSL0w zxvz&f8CBupG(`j#(oEYwoY`=Rz|nV5Wi?6^7MW8ds#^)zl^Cn-FBtWVKQ97aY+iq0 zFo`nMvd&ri=AU0Sb!m$i!ermwAZ40DigD7YkGivKV&A!kzM8rXa)1yJ!&#!`Vnwom zRYtqms+eC`%P6{1mgf@#Zd(fTg-!P(g)c7qph!xu)whQ?!zUoISUpPTo zP&mkjUB?j9gf?ip{g=yQx+XL@v-vU?H26DhJR%$=^)&dv60kaN-Aq2qFZUa*7gD$L zKRGQ>`Txokz=lQ^+l<+q2}yuHJpzg_R+qbQMWUqCr|gnaMT`nnLQ-8OpR@&1X=Vgl z`eeQHC8QB@$ADFk#B{OV=uib*`{Gx#LdP1u;Obu1sC4YW(_s+ilK4Jx1oE`iqhC|g zVCm)2qfJT`$n~*=-(U_QBZ?4n>UASj1lWME=+uWOc$PdQ@UL8uMt^I6D+l{sf5F~b z%mK9^TQ*N0xo;h0HQi-j-{Pi7LbAGCm?d=2QvoeQ19%YUQ5%;GP-VK8|Nq@V)w97z zs&j8nRBe`hkB3ffy@KzRABh6~J&6d!>a&;v?EZKIK`CG;LW2-zK5FW9pHl|J<`=Lb zAFd=7)CuR4V~=WWM@X!(!Vl*pk#O) z9_>QxG%V^&3u3-HRvGhN+dLkyyw7cW|C4OjGf2F4&cL8@sO6YI+x!II74yeQW6Kwm zZDnV0Z0*VyzWUc52=AbqTq!bA^_(H%Y5cN@=TW^qK3`S7~V45 z%Xd8~=h0Xnv|s<0Q}QjJtHC$^fPO*JrzKZSc!ii1O6+q8W+SCtyo#=fghNV(Ozce7 zpY^omwB6X<`f2C0g*lpa-f}iHb91(Uxx5x0@}D>*ZxIzxXRE128rYD4(zkoLCmcYB zf$&S93!uqAWU|@eu6cctnNm=y;xz!0Vf}TW**cP#<=_mENA=9h!^q2<&~)z`(Uek^ zMapk8MTTAnxKvQOzZd1bsczT_uwBizVRnEFuSEZl*#AbOXJA)jZ-`dg%EacxBu*2@ z-*io^VJey)1I$^4a2>U9HC2;f*_bZVebG~T^nj)|+Ou^598i1JKx(@)qeHIxFKP)> z&IRC8?cVZt3jvCeMtAXN#{mnx_@^MCZfg~K#(=Kn%3vQ^1To&@c<%X3F*x_;I1#l- zW;XvQ(4R!^c&dJFtz+o(*0nXrdUDJOEdbk6Q&80*z3BC%h|$u<5BcoOiqHh|I&^_l ztM;gn=KzgXzT4R21KgqeUWtx@lN@jzB?EG>H!;9QpVO0Nt&iTYWTaR8J=CxNkBYnR zPhMrhtct;Gky7~{Bkr>)Sd+s6xp4OHSxBF;VYGa}#rpfJR;*4p|fO_%-ar~mF zTsL=w08>6tyQ}-XFb`0!eJXliDSKI`ro@92Tk?PK!;l0K&!U<&#_#T3G8^&tJ>h$Uj z*>6LRZCh>R&Ah~7kU#?QEV*K~*OU*`SpH^Q)7AL?V!{Otw$MDGqrd}&hF=Yh0~>{w zfC59;Nex`<@_{~8gUA*6z^$I|*8MZgK`LV7>-^1&_+1kelN;a4n2C??T6Rm~I9OxW?8(igJLrdL}C+pxcc7m)HP!{RB z5*7A+ovm_vkC#`j%Jb>oCTDdaZLnaw<42H7Y=66bpc{j1kqY1cdELbPMVGLz(+N$f zxnv-SHoTX~sqqcyqLYCg(}BV@i`6U;)J|T zvtUesV5khmS`+QOLl1+xtm{7kz8d~YFWnqhU(r0NWQAF_jeB$zw1jV*hQp>bEz@4Z`d9usAAa*)C64#^MU|oK+)96wcRRLDGoK&)ksAwFjeH;P?gP`I zce~?wvlC^VseE@cl-rE)(q$?KUtYZ!sqK|1_8ib?AgL@D(bQ-Yc^Ls7o*HXK@~eIF zr9~Gp^&azsAQ?zxq|3S@p51>Ttq{{Z+@%0Mr;&c`Dcd_Oe$@)i>!yol=gOFePmfNI z<&Ic;Ax^;+$NbGuRI{?xljU1SB$VM1K$QeY5x4??6k)Q=oiyQ5-S#TLHzAauX?A&DhSCmxeDDZg+_A zS#)bhCUP@iATZ8RdhJov3L4JIb~4WUM6O&XDp?X7Eh|@qJ?U;mr~i!Ac7>LAst0`z z+K+$B0Wxzk-m?KYDJ;cB@^HLbKTHz}innCU~-AD9w2sOG> zy>xq}7q_h*CeDdb7Q13F!2+v5kzPLGV)L&<=wY`mC2;m=Mn}@M=KH31X>lS@IK4-R z!B#$^13&f!3wOU6*z8dC=$Yf)jbOt&OH+>dp!Gw%5{*^!Etc%#A;2JijZcV4Osk^@ z*z^-SA|OF37-qlrGWFqCmHGtXv$m(5QL{x=9YSG^S~E23D~72&3@&vBaD>t9!OpHl zD-3FxExND%~d1t)uQbO#|Q5;mk`uu3Q0JL@W1&fAuf%-0ylV`*>1$`59F` z_g07UcNTr|oT;slRvt8nL*hoO+xa%#aMyVNf z+B)lnZ$mWyYWdH8uju8mJZLtd@#Nr}l6}x^`{O@PDZkBrELP6+{W)ADc)b*}xq1j& zdpaA|T}y3$l!j}Syt5l|#o`*;&o&b$LM)bH?nl5y1RQV`SNPN{Rz4;=c@o&{>6T<& zu>Del&~P*exc4HK+8(lxsdLveOCGJIA^p~MO<5B>qw8EY$3&!a?b5t1d*a4H0QLsb zNg)Q`#41@@1pW;Pg8MXU;j@3QB;HN^sE7($HNrb))FXD{KuTLN&HGiQ&ey8*+;@Ct zvA<{<-b#D>3umWWV{7fuQ#T8b9&Mb#e_(pRXHvp^m}3atWmnnh@(eI*@UIf{u@?%D)@VuBHhCb!d zx*YBC*MedWC8bF(o6tHsMkM{QwAuJr(6!VDTAYL)YeV%0=gO||g$%Zv4DZA3I*5tX#UO#DK@ikjz0OmNUpnJEq=%EX8QF6@;ax+C z5+D1o!8x_`tY~w< zF=5jCA63>m>)-MwlY3YwUAL)^1A26N2ge!=dRewnY{P z5mmdqc>hIGK}Sl|L0U0zckn80T)$IqnB*iIjRdZ}r7RPxh^ng442a2R)EvM?kx8Nd z-rQ~gcBWwH&x0bWs2v=i)v0lk)yy0)Ui2N* z;OE&u8z6r*EC0(-`;+Zo@pswIUDCTDXlXu~ZGWyaDo>6Z_g?qb#*B(Vw3IW|{k)`X zY}+5pKk-7!n{6Uoh4PPOw=S}`(?E zUMGiB5EJpmx?;#!m5tjbzbG&%1qa^qtsR7&mLCr{sg}Ny)Jom5y?)6E{R=>6A zji`NViamlooOKzi*Uih6iLoF0;#@7LmrLvTO<#Rs4EAzzO*VPcu=T(!OSL{`&O`!8 zTi}O8`mH`xLbx3O1F^f4g8tIPpp%IrRM^m%ljZz8$54uN6Xox01PLXs##->jE3!I8 zcqT%+UmBP?p#=pZn-`=Y!)JI}=3^E-_>d?(&c)1$2OJxC3A%HpvA?a)dQ1i!>G2O` zw8I(<)eks~S3ewYS44`#^>1X2cNuNcI%U%?=pa?K3(P4xd&69MEk;4diC`rUW&l-9 zh_-4Bcn48V=jDzjb!Wm6XbuIGHWMA_#}C(aZVR4SOs}_pK^f9C_@r!nwmL+($mA02 zfg2a7L6glCB4&8QSpssmNO@LsT-Tad#0AnK_PjP{_-n#%3D^breliwd;~aVAUuEf# zc3)tok6>9}b@Y5r(_Uxir3gMQ2{VS$l-tYP(o27gzyr_uMkI0Kme9A`KGh;@k(|6bd0SSED7ox1#@4Q+YhxdR364xo@;z48G&W zFB);`#AQ8xI2o^=V|Q>?e5Q(X2!JlMRRevyvS22?udP6CayL)(4D7UBNk4>tz3d7L zk0TkEw8IYO%<+M51JB#&Ko(7oom^2KWdO;@t+3HC*`$ApGRew6{yU`%$3g=ZBV#CH zf=pwyzkZ|*9Ae70{e+o+~D|dMA&tZrliYyWAn~p77_ta@tRA?h5Mh zUEi7X{ru`(MkGyG4CP)~l&*zP{^@b~X=ThaF>zpFq{qbp%83}9E}58kqowUEIXax{ zFdW_N*7v;BdEyS;;kf)wJvO1d?iV$?#@Tap&v`SrFQvr^=-~k3vjjizHSIThulIRY z1l$vuEKBZNz^CIfTD*a{P^gUwa+UwNmD+; z{|lAgURB$_?g}zv#u;z*t&gGyDwamigW&6V1E@El5h&94(7n%e+6NwXXqaU@*UUGB zhKPP_9LU5eqb^%58Bno0Ge<_1A~(=AF%wrnGaZ(Fd`Ru(7iMJ>=Tjc3(qu@advt_c zA`mr)E)vqP;k=t#nRP8mc(MzwHtbiSZ=3>^-*N}n#?NhAh3`|$ zD!L$`?~nRf+IU_t>sb0CtJ|-VUVT2ytl2e^qOhJ*y0rO;K4Y5qq{~jfPjz1<7XdR|1H~ zidOCzvXq(=k*Wf*F`WDw+K|=7l`>F&pbXARi+LY4-yT!6~4hU#v}LUFMV0Rm&EB@*>WUtd$J zv9DmOJj5YwB0Ok5g&V>A9y^gGS~&Eja(l_x1%M-jqK8{e04nCmF!e~>T1D6us>fn} z4G2c|G+dY6ON)uc)HuH3jfqNUTPX5{5aWPuv%&(dsc`Z&Jz(A3@P)d&TK&l~U9!2} z_hH#^6&FX|>W8^Rv_*?|nUqGevT59`B>^zH68VrSCS}7zm)-+y78gcyfwDJ0R(vvb zxP8!uE8snUQjRRAB0P-X1f5hkJ)4`-AbVTrLT&1ex9GOQ`2^(sJg%`gF&&Ni;l9m%daPEhc(@ zyExrfI}hp<38)go?C{5HB2fS5;(-jN(ZJh@#I;m^B6;KP40?cijw|S_nUTOVx4sL> z87cae$bEAEsddkEl)uLPgF~COWYdvmubJ}x(b|rP{~E7rcW3ULMBN|Hxry{{DpwS$ zsRMd{2S!7Tf17P;eTa;jcxxRLxF7QX|5i9Lcn-U-+RsKXN_RYmzw}`$|LQVN=pGbio3@Kk?uza3wyftyKxPmIe}rU?;Pd zoPq*<0Jh~6^>jkVNiYkg`V=DBOjTvs{*bxCf17COTydu81y7sf#J`VIeGk7v?%qh` z#Je7BDnylF9mVD05ftQyN z^3SNKU?0qSVosI!((jLCy)qX~GOx(CkhN2(**Q0f=OUO%=+WC>7&mtcLx*PGK@S*8 z5vu!7HjS;`-Yq4m+jHvdZBO)rb28JX_4Kg7qCzR>w|c_pyH+U1%oNor;s_dXb4+ecvtK`W=9jac+P(c1p5piW;}uW>-hci%APnwh}N%*C%uMzUmD zBBYkCcT6kWBW9cZ?C4O3#NQ=4I?Lk(<~l^omI@3KVV%7eK|`3$rJ}^X@xQl#B=+zu z@k=si&s=Kl5fhO0``7oZX8J$LpU!Ek>nhs{ci>n_D)O4jjCLs~2v&opNMalVD3%cv zYR^5oG}CVosV?-#lM zD*W}(GQ?q+H^?i1-sKhHKb#_;Nc*jkrL=MV$(H$+-w3P0>9~se*YxOi?lUs^%~+mn zPL9`6sr1&Ys^@UvE?&xbEL;O+eUc##P0H0QqV{c`DpqG!52s(4>)1zKHDXkkW(Rgf zS#i>4XqNVe$HXY&8*chqxYIXck`3=-{Cn>OD;?_4h*FM`YH+t`Ok8ZT)f7GEuL__3 z#u0U-(qYkXoFH^)N+`>j@hWmlL@G{^i9iB)wkoVN@SP#Nlb1_D7sVVIm^3u@K2@%8 znOijM;?V9|VRmq(h*SAE{h8fDx*9r_eGE@}j}g1!T`38h1a+MQjM+Ury}S-^#}@i#*LND&3V=TR{&;%YP|d1>?lI@2Yo>3SMnbXtqTLjYgT z1eq1KuO4=oW5~-1cPf`5{U@dwZtoPGRyf7f%ld=5`ffYiR(P*_r4;1)Qm_A?e{$69 z!q0O-#6TLZ>_nAZqXJDW)5lsrbRSVv0{XLcwi4*C$S$|uqcJ@HrYtNC_ZHrN(bK*7 za;FPg;msV2BNQm_7O~F=$k#VS29D6QqsGey%nz5eSxr6a?qU00ieYUF)gG#~f4FVD zm^oSuSY-51@p|lxh)uB`b07TsyT<=29q+5}cZIjPYJk}Zv{8&{ zf3xcA!YqFF^i7mI^*HBQMN6$Q$PZ-edbl>xN2`h+Zm@22PSCUK2c$>365ps*F6fni zoaB;*+OElMcWWf`n!*2P~p~BWJut}2Mw8-l-W_#0*)QT6doa*c+~uI&8SDj?B+?pM=#H4_fGm< zRXd;7$r1j+y9_u)vgOrru`v=R-enVpP*{M+Z9*-u^w~t$FyP{O(aftkUVgE19wC)A zV>D1qltY60?`**7!F2r4iK!8q`44rM{j+O#(2m-W>YXs)JgyvT#wD8#38zG%txA*; zvU>e#!gDz>i2OlECb|Z<@vg()9Cw=DBc^h7AgSt5xahTeGUWp2BVpLf5sSZQK97aD zrE)BDUgl=76^$DAhE#P!N(L{8Atda4R-aRRQrAub5CQ^POXo#_B0yuaM1^2IftQ_* z`EWOWheK{mRb6ugkV<*l+6HvM@~3um|+v@k4P-485Ue8;6 zFf~{mtN3}8GdMrhA|NWY6P(VS)lhiwBhxHnZuFHzc%DiqCAcdpg!%fva#;o2>4>u~ zHXx|z3%iQrv`%-^M^&rUKIktYWeS!~FG1MaA6-rz*vd}my76%tKf*1kbRsFF+A+!1 zWYq?G81UzN^_XPEio?op3o92tLM0j!wX6av3m8j2(s7uV&-5(00&er8EPa^$pWZy1 zy&@3xA<>iwqfkYgb0Xl905KHITDQz z@P*cd(ADr1e;A}=4mB={Y-nu*=8(pQEQk>p(n-cM3XGK(5}yGO!Nc!~b|s-M;k#h3 zklj!!)=WaXRyvj$v;I{?O0A(#y|~@zAqE(A<5)LpGau$6Vzm6`WfYZ*=H3H>o9gtF z3MV5$90^qXr_`RScx&YxAF_(|5><1 z*{JwN-d)psm)Sm;XgVbDCnsN2|E9e7B*Lg5ruR$mv~Rc};*xSM`*5M-6Z*nq{aB9C zyi5B2bjAfX!Z6iA20?oHYGdrZ!4 zUSHc1gM2Jl9pOc~XLvbB?7zO$Z|~b2zS6s%m{&3a{Bo5rucE}aFH4atl?A)yh8&)) z;=PWtEO^^@=N@P?A!{S=Wg+QF{@lB=EkP0$LOJ^}A!=;i{$; zyNNI`R{i_W+A^dEaVFV29k+DMGyIg&b_jtS<5-!E^#2uKJqcgADw5c zUfgyHNgC$pqfNK@T>f;yxT~$=kmZUk;b@vrGi0N1w+O$uMGTE^>`Y+M|n61MpqbxE=?0+k~IWtYy47{6Na(L!%x}bV3 z{5_U6$Lv!v z&nFiEUw6KE2+hcXj&3~DJ*pv|6fbuaQ!YLcpyFNK*xnyzvHC0-RhxD}VE}{s}*FVEh~RA+<3ZBt>lfqE0a5psdF1m@u(FL6=G4krs|2Nc*{>Ueo8r~& zpQq0yc9t+{=)l>RYWbF3vdSClGzqx&8$oN&h9Q;h#T1sJO0irXdRNcf;f{u8TbCS( z#Q8{lgn`ykqQPj zgPHm#-xdl2Tm3F(QLn);c&j?1=GR~;!fOR*&D*jjdDcLF(!HH4W&WH&{I|Goxi57A zN}aS)l{yn51jL99LGqOg_0{r?vuJsoXEJQQV zj0<5w)frIMjmZBUT)PFbYBrJ`nB};nB+6F|00@et;WW)*Mo0}4_)a9RI^SP;INpRA z^x|3GXo@b%%A9Vu`n=y#hd4iS3IC_mdXD0o{{^Tf=ZpJHfOx$~GFcw&h8kf;upDTd zfKJkmLBI!o?JCwqQ26{P50AuVS;RX~$Re*sb49@Gxeg&y?=ta7pF*X{Zqu@Ajy@pc z(`J!3PU;b^xJ2Jtl~Khnfr`{9CPul_i;M%%zv=6vQ}57B1iO5Mrm|yNQMLxLxI+sE z!30v&G?&u*v6H~wF9WJU@-bf>1IX3*mi{(_3|96-n@@7$NwTE9OyseV{sc8|_xR%^ zm^JaYsoU@_aWdC^`kgMW7Ea-9XuphXR(47LRq+PygoYyTVZc3rB1J@-a7!>MHOtaf zDRrd*$jb3NM(FJkMHC)2IpqbCG)QfqxLRaM3_D<*U{`L=sf_M5Hxdx^&KIsA7{qfgsBE<-zRI(NNW8C^%T{u4i zq|hucUl(wTtYZfDtG@j+d3k@Vs5CDM~c_J=5Dbv^C6 zqLtj&-P8n`@yYmS%m8VqXv!l_A#44_{E(zC0sqFQYgO~TGHHORZ^dL%|;Xx zT1S_X^84EuT$|P=5Uz)VP=6>n7xBi{|2odQ-+#pmz?T#$C@$z!OKKQ+8hUWRg>&Ta z1t7p+JIV;N#c9!Xhjmwz(O3PGA%MmF8}-{N1qc>^$dc;6+>@3IDl&U1D2SkT|NT%g ztAcq|P=Ol}ABg%}AZ$-0(IdnMxT$o0vWS|$Td&5Hb!3|myQ5nUEQ5C+mF=RtAe2ex zw#Ul=huiB)V-%8;l}>Xl6YZC%M*mi@hR)}hIK3X67WjR3mniD(arv1K_cbxLtM9J1 zOTFG1QhU!Y+)dMD1Bw`!H+4m?pwCM>gouSvy8{G2d76SjhK^|~rpaw*@4Fs;h#vIe zXoN?dsm9h(&CoO%Hx#@dg~nD{*&DzYzo)7W0SG}M0jh+;n-?2rd_w&s^oZ+1v575eJ& zfSZ+uUo7j#Y?yisZKnve-;yz+Z(n!GO>tP;|M3lSkjx#RMeivD%ijXs7!$J_G6qKq zD#3(^PBC)YVb_$GnzJqGDJJohf5;zH!vJHO9e^-P3BuzF$T6%?f$3qerM)Bg&e*#|{nqppMH zcpxB_-!%|A(FZ%GKG0C5Ih$fostY4I$&(fPu~6$Q-QU^N!6&HT-29meKBjboW|SDh4acqPADLy0Qlj z9WcH-ZR&y&k(I9Fm60c&JQbpLk%TURu;DaC)-aK?AZ2TKRML;09n-173>;fw-@(a7 zKITWlbQjAKLY4WEP0XiK7_W+m7CzwP9uMhJ15f8lR_-E#f%9R<(}_n8Cr~oZpX1P) z;{C11nO#2MkV;SR2iZpW`MC_AkvO3_DfXN<;=AWGB#H!D5SKhnH|7_s^A|M^7giEk zYq&hL>vWEx`X06UPp3i=m|o*3nnSC`{I+Ar(8 zV*3G%BF|c;Q`pyYOsLJ@T`pZ>(|z#21U|MikAI4_Y9{k>yIwV9bO^gKR33c#7;11K zS>IZhSR<8=^O5%Fx|c%`lM%aTOdOTc9#u&+8!5r`8}lX(Yg#emJ(@xs?%K1mz(;7jN?7 ze(!oIC&=^kYUjgsk8G_$8@8Fo;K-Vb69PF-Tyxb5$aa?%gnvYfOgGf+;FjiB^)9lr z^MXi>fj;<^gm`w@eaoA|%t!LW1&^gN1|V)e<$oO}NQcsg+lI`zCxpa%xD~Wv@$F~M z+N;Nsb#uB0!-!^Z5MJz2#YR%ggpf{;x~X^5aS;c(sTNSQpAoS0bGu{e!DYAMh;SYi z7C3pbgRfc;sa8TKY8*K(!d7d(Se7dDA^qlo#55j>0Jo0!`BZ-7( zyxJ4M#%7!$)UFZ>p97AwHDe{fr%@VGr~yO(d2OfxBg$|l5J{pQTMJlywmDG5B0x<5cB<<#97S=7m!1LPF|GtZmA zx#|+q5#-q52bovwNk+={m%&E;y!tg)wb!3ink^5?LCl1_os&bN$SKtcEprN6bxaaNQEhiuV>x5T zsV;o^{^(>}wO~wz`0Pi|}wu*K7De))=w zLd9SzDu)UmZr>~G)N)m~qs!(qH${;V{(#Pa@UD_!B)6;fn@N^V@gohutnUH0^E{mV$`yvOC_*9ajat-80$ z$+2fHoVB{(mh?dr4!WLbO6xCpm1ol9V7m1B`ZLoMe!g>I^8_$wtcF3tj_rg?GPnP5 z4461*ZOxghRogb&#m}t)-MHtl`mf^Q^BrM5TgK34zA3d?N8fi=eV2eB-r7Qki~L?@ z5RlzztP`sxoWU}<)|p3$3v1#jp(ld;L4EPe*SZYj*gqGQKcc+zYT! zm~qOa(_L_cCzC15j#>NtnUOAT|6dV-4e;gq(JRGy=C05BjK^1KkS2+a*Wt|TV2~(! zA05by_S9`kGlXWkqr_V7#Gd0Iln_V$TfL-&f4(?9mLJbP{j5!>b!#{h)UG zU$9c%c#b7OCgTxhLX_A)s&e=mxmN=bufq1kvP;$74Cg~O&;GAWic4U&%tUqmbE(CQ zIw{RSW&$X4&sbHF>bMhsgX52@ju6hae>)yi>DeiNhs?CKBiKZUkHx&tig0!oIIBp2 zI6nlV*@w6)kleQh(?Xx$g=S8=)6ue-KIgc+^2Hpg9$tAARR6~K{VQ$Hg*EMs;~q2* z4h-o#2kd}H07bC?xHX`>{Ys<#$kd|(gu5# zV^$1-9D(5B;uWukhaq^_^T*@k_3|vMC2_F^%?Xer6&5ZIbm&tQJO{Y6np8B8Q)C&+ z1FnZ{Z3JWIE{Uol=mFmBbxuN!$}zn&tDFH2UyaY`A0E8|ZpqS!ogHgf@!lLykuchL zT*GMn1kP>7fdoW?-nc5BI=VV>#b6u(l_QUYcoN|v7TCKbHLW>DRi5E!Df+}a%aR9v z_uwleoF(kyRPNH^%x&t_FOaEBD+P;102u_3_jS|@`6x|~BIs|S#98{V45@Mh$@FnZ zEgAVvo+HkCs$?Cp(s=ZGzy~zJ!-Uo<1avtug6PY?ExfLj6*wsAyB1|=-k74ED zQHVL9A(A8MeFRCe?GnMbXqJB~>*yD2L|p?C74zV|J??0)z|fub_Ek*?99e9SX$Z{pJk?FCsw8*V?% z;Y4|a&#*q!5JUK<<}YhZ~8aI5?Q$Xovd2ocmLA+(7u3(pmZKJEip8{spwd_Vhlj|%lq zn5fK2pDQ|3O~5v@A|7~u7fOst-ek^Tz-3;+0tU#g-@v{sZm9CS-9RG1hSWf~b)AjD zn`KZGtq+G54^hKJ6ry8oAs-xN<->p%{ToAFS3}|EAx*-5enI1CEL+t@v5xgUv(LL> zwU}cXtK(S679-Ebmir9=*8T4!t&T5wB>jE4_`~@@wjHs;a_X;}vC^$>4kG@q44#K| z9OScUy@kI#pHd<4wpL5i{p?vx{;;5zfVj<@q#DP1D)p>PnD#TrA;fgwr~Z1HYj!EIvfTdSXiRfro>iZU1x8F5|f}#}kK2h5IihsA?sPDuPo$D#su`AwE$Bp_SAPEsCY zJwWHB-M1eTYB?7J3P|QY{X;>N?qVj*!FU&{O_0*5^PypK{zBbso|h4z(H!&v#kg77 zQ`B*S?1{sCiAMc!`pm|IyM|E8Ff=ljAJ2zmx&iln1f4D%vpR`nKV<3QWC-w}yIa!F z1Axb^U!sf$FwE85VEzGv8p3wyk z8)aDu7P_b)U27A{h@#G54ga}(RT6~1(b@B+zVj02r$WgW4u4WU-FnsM|2mXK-T8^^ zLxn7eXkfi_323z&i8+s%3oq!RD@Q|U+93dDX;&=+w0vQk;g(u=UYt--5BQ+D<*ArdZxYcM^QV2 zzx3QFy=x`B8)C(BTgNVE#H-w@IQoXknJ_ul`xFnqNCWRSt?Pdet}Mj$2I-Pw?zDm- z64o#zcXBp5b+R$JPr5kdX;}Rm!tV*6cwr1h?bl66KoNU*OuQ`eNsi)DB04a3ur5Jn zJ^*IX0`|^?$2I)Osv%?|e|>>$jCk8=)aJ|) zb{S^X<$GRAi!-noJp1BaFYQr$TlAZ>x|M$OfIJS7CBe~ zwRBa$qaIeemfWc8P<5r0wdP7SFJC5BSlbYQEN9Qu(sW6XFgH?V!_kr;3EYA5J&9g; zvJXS4xve<7X~F@Z0WH{tDm#Aljr8Mdm7;uuPV7gI37b2HwdeX0<`(#$sDjvi!r0Hm zn5G@v(-q6)Xn*7PI`!@AvqKF2xF!NS_H3K0l!2{dW2JcU8}5?AHmJ{h{&Ko^Sm!qT zV8rS62qyGk+g$0GvD_HNdeysbcGT{-mF7fz>=F8=ji2AO$tOB=HGe7}l{uBOQX?}T z8%G#Fe~rVgrTE2Q+(r~7m@2V&8dvijv-@rg?6)IJGCQ7M?llQc=2Xz%E?Wr+VE@d^ zba$xO_v+)o9NsDM@>3sA8!nC68JYVOurRzXZeQbww9u$q8|nPj6rxOKw;iAFghy>G zv&Ud-Y&RU|Tl15lVh0I>f)KDJbEACyk~r8e(iz1a6fpLsp_7M0PZ71*D`>1?UgMAd zEEpAIrj<_VnVY1=%D<0%{SItpOA1G4Oj=V^yX3r4K%A8<66d?2&t^ElC?mU1%2LBN zwOe7jr~cT3)@$ptFVvv6RQS&K5=0U29*Sp=o;-#Oop*P6I4DrjmXXYR;DxWP@bXpL z@J-`SUha)r@BW_SvpSZRYu41g{4sJPDa%BPc0|)Y)H+peyE4+(sQ|pgi_4VCjWQWg0-QW=95D&$-mBJ}pG3C}(m%0Kw zYV#KyHx9k4kCetP>sS48#^CRmGk>uCyt?&5aw$6DoPCh=SIv9de?3;|@EaEK`nklX z;?Np=-%}fr(9_QKEt^?Zo16u&AFCv9XKeYsPM3-8qnh^87-@~b%8W)~=)vcfFp+H) zg7SL$@x*A4IQ-W$C;{s#-R1UmL5xNQ)b(gQr4?dYUR?;|hqPP$RjceC_fMxJ{qK9GqEk~E~H(0>8?_1n1-PLxL z;##|PxBPHBFI>SM*HI~x@emK`rmaWUSXVxVLRdpEGhD~S7jl44`|KC8yELa^qz`RE z#HspKFVzzNo-)1e^uAGlUHV`eY1;_?TEL=jF3a-b1*Y3mFFDL8lzXXY(kBRH0ww!+(YB#+>AVdC|r(A98 zVh0O-pNpK?{-QQp+jRU4if^Lb9kj{c$=$MQUO)N{rfwY``%TpF>|YfnV$>r|@T4TUHNi3OQ@iOZ=m}K=12DMa@0{Wsq)RrczB^I=d zHFEE4nQc?h7Y)Fu_!^DGEep=752svBQsLrqCI$W?>IXMzj z&at?i2HT(Bg&#e663kl?OO1nKim@qbFyy z5@!-o8DuoEVC2>Ry=h%NN~?86)@5A_78KI9CUI3?uVVM(QJtmc6SEk!<*x;&YBg5r znMaW%AK!T?7yNs<@QCO$rLKR<`*tsPnU*iJ8~>_6hWRG%s2{QB>^08>vz;@%U3fN) zU4Z-fzpI!35I$eILa>l@%uY*J)`9F@;dj)K!!2n4L|w!xCln}q4SC%917ww{&J8<7 zA7NRnZGY$iqIUge6_xrotE-hSRc+nn8| zoVb4NnXx^7Nr|dEPiU_e2~1v+G*%@SCJKAk3-Cuab()7GGntLM*|Yab2Hm;L#nyM> zl%DqzN*cMW_E%3OeCDgoh#dBoyCMRj$QXsJam_a1v$n-< z;A740N_oSJ-~_8Jb#8nyqW3$z7pqSh7FPQj*Zfs7r=Dz-%Npt7@zxV?{iS{qhUL*$ zUR|rP9OH7G-eoTiY#6wwe$foekdBdtsj%WDUFzFnq~^l<^U*caF5F`CU(?qEe%0tT zhItAQvU4RAC0ua6D_h(P6@$O-*FAyxE)!=fA@gPXB1TH9$Ky8D_FFa9Z^N7HTHG`8 zG5V!{MfuYD{3wGG8N2Xs_|~B^=XJOH{X9mK(*^vtw}Vi9$Lj2%hAP*3cw?;`Y2%`c zOvnRSQ{O9SUQvbv<4X0*Z0R4dDVbL$3$%Q8_Xx+bkpTw<8BHFcCpL4H$J>LzMMUz) zv^1@WW9+=<_P$)I6IIT3WU@IRHq+~WyPdDK*1WFh#^_D4_3{mTO%Ja8nh_Cr7zC!K z=r`A(Ru7Nf&uMRNUja3#>F>W=CQcDWaYBn)9V9=Zy}n;3XNICGGScoRX**s+M>QO> zn~R>*a9B-g+FU*OVirMs)#MLgzMbt@De>bzAwsuJ9ZSP5=<0|n2Iw<4xN#0uY2VEpUBWZ%*!|R|c2=Y$ zba351s#JKXV2OqL29*CZppvnObXfFI>ah^tf|{T7J~D~>1w6-@sO{)sE8o;6~h&m z%~@%IM<=Z?>_I@@YaUlTS=l!`yxe|`kPj+>|J$Md^Gb3}-B>1A7}ZQVULYaGI-U^* zo5z=Y#u?H8UQr&OH^&}e_p|$_k?U(icIDzq<<5C^phBS`E!#Uom` ztX0Ox*5O3%@DP+PEBv1wMlOBZ(PV35zKFc+Si!-e3#r>~OT<7awY}txIE!SEMQitc zZp-_rk|nGc$YQ*t!F|{r9#!&_yDB%~oR1bBu$hLPIxkvw6e;RNLg|vQDQ*1d+US@8Z zQ7cz(6-8aUETE;^%xWKbl55+i7*M;!aqxYIb6z5nIkcS?Rqq+Pn}!{WqP$IfU#(OZ zIuZQ4?z2z3eZ)_j$ZenD{Z$V8-M73)hqq;T#U`Fn$Wq%Km@?~DGP|0GmhapE?pfvb$(E=VtmHdk@9mw8eyZmw|pVRQ|!P(Zjau-I7(Ph z^0o|cM*VCxv>9+pzy?+3UYL8-=9q#Ny|dB-{-{zaDjE_X@u$z&D{e?KeFZK$Gf(k; z-O1si2Ot6u2k}z!7?+V&b=w#CpzG&`r$u4?$y?LN=fZyS2H{j8|hrhgPKoW z=-2o)R-`-s-4yug6vcqkPy%?VDc2+C9;B7Z!nb{6_S;@1>(#?c(}iq|PhZ%&isz&9 zE@|%6n3vPa$>IHi#9a24dh(cRPS{NE$K5dO(e9O;u>NBIKQg!l^>?$)BHB9PeY@}s z&MZCf@mdWsx|!88KT*nW>ld`-GCeg#*fxbttsw$JVbJ~?;US~WrT z_2Q6ZO2Bn#%eGq|*%2l(?=Q)KZE4!qmEDUqL*DDcHGlr$CseoiOL-U<-Ns>z^vj(P zU7Vnt5w!4rGI8*IVvrRlYN6%!XN8BYkJFEu#9X|l$I`qL2s^N5X6s~f zo2Z7hJveR0pnMM~DR%YKK1VF(M~m%?u(J9{k_R2@;=JZGUoNX?t1f9S7sO#zALy7+ z1YU9<6JE6Oc6{ya9oy;Je2~^gJDX4~Hr{%RHqpx-v;vFROuPU#3zQ+00`Okl3b*ju zP{O_{He~xsOB>j1Z>PVtELM5CW7nSh)bF!dXX|}X2!%Vb#ma=n6Dm~cFmGDHX&25S z*lqC$qMH&_kb)Wa?R-vxHRY0#EP`umvB9;2(J&*S})xW>vv6#M&bo{ zmGH3_cwJ*mO?O3tn|DPl@UhSAuQyn|ADFCMZ4el>#WMH>i-mSY^o8YFArW$TMQ_81`S}Y|lP&#=1d%BH zUF%$@pU7zzzzKaXQGx*j;OQ8jz(GaSAPR{@-n}Svb=@UiNAvcjyH$p-@0~lRD)+Hf znB$dvzj7PtDl;$+=>bhs(4O@ zm~ODL#_a9J+B0{!8rR`7YtlKa3i7}3N8sk*gS-QNIltGc5Py=}nds+D*J0xW6Ct#rzM}-t1OR2@4Qecl?8!jNu+|YscTw|%G{Zi3twJU|MPq8 z3aVE8e?iA1X%j~rQUJ=!>FhAqTlDyx-eA2GP1X?GofDRV1qapbwq8mc$ok+qS6}EV z7srvmXp@)$wV$RPq*D4hjQ3m)m~16mugv5F{fXTRm2nq%BN@IM>Ebr1?#?H$am_Dh z`qp}q@nM8YC4I1vKelIOl5Q@lri-Kfc=f1%b)xqvH{`gZ<9tPacY(I>uJxn(1;X6q zJedn8Nt#C1`0r4$F06T#{cQ2srHwW9g}H%+(N%TPu<0IOU?vvR-*Bv! z<+s;8VD!GXMK51-fh^zSNYT^rWi6aFZlRMA_+M?R!tL^7tQ}0xz`gOA{|FY{}{5olj>~f4e9QDEZ<{Ad5n6M#?5U=pvj4G zABsHUHGh0Lwdwzwy7H)`^1h!r!Az;tSmBCOPT7p+GKOMqHBMS5m&a))H4)StnS|U; z1=J}=DN(GPG{>#wDRVG!8B|n2L_{;CMiI#k!5tM*QBc&E=RMDP&wKy7=bn3i=l8qI z_p{vZ_nUdi!oe4#Jl+7M~dd#C|KNqY|N$C!vT7sD$V{v6Rn4%)v=_OFYR_wz?cYaPrkiRiB_P~@*A0XyKmp3qEyD}>NFZWb~l?4yFw7jzh z5diuw1;P-rt&r#AW)N(yhQx&CTa3s5!bQ3`J(N}%#42$QJO0t%irQb~ZnE=@44bz( zD(ydrtAhhEQ~%DEAJd#sC5XUtlFRz(Lsu(WW)TupVxLIIBQI1m(2Lg4A(YScxAG3t zCYk^iuogb>sASqFCMHBCXO)a%kgnY;Zt3*9<5QVSB)u4!N+>8kh(L6R9;pum)Dvq+!K8S9<0_QmA*r!aXx?*IbNzy z169-;s*Jn*gVx*FcfMt%+KjPhy==J4w%!S(oUdIttQfg?-D|`8OE+6#@<97Vc%Zd6 zdSeLI{=gh=Qx&-Bew%6hjhMd#_4rG$yQxP4qjyQkaY)VYK( zznDCCZLTPczO7sNlg-mryMZ1m!dUsPPqro{4)>N z^V*(HNqY06vaNI9nl%r(KApUOCA|&NAcG6gawYkMH!P*@8w*-T^(>}mvWL@Nlz}`T zIaxsy0Nm8rxd%<-Xq@)xiG_{|IHhL+TG4;F1}~2G=`VUaJhc!(fyBSyG7&`9^|PTy z8jW@7Z5-FnRjA=E)d*BRQ|l!H_i>*6mez9rfrXyJs63^as4Y;5P<`W}ngUcRcBkoU zs#{&vvSN6Vy=h(X9z@YO^1bKbA5I>8|E%CltDfJLZydG{e#T~m*Lz)$o169c>bS`U z=dbp}oOeJyx5Smc*l+Ixngf+Y)R}BzS7^+cYDY~!8eib(VL}G1me;zmJU!aWt#sPk zugMT=9#5FKI~~)%E%=W%dcX#vx2Qai7k7EN+-ZJSG@9N-Q0?wvF-}Wsjy4oL_kd-I zfG!hHceo~>-3H}S5<@$Fw4@nDgq#uQ_}lRFaw%;~x(@j5bCDozFQ<rjqIkzmU#5;94Tih%Po{4zLLO@tGqQ6nndmV58#zJDLo^ z3^&}(Ge`Iu7CBrd={9m4T5GXb8!#i5pYjxHpSe%8>s|KGaD3hT@V* z9(~43Z=(3bCgvj=&X1%E#4UoHX;u%3d4jue&&!E;2uI2@&&B>k*-;S~(67h`RScT1q4#@8bqYK3xcl7K*)D7Jt6X8%d;&wopMaOa|@rxuhZQX6f| zp92HxZ7m^IxSksKeX(_pQAX}K>?QLi${9cgBj@$q$hc#L<@rURiPNyJ*OeWT%23k_ zjm>)_bdXhLY1-0)U(Po9R%QB33!g2jUWKlF?#Sq`%=Et-wIl) zOHb7#5llLH-I)(k0cH0T%9oEHX5 zWxn+5pt|9nCzxeuaOsK;VQdt=u|5;BcdnJ$)OF?FWAc$xQH{Xg4+lG1{(IZwP`6Qc zt=&=9NF6lz_aj+1k#@5$&ID#1cSc0s!CTfBgQ799ClfBeSA7dW;?M+6-v!ChQe77` zD8(KD7^81*oULdP%}mRz+mdK%a&iqLlBuXk>%C&ejH1mq$^#s8%o_nA>9(@$ESamK{Arn0$;|js&&pt#hJ}~PxkLw2|!?EX0^u&QW_yp zll8vD@$nHlCwlIjxHi$uCM6ie6P6C+Xg9C9&WF{Vl1|?RMTf5)yBecR?0=8x1%n&# z3$D%%u~QvhV?xeFH`mK6c7qr9%6!`LV8ueCsYtc5Tf00(`LM_Ebzr}hZ`6gzMig2X zWCJ}GY=wqlR({vI3dgDn52XWeDI6B@Hr*eHgOdwBY3_6yeFl|n0nn7v6)69Tqjw3O z@s0<`#g_so!oY@GG5yD_^{~IfzzGy-rE7XZ+FY*EC~7%uUlSt0ORlW90aI(OuWy>B z+jbr!BiOd&BE#dsIEQ#iz>>!5vzvvlh_rj*lm@;w?rFULHMD#X60;TlF+kr$sOf`B zKW_ZGr9i5!mof=75B^RWyK--Y_TAt^-PHPpC8LKRe4MEJt}cy!qWn{Bkc`@_8M^)& zCC-uTxE)_rW6PPZ=A%nnZ6@0xj*1s#!IW+H5C!d!@((cvNnh%Tv+tJtq&tK=%aiQ( zo78%Q0yMc$_kZ>6JoUxXQV5K8a>e7G&Xug2=)q1sU1PhWIVN!r&U`LpgYtnpXJ(q8 z4Nn4jf96&cmpy+PKKPl-s&w)ciPis}L?4dOBN=cWPmHH+7Zte0^{rN|)r9!&sddRr z^zJJ(S9VD#3hU0(DWn1n7qm4h#=6*c&vk7Zf=^7{6gpPBz1>CyO~h{f(yI!HnGivY zkZ=2dS|n%?^lW1gWh&fn_66%DqFDYBm_V;3yge;m39z7M_1JhfCMH-JL|9u^ zj2#?k4=&T$sFfTXfc{6oK&yI6Ot<4jpnTmC1;RM?KXz@?osho?`5yM(3AEm8-ouI; z{kENFhJ5LT%%YdrJonM4Tldk~0d8TvWJE)iK-|0`%<&*;aEFIaFVqz`ymDuO>hHUR z6Fqp_AE7by)-)WKW5Np;HD064hq6L>NT1j)w=*z%sw1s<5%=%X(2t>%g6Q?3GD;NVolBN9}gzEF6)A_Zf z=DVp>%G$pYyP1>~})d;q{uy{=x-d`Mi=@ZHM!sz0j)zVw=8b!eb_ z8Dfp|ap$thwgN%OLeZ(jo3iW_BQ6g_>h zj!W9H-K-0lG%!C|Jaj5V%kmHZn-7lKD3LIPtZ6o*S-ukyUMq^9HE z{65|AIp%IsTN+X1DS9<2B^RBE*HkV!)Dbq|4D@&BQclhZSIsjP>%5wHol5c%D5rVy(md1D3?feyZ0}efQbNzJ@gbqPfDa#1)tefjmE`Rrg1rt+OM9KzZOqb9E z)wg?h32ZeSF1qmgcnZ-Y8Zz4D~4hb~kFf7kl2S8fIlYfD-yB5Z1Bq>06795U^CRpSE@zc~

^u-xO!z+rQ<^a!%u^89+3IXPh zn>7xgO{ToKepYmY)euHxvcx*>>!aEB;rDJm{QVRw8xhO?bJWP+4PZFY-76u<%*Zk$CUrJQa5iT5!?9l39>1uG zdu&8%nP1IpQ!fK;f_+^ikEeZ5h{nfEg?YeR%=qf0Wszffy1a4spC@C%Zg~DG(2S0K zm9bQT{CjI~Q7UsROI1aw$B`th*-m@w5LKSyl%=NYi@dv>NoXd}ck=kX`JSAa%uxdzhPOJ5sHs_t~@`@O55=;84KDchj;c8%9F7V0MhoSIFa z%aXBEvaY=av^C`Q;#x5g%KY zj~+Ty0RDRO(cbtF$k=K6s(Uy|YFvoR!~Fo#i{Jdt@ke|M?Onk57_++%@ta(KVkfd~ zSg&IF?%2fQ7!Lb+odscje9G-sSsU1>acWF|UZji-_mcr8fQHxxAW7&kZq~l0Tj2_k zvv_AbLH6}Z@le_m%;5Z-V>!N?{`owCRbkT#!DB+%1z==SVH2(iXk-@W;xdmt0iif^e;rcZyE)1GES$JKFN?#^y%4!K4$YP$2#$YC3sMKGxr{xi@=ukoUP zs5J+Xhtpb5dTG3Zn3pb!PugSba+~d$)%Xmj88&`xEn_<&Mfza+P{2eheo^gQm`P|P zU3PRYsp#X)%nrLFe#D(vI#s`;t`{C!NC{fYpUy36e7XA4rZg6tO0AHOn$wW4|*;a14iZ!9L_HCNJk9)lV5ZhN!?ZuGl0*)UN&UOw4Ot&7vx9+euSFA zMDkLPNTd|o{35^7w%YaY@AFc^jG5Uw6Sa}D>4f1rLu5rkL9sAZYwo!$i`{z=4}!Z5 zsfQ=OcR=KJH9bQl^1a~aK}n1>$-<<}c*viZIdZe@*HsAxTtZOoNh;XEBNcyD7*t^Y zJ0a9sgUV@$HxtHC3DuZ%hyR_Xy&APf2jFB^xppfO5p!k literal 0 HcmV?d00001 diff --git a/doc/Images/Fig4.png b/doc/Images/Fig4.png new file mode 100644 index 0000000000000000000000000000000000000000..234bbf18952707dd40b4f898272ac447485f7655 GIT binary patch literal 2097 zcmV-12+sG3P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2gXT6K~!i%<(pNg zR9_Uvef32|A4Np5Ma4qw2NN-|3j@W%z*g*T#ZK(*jt9F7yRo~w^^@On&M6`XXBgJGeu)W?)`ts$AK7anKPoF;N{{8#fy?eK|ZQG`K^X6&Lph4=_ub-MXZ?1|JD<-=&Y0^Z! zd-t|}rc9ZljT<*w|JSZv)7!Ui?Y+F8aWEdnl~Tk^1}nr%KYskE7cXAu#EBD{GiQ!^ z_Ux(JwQH+Hi4w|~F{83(&8n(ZtEyJ5T58|Ey+)24sqy2-YsHEcI&k1XvfJ9VYc*-o zBu|7yDoYA8>hJSttfv^sU_q^VP+9FAQOzq68GM-4fAr{)txAUu9aOYvQ6qtE zx^Le;y?XU3@aex3O#JB4qnb8tn(d^51q-Tm>(*MhaG@SNcwqB$BP9VDFn9OvT}_@m z*%)rtteKWATV~99882VHw1w^1v7<_sEUB?$$Ljj^>*nJ^5hMdPw{G34GG)qWz<>ef zp!^7S{P5w!RkmzdEm^WeZ{EBK{3SpJL@ZjgNKKnIwGYPwI0U+O?P{jasRYtsJF8c( zR-HO^%uM15%oo#{J$ttKkTe{bRjO3cjvYG!ABitqx^zhe3KTHuU}R*#?C896=T6`w@d5l}+qP|$EnBufI|KRJw{KsQzKE(*r%suIOO34k z+kx{ud-kkGj~=ZG6)LD*yLL8TbD*nMty05=4ecyOq@XGf9z1B8Bm6A(DhMSWK76R- z$B%2~%$cU4X3UtOR;^lT{`~oW94P->xpKu+8e0VdMvWS!#*G`>dAw%L8XY@!Ot){} z4jw^X!Mk_wEN~=_+OT1R#*G`NMvWS2$dDnreEG7SCWQ(WviTbsdkM5Bc1m=_^C5hDYEX%y_e zd-wGG`E!%QDK$3+N-RV_O!mf&8`l5n)2DU#@L_G=zFq6rueXJpIB}u|4jgFOfpK=} z(#1@HV9qUiB^}V9L4#m`X=Lz0@&%YjQebcwE?m&&&6|zsUcGu*&VlU*NvJydVk@#O zi6&>wnq{j5X~}I!W<(MFFj;me{rBk6!zPV6Ga*t5g!=2&tuvws-;)9d#)JtIf;-#D zVB6pu=s2Yy5@a`ELiDwA73VZ!#E8HXe*=e)vki3%K{9yA5rxz8JJ=0{3l}zrkH2&f zB!h?iF&>;Kf{|=~_(>Eb10Y}X=g)7-%+DaJN1{7o4is>rpf&uS+vwc6v)%cGXZ8}1 zfx1M6;z<3BQl(1iKO70jz=;x+;Y8g68$W=6l2{7= z7>)#F0Awm@K)3Bo7|9y3>YJuS#Z8c+RZP({E0`nF9{c04qVAVmXjMCJa};6iT8n2 zKzT;1GH@jW7OB6J;82|R(0e`MyL~Y(&%0XS$uBN*!7X%N(OlP^r^~~E9aXE z9?CArghyN4h-4tkiTSyQ^*XpIz1q7auAUWN*=kV;LgMAICSWc`9F4hG(;qW z#0L=`8FR0LMdcDIw3y8e5y=R3qTv}KuYn}@rcIk7iY*5b$pBm^aRo;L)vI73DXHdu zIFi_M5S0u98uEq9moE=I@g7i|R2WbhoD`yxK|n*DBsbo4>NOlWazu0I&b7*b&^y&g zh)M=qj7&KN9o(XO4HVK-_f9ncr(;rxN`~V^GiT0hMd@C0q>PmU7Jl+U9ak(DV^PTf zbVA`Mx;}5-yp|FqFF;k`-}LJOCXXp&7L+rR0cXqUOwbz0S{S2t6ju$QP5hfm2B z^(L4`QYT0#Vj{xPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1%gRLK~!i%<(uVC zD{TcMBfe9fAdSclUYoo437z3vG8>AZ_;~n+v_TB%z_9mN`N~LL@9KOh!jX+4ckS*cOo($XUP`}?-5*1_@dvDDSoNkKt@3=R&;-QAr^Yfes%$}I4h z^ylYiIXpbnCz_s~F5~0l8reJqs{pxte0-FFfdPq#h>-sNemOlo)oT|Q7ZMc}CFkeo z`a5uobUuVP86F;%yu3V#kB^t#-CfIQ^?Tgj-fBdrq@<{vAT4(AAjo{KzlUkkd4gCz z(uXu!US9t2K~@17J~=t5L04N_E1R2}pMnl8&(F`-Xnzkwr1Jrg4auZRUtgcD%2tvP zI|#6olM_izO;ro*DCpoOBO^m*XJ45y^u+}z0A+?=KeP6W-eqJ#kMo5GH$j*E*^ z)8GuaH+cIBUy{x$+uhx*sZ&u=p=rb_Y*k{JP1k-WOP(u-CFRWr^EO-4>|C`N&0hID7a8bFy2*?<20QR|nTovlXRsz+8ad)ceynu?zibakcNCWzfkuVF^K%5Qs+6a_%QlqJEF8GIn!u4`RgUA8m+4+1PaJY2oG89+D%5Rzn8l0T3RSQMzh z7%TZZ2(tS6dTn`_1#&`m&|Nf(Po#sDl@%RIxMQ7s3A8zKb8}7Uu^sL0?OMIN7-)wNuRy>3~HCnQ^Bf#g%cz TPJ_+X00000NkvXXu0mjf>yF6I literal 0 HcmV?d00001 diff --git a/doc/Images/Fig6.png b/doc/Images/Fig6.png new file mode 100644 index 0000000000000000000000000000000000000000..b61664694361c8957adf7c40aefd145010494fd0 GIT binary patch literal 2751 zcmaJ@dpHwp8=pf-yv!C$F;-(qnXFzfVUC&O3W>@w(g}qWW}`4u8FE;}UX>gwhek`w zMwVmd_@-h_Yu4*^9K|sDR^NAB-}hbL^?rXm&wc-%-~HUr{XF-5U)MuA?C$u>=53n+ z0KhL!7<&(?WJ<^VO&g{2ev1e1q(Uaz!_gK{&C!~W0{Ji-HyZ$;j;tgJ+91V>7csuk z0Dubn$B@Z0RM`#yZ1!@pxAD5_KV4jNd&p`laaj(yNnSSDS{I1;w}Nojm4a)U|0Mo{ zf1?Xb2mcE%a|X|f^PBwX)A|!df`vCB5lb($Mg(UIbs`6La)6GRloj)PfpBI8h=i=N z=ik_!Exzac6QXl?ECRj8UJ)Zv(_JHh5u<-RI>x1i^^THt&>wd0I0j$G;MZ2hOVEUw zYCncBQ~RLM$p7V^P|J7$6TkKWZ!1V)Q)R9s4c7!PxLm(067eN^L&NyY$EHcE#FZhu zt&bx=vV)2yj54mwjy^d-AFcE(AML!2C>l;kNa)!Y1SaWhJ-3#yG;`pJ%*@77^%%8x zc6`SW0)fY65x&nlG#4i^uKPeq)btsLu_C({oAT}WNNb+M-1^$;Vg)9d9k7+4cGoiD zZLwK&nxtksn_kDgO-w;_15PTgJ_^i_rw;+0FZ$zfO

U?fzD!e*Z`6$S0}N)1HLuUD%RayK`4Gw$-UbsXjY<8vDNywLIGjwQBsDKXtM9{< zT<}T28%1MKeN)quGyO7E8s9&+ldl_?iY#MNd=1ASy=QuwZQ$$HT}N@M5CE#>Bw$o= zzvdzuM9>{KViG%%r>`lJuC6YIua3OP7Ryy`5m}~K*ZQQXT$~nJOO5jMt@ zLGF#QR-&rgJGr8eJo5=X(a!8u2WtlP9YKQyl=IpKLd$S(1iC(zZLn1Gr+nSLcg&5C zNLiTNZuyt~jkxC1jciFly=oR4i>cERm#Ub- zMBstE_+4=?YW7~2)GtSGo6Z)ub?RYp#Dd?bT3KREYo;K}^yYlur=B1?LlRZZOrWam z7aLv6q=p^f7l8bW?544S+b84p)(ER?aj_b@ud>t+HP+h<2l?Tj8HYejAE~>m^W|1M zY9DQdG)teq5RoZhGKZq3$#d)l&-XqLwuuV|7_|yQf7qF`YECu|C|^DvIDcm;)LU9ITQ`Tw|0yjZ zw_ADL8v>8u%o(l`zUiN-F+yUfS0_K=xdS+v@{^hCzw1JN;3Of%Ad_4QjP7t_TdTy@ zx3E1dfkl@9g`jzR?y_Bf@-7AmIKAsh&xWjm#HUvVbC!FI!j*UINi`UZ0`XcmjL|;` z@6f!{Y@Xb}GW$awe6N=Wp@Exv=MW2k3(fWW1>=-@xT|hXfXnD!1T`)|m~9LEYj%&~ z0^p78;5yx0A<%#t&=f!Z?Af!zep;?;S8_z!@-PflCoi(xPoPwV88rtR-z|G7biZRx z2_JaBL{cp^se_A>)BCtwt}r&@l4Vt%WU=MK(0U~$MhGH6<Q*6bdAqIpmi4w2HUQp2>mv1dzRxhf2WE7&<?-P)IL`IYLvH+zQjhmmf3xa+2OrA^c-4J;8?Xa!p z+?d&l{XWA~r?B@uxvl<@wr>1{GxL3wwEBhG)TqGyH$zM7C^2auw2EuYkyhXM7vh0X zn{aFiJL6h$Oe_cu-Gd8a6AL+AS%~p{`mbdt&d#={6AC3yPL*8kVCC%`in5Of%>ucWpclfMxzZfm52s|=O8*2(*_s#e-uDx>AF_xfKpy5wcbc{>s5#feOZq_ zUaM;*zB8NUR?@&L1aH6nBMG^(b%rjP;=@pBUOlT!PGy^;c%_sd=T)%L9XVLddbm|k zHw;qcB2o5n#=A)ST>H7434>t(LWA?Oc!gqXEo{0zncbT{G=E|X^$61YLikFp8HuWC zFkWua_FxFez@|*<*R7ssUFMM%e^zlBErvKOYcZJw6gE;!-1y}7Zfd;K{XCOP2Tm9uzT>!Ns8J9)8aEZ+)%0a=9@q`~>%+nfHWILz+QG?94$tq} zEGoB(!0;imj~dgbTvN-Q-m63%!1+oSiY=jmEgQWnYrXtE<=(;ZHhn{|l2pG@kF4l8 zC&{Zh`dU*{)1v96&W1N|d@_*5F%HYfhLUsuBpnrv>OmpaCUab0q?F1rYQ;SkWZYu& zfFvD_53Bp*JS@r$=}&KqQ(M!HPQBGwZBYUdHrq>@-Y*v`#3^mm?TQ%$Js{q z*JZG&BsN*W?(WUm_RKGxWy*SZhg!>^sjQ78v>ZtX5{uBqTP_Z6jo#%*3ebG)6CBB* zU?e1rL~LC5dhYa;qna<$g%kkW<9oBezf95wSHeh~gD#+B91G?rM-s}5;CNLKTH8Ki zpr6a*MG|OcAc8&!z4t;pWWH1y*$;isu#)eji{uVQ^7Zf}@%RHHQP>XAU~7kaMPDch z4JNUa_jtb#(QzjKh&5ni@VCluril+9|3oV1*33?bE@z8T+2Za`=hxgyo?@17Ica2x z`N*La13K)a^l!3m`=qyI)j*>hd5aDC-u!%haWQta`XrnwCic-9y|cx9fQ5rah%(s$ z&ipNjJbJSU(wl?9^}BQqTxUa$?hrZTVQ@$EKmlH)|AoQsIK8sUvno}=|Ih00e4u3# zOY7m-r3TFPC<~cMcD{JxNG8IXKVoNAl2plG_=v#r!d^UPLRxpxL1TI|4~@7>MD6V> zrnM{n%J)Z|o6QXv?ZlMD8r4;Uw)` (or a doublet for Simulation times) that appear in the condition clauses of control rules. They allow condition clauses to be written as: +``` +variable relation value +variable relation variable +``` +where `variable` is defined on a separate line before its first use in a rule using the format: +``` +VARIABLE name = object id attribute +``` +Here is an example of using this feature: +``` +[RULES] +VARIABLE Dabc = NODE abc DEPTH +VARIABLE Defg = NODE efg DEPTH +VARIABLE P45 = PUMP 45 STATUS + +RULE 1 +IF Dabc > Defg +AND P45 = OFF +THEN PUMP 45 STATUS = ON + +RULE 2 +IF Dabc < 1 +THEN PUMP 45 STATUS = OFF +``` +Aside from saving some typing, named variables are required when using arithmetic expressions in rule condition clauses as described next. +\ +*Arithmetic Expressions* + +In addition to a simple condition placed on a single variable, a control condition clause can now also contain an arithmetic expression formed from several variables whose value is compared against. Thus the format of a condition clause has been extended as follows: +``` +expression relation value +expression relation variable +``` +where `expression` is defined on a separate line before its first use in a rule using the format: +``` +EXPRESSION name = f(variable1, variable2, ...) +``` +The function `f(...)` can be any well-formed mathematical expression containing one or more named variables and any of the same math operators and functions that can be used in a groundwater flow equation of a pollutant treatment function. Here is an example of using this feature: +``` +[RULES] +VARIABLE Q1 = LINK 1 FLOW +VARIABLE Q2 = LINK 2 FLOW +VARIABLE Q3 = Link 3 FLOW +EXPRESSION Net_Inflow = (Q1 + Q2)/2 - Q3 + +RULE 1 +IF Net_Inflow > 0.1 +THEN ORIFICE 3 SETTING = 1 +ELSE ORIFICE 3 SETTING = 0.5 +``` + diff --git a/src/run/main.c b/src/run/main.c index 15f272b9e..20cfda408 100644 --- a/src/run/main.c +++ b/src/run/main.c @@ -101,4 +101,4 @@ int main(int argc, char *argv[]) */ return 0; -} +} \ No newline at end of file diff --git a/src/solver/Roadmap.txt b/src/solver/Roadmap.txt index 11952fad7..a75490ece 100644 --- a/src/solver/Roadmap.txt +++ b/src/solver/Roadmap.txt @@ -1,11 +1,11 @@ A Roadmap to the SWMM 5 Engine Source Code ========================================== -The SWMM 5 computational engine consists of 49 C-code files plus several -header files. The engine can be compiled either as a Dynamic Link Library -(DLL) under Windows or as a stand-alone console application under both -Windows and Linux, depending on which of the #define DLL and #define CLE -declarations at the top of swmm5.c is commented out. +The SWMM 5 computational engine consists of 54 C-code files plus several +header files. The engine should be compiled into a Dynamic Link Library +(DLL) under Windows or to a shared object library under Linux or MacOS. +A main.c file is also provided to build an executable that uses the engine +library to run a complete SWMM simulation from the command line. The following header files contain definitions that are used throughout the code and should be consulted if the meaning of a variable, a data structure, @@ -31,8 +31,8 @@ funcs.h contains prototypes of functions that can be called from any The following modules form the main core of the SWMM 5 engine: -swmm5.c contains functions that provide supervisory control over the - program. +swmm5.c contains a small API with functions that provide supervisory + control over the program. project.c contains functions that create and destroy all project data, establish default values, and look up objects by ID name. @@ -48,8 +48,7 @@ routing.c routes runoff and external inflows through the project's massbal.c performs mass balance calculations for runoff and routing. stats.c collects summary statistics on flow rates, water depths, - solution iterations, and variable time steps for a - simulation. + solution iterations, and variable time steps for a simulation. statsrpt.c writes summary simulation results to a status report. @@ -58,7 +57,7 @@ output.c writes/reads runoff and routing results to/from a binary report.c prepares a status report of simulation results and, for the command line version of SWMM 5, reports complete results for - selected subcatchments, nodes, and links. + selected subcatchments, nodes, and links. inputrpt.c writes a summary of a project's input data to the status report. @@ -103,11 +102,14 @@ flowrout.c implements top-level control of flow routing through a project's inflow.c provides direct time series inflows and recurring dry weather inflows to the drainage system's nodes at each step of the - simulation. + simulation. rdii.c computes rainfall dependent infiltration/inflow at selected nodes of the drainage network. +inlet.c computes flow captured by street inlet drains that is diverted to + sewer nodes. + kinwave.c performs kinematic wave flow routing calculations at each time step of the simulation. @@ -125,7 +127,7 @@ qualrout.c performs routing of water quality constituents through the treatmnt.c computes pollutant removal at specific nodes of the drainage system where user-defined treatment functions have been - assigned. + assigned. node.c contains functions used to compute the properties and behavior of the drainage system's nodes which include junctions, flow @@ -175,6 +177,8 @@ odesolve.c implementation of a fifth-order Runge-Kutta ordinary shape.c functions that compute the geometric cross-section properties of closed conduits with user-defined shapes. +street.c reads the geometric properties of a street cross section. + table.c functions used for accessing lookup tables that contain SWMM 5's curve data and time series data. diff --git a/src/solver/climate.c b/src/solver/climate.c index a44c5faae..e235f6798 100644 --- a/src/solver/climate.c +++ b/src/solver/climate.c @@ -2,33 +2,26 @@ // climate.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/10 (Build 5.1.001) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 08/05/15 (Build 5.1.010) -// 08/01/16 (Build 5.1.011) -// 05/10/18 (Build 5.1.013) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Climate related functions. // +// Update History +// ============== // Build 5.1.007: // - NCDC GHCN climate file format added. // - Monthly adjustments for temperature, evaporation & rainfall added. -// // Build 5.1.008: // - Monthly adjustments for hyd. conductivity added. // - Time series evaporation rates can now vary within a day. // - Evaporation rates are now properly updated when only flow routing // is being simulated. -// // Build 5.1.010: // - Hargreaves evaporation now computed using 7-day average temperatures. -// // Build 5.1.011: // - Monthly adjustment for hyd. conductivity <= 0 is ignored. -// // Build 5.1.013: // - Reads names of monthly adjustment patterns for various parameters // of a subcatchment from the [ADJUSTMENTS] section of input file. @@ -373,11 +366,11 @@ int climate_readAdjustments(char* tok[], int ntoks) // EVAPORATION v1 ... v12 // RAINFALL v1 ... v12 // CONDUCTIVITY v1 ... v12 -// N-PERV subcatchID patternID //(5.1.013 -// DSTORE subcatchID patternID // -// INFIL subcatchID patternID // +// N-PERV subcatchID patternID +// DSTORE subcatchID patternID +// INFIL subcatchID patternID { - int i, j; //(5.1.013) + int i, j; if (ntoks == 1) return 0; @@ -426,7 +419,6 @@ int climate_readAdjustments(char* tok[], int ntoks) return 0; } -//// Following code segments added to release 5.1.013. //// //(5.1.013) if ( match(tok[0], "N-PERV") ) { if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); diff --git a/src/solver/consts.h b/src/solver/consts.h index 829b089fd..4ddf70f84 100644 --- a/src/solver/consts.h +++ b/src/solver/consts.h @@ -2,12 +2,8 @@ // consts.h // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 08/01/16 (Build 5.1.011) -// 05/10/18 (Build 5.1.013) -// 03/01/20 (Build 5.1.014) -// 04/01/20 (Build 5.1.015) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Various Constants @@ -16,12 +12,11 @@ #ifndef CONSTS_H #define CONSTS_H - //------------------ // General Constants //------------------ -#define VERSION 51015 +#define VERSION 52000 #define MAGICNUMBER 516114522 #define EOFMARK 0x1A // Use 0x04 for UNIX systems #define MAXTITLE 3 // Max. # title lines @@ -98,8 +93,8 @@ //--------------------------- // Token separator characters -//--------------------------- -#define SEPSTR " \t\n\r" +//--------------------------- +#define SEPSTR " \t\n\r" #endif //CONSTS_H diff --git a/src/solver/controls.c b/src/solver/controls.c index 33e1b2155..38c56c8fa 100644 --- a/src/solver/controls.c +++ b/src/solver/controls.c @@ -2,12 +2,8 @@ // controls.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/21/14 (Build 5.1.001) -// 03/19/15 (Build 5.1.008) -// 04/30/15 (Build 5.1.009) -// 08/05/15 (Build 5.1.010) -// 08/01/16 (Build 5.1.011) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Rule-based controls functions. @@ -36,20 +32,22 @@ // E.g.: Pump abc status = OFF // Weir xyz setting = 0.5 // +// Update History +// ============== // Build 5.1.008: // - Support added for r.h.s. variables in rule premises. // - Node volume added as a premise variable. -// // Build 5.1.009: // - Fixed problem with parsing a RHS premise variable. -// // Build 5.1.010: // - Support added for link TIMEOPEN & TIMECLOSED premises. -// // Build 5.1.011: // - Support added for DAYOFYEAR attribute. // - Modulated controls no longer included in reported control actions. -// +// Build 5.2.0: +// - Additional attributes added to condition clauses. +// - Support added for named variables in condition clauses. +// - Support added for math expressions in condition clauses. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -62,26 +60,31 @@ // Constants //----------------------------------------------------------------------------- enum RuleState {r_RULE, r_IF, r_AND, r_OR, r_THEN, r_ELSE, r_PRIORITY, - r_ERROR}; -enum RuleObject {r_NODE, r_LINK, r_CONDUIT, r_PUMP, r_ORIFICE, r_WEIR, - r_OUTLET, r_SIMULATION}; -enum RuleAttrib {r_DEPTH, r_HEAD, r_VOLUME, r_INFLOW, r_FLOW, r_STATUS, - r_SETTING, r_TIMEOPEN, r_TIMECLOSED, r_TIME, r_DATE, - r_CLOCKTIME, r_DAYOFYEAR, r_DAY, r_MONTH}; + r_VARIABLE, r_EXPRESSION, r_ERROR}; +enum RuleObject {r_GAGE, r_NODE, r_LINK, r_CONDUIT, r_PUMP, r_ORIFICE, + r_WEIR, r_OUTLET, r_SIMULATION}; +enum RuleAttrib {r_DEPTH, r_MAXDEPTH, r_HEAD, r_VOLUME, r_INFLOW, + r_FLOW, r_FULLFLOW, r_FULLDEPTH, r_STATUS, r_SETTING, + r_LENGTH, r_SLOPE, r_VELOCITY, r_TIMEOPEN, r_TIMECLOSED, + r_TIME, r_DATE, r_CLOCKTIME, r_DAYOFYEAR, r_DAY, r_MONTH}; enum RuleRelation {EQ, NE, LT, LE, GT, GE}; enum RuleSetting {r_CURVE, r_TIMESERIES, r_PID, r_NUMERIC}; +#define MAXVNAME 32 + static char* ObjectWords[] = - {"NODE", "LINK", "CONDUIT", "PUMP", "ORIFICE", "WEIR", "OUTLET", - "SIMULATION", NULL}; + {"GAGE", "NODE", "LINK", "CONDUIT", "PUMP", "ORIFICE", "WEIR", "OUTLET", + "SIMULATION", NULL}; static char* AttribWords[] = - {"DEPTH", "HEAD", "VOLUME", "INFLOW", "FLOW", "STATUS", "SETTING", - "TIMEOPEN", "TIMECLOSED","TIME", "DATE", "CLOCKTIME", "DAYOFYEAR", - "DAY", "MONTH", NULL}; + {"DEPTH", "MAXDEPTH", "HEAD", "VOLUME", "INFLOW", + "FLOW", "FULLFLOW", "FULLDEPTH", "STATUS", "SETTING", + "LENGTH", "SLOPE", "VELOCITY", "TIMEOPEN", "TIMECLOSED", + "TIME", "DATE", "CLOCKTIME", "DAYOFYEAR", "DAY", "MONTH", NULL}; static char* RelOpWords[] = {"=", "<>", "<", "<=", ">", ">=", NULL}; static char* StatusWords[] = {"OFF", "ON", NULL}; static char* ConduitWords[] = {"CLOSED", "OPEN", NULL}; static char* SettingTypeWords[] = {"CURVE", "TIMESERIES", "PID", NULL}; +static char* IntensityWord = "INTENSITY"; //----------------------------------------------------------------------------- // Data Structures @@ -89,15 +92,30 @@ static char* SettingTypeWords[] = {"CURVE", "TIMESERIES", "PID", NULL}; // Rule Premise Variable struct TVariable { - int node; // index of a node (-1 if N/A) - int link; // index of a link (-1 if N/A) - int attribute; // type of attribute for node/link + int object; // type of object + int index; // index in object's array + int attribute; // object's attribute +}; + +// Named Variable +struct TNamedVariable +{ + struct TVariable variable; // a rule premise variable + char name[MAXVNAME+1]; // name used in math expression +}; + +// Rule Premise Function +struct TExpression +{ + MathExpr* expression; // tokenized math expression + char name[MAXVNAME]; // expression name }; // Rule Premise Clause struct TPremise { int type; // clause type (IF/AND/OR) + int exprIndex; // expression index (-1 if N/A) struct TVariable lhsVar; // left hand side variable struct TVariable rhsVar; // right hand side variable int relation; // relational operator (>, <, =, etc) @@ -149,11 +167,22 @@ double SetPoint; // value of controller setpoint DateTime CurrentDate; // current date in whole days DateTime CurrentTime; // current time of day (decimal) +int VariableCount; +int ExpressionCount; +int CurrentVariable; +int CurrentExpression; +struct TNamedVariable* NamedVariable; // array of named variables +struct TExpression* Expression; // array of math expressions + //----------------------------------------------------------------------------- // External functions (declared in funcs.h) //----------------------------------------------------------------------------- // controls_create // controls_delete +// controls_init +// controls_addToCount +// controls_addVariable +// controls_addExpression // controls_addRuleClause // controls_evaluate @@ -161,7 +190,7 @@ DateTime CurrentTime; // current time of day (decimal) // Local functions //----------------------------------------------------------------------------- int addPremise(int r, int type, char* Tok[], int nToks); -int getPremiseVariable(char* tok[], int* k, struct TVariable* v); +int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v); int getPremiseValue(char* token, int attrib, double* value); int addAction(int r, char* Tok[], int nToks); @@ -183,6 +212,43 @@ int setActionSetting(char* tok[], int nToks, int* curve, int* tseries, void updateActionValue(struct TAction* a, DateTime currentTime, double dt); double getPIDSetting(struct TAction* a, double dt); +int getVariableIndex(char* varName); +double getNamedVariableValue(int varIndex); +int getExpressionIndex(char* exprName); +int getGageAttrib(char* token); +double getRainValue(struct TVariable v); + +//============================================================================= + +void controls_init() +// +// Input: none +// Output: none +// Purpose: initializes the control rule system. +// +{ + Rules = NULL; + NamedVariable = NULL; + Expression = NULL; + RuleCount = 0; + VariableCount = 0; + ExpressionCount = 0; +} + +//============================================================================= + +void controls_addToCount(char* s) +// +// Input: s = either VARIABLE or EXPRESSION +// Output: none +// Purpose: updates the number of named variables or math expressions used +// by control rules. +// +{ + if (match(s, w_VARIABLE)) VariableCount++; + else if (match(s, w_EXPRESSION)) ExpressionCount++; +} + //============================================================================= int controls_create(int n) @@ -192,21 +258,38 @@ int controls_create(int n) // Purpose: creates an array of control rules. // { - int r; - ActionList = NULL; - InputState = r_PRIORITY; - RuleCount = n; - if ( n == 0 ) return 0; - Rules = (struct TRule *) calloc(RuleCount, sizeof(struct TRule)); - if (Rules == NULL) return ERR_MEMORY; - for ( r=0; r 0) + { + Rules = (struct TRule *) calloc(RuleCount, sizeof(struct TRule)); + if (Rules == NULL) return ERR_MEMORY; + for ( r=0; r 0) + { + NamedVariable = (struct TNamedVariable *) calloc(VariableCount, + sizeof(struct TNamedVariable)); + if (NamedVariable == NULL) return ERR_MEMORY; + } + if (ExpressionCount > 0) { - Rules[r].ID = NULL; - Rules[r].firstPremise = NULL; - Rules[r].lastPremise = NULL; - Rules[r].thenActions = NULL; - Rules[r].elseActions = NULL; - Rules[r].priority = 0.0; + Expression = (struct TExpression *) calloc(ExpressionCount, + sizeof(struct TExpression)); + if (Expression == NULL) return ERR_MEMORY; } return 0; } @@ -220,6 +303,16 @@ void controls_delete(void) // Purpose: deletes all control rules. // { + int i; + + for (i = 0; i < ExpressionCount; i++) + { + mathexpr_delete(Expression[i].expression); + Expression[i].expression = NULL; + } + FREE(Expression); + FREE(NamedVariable); + if ( RuleCount == 0 ) return; deleteActionList(); deleteRules(); @@ -227,6 +320,120 @@ void controls_delete(void) //============================================================================= +int controls_addVariable(char* tok[], int nToks) +// +// Input: tok = an array of string tokens +// n = the size of tok[] +// Output: returns error code +// Purpose: adds a named variable to the control rule system from a +// tokenized line of input with formats: +// VARIABLE name = Object id attribute +// VARIABLE name = SIMULATION attribute +// +{ + struct TVariable v1; + int k, err; + + CurrentVariable++; + if (nToks < 5) return ERR_ITEMS; + if (findExactMatch(tok[1], AttribWords) >= 0) + return error_setInpError(ERR_KEYWORD, tok[1]); + if (!match(tok[2], "=")) return error_setInpError(ERR_KEYWORD, tok[2]); + if (!match(tok[3], "SIMULATION") && nToks < 6) return ERR_ITEMS; + k = 3; + err = getPremiseVariable(tok, nToks, &k, &v1); + if (err > 0) return err; + k = CurrentVariable; + NamedVariable[k].variable = v1; + strncpy(NamedVariable[k].name, tok[1], MAXVNAME); + return 0; +} + +//============================================================================= + +int controls_addExpression(char* tok[], int nToks) +// +// Input: tok = an array of string tokens +// n = number of tokens +// Output: returns error code +// Purpose: adds a math expression to the control rule system from a +// a tokenized line of input with format: +// EXPRESSION name = +// +{ + int i, k; + char s[MAXLINE + 1]; + MathExpr* expr; + + CurrentExpression++; + if (nToks < 4) return ERR_ITEMS; + k = CurrentExpression; + Expression[k].expression = NULL; + strncpy(Expression[k].name, tok[1], MAXVNAME); + + strcpy(s, tok[3]); + for (i = 4; i < nToks; i++) + { + strcat(s, " "); + strcat(s, tok[i]); + } + + expr = mathexpr_create(s, getVariableIndex); + if (expr == NULL) + return error_setInpError(ERR_MATH_EXPR, ""); + + Expression[k].expression = expr; + return 0; +} + +//============================================================================= + +int getVariableIndex(char* varName) +// +// Input: varName = string containing a variable name +// Output: returns the index of the named variable or -1 if not found +// Purpose: finds the array index of a named variable. +// +{ + int i; + for (i = 0; i < VariableCount; i++) + { + if (match(varName, NamedVariable[i].name)) return i; + } + return -1; +} + +//============================================================================= + +double getNamedVariableValue(int varIndex) +// +// Input: varIndex = index of a named variable +// Output: returns the current value of the variable +// Purpose: finds the value of a named variable. +// +{ + return getVariableValue(NamedVariable[varIndex].variable); +} + +//============================================================================= + +int getExpressionIndex(char* exprName) +// +// Input: exprName = string containing an expression name +// Output: returns the index of the expression or -1 if not found +// Purpose: finds the array index of a math expression +// +{ + int i; + for (i = 0; i < ExpressionCount; i++) + { + if (match(exprName, Expression[i].name)) return i; + } + return -1; +} + +//============================================================================= + int controls_addRuleClause(int r, int keyword, char* tok[], int nToks) // // Input: r = rule index @@ -359,53 +566,84 @@ int addPremise(int r, int type, char* tok[], int nToks) struct TPremise* p; struct TVariable v1; struct TVariable v2; + int obj, exprIndex, varIndex = -1; - // --- check for minimum number of tokens - if ( nToks < 5 ) return ERR_ITEMS; - - // --- get LHS variable + // --- initialize LHS variable v1 + if (nToks < 4) return ERR_ITEMS; + v1.attribute = -1; + v1.object = -1; + v1.index = -1; n = 1; - err = getPremiseVariable(tok, &n, &v1); - if ( err > 0 ) return err; + + // --- check if 2nd token is a math expression + exprIndex = getExpressionIndex(tok[1]); + + // --- if not then check if it's a named variable + if (exprIndex < 0) + { + varIndex = getVariableIndex(tok[n]); + if (varIndex >= 0) + { + v1 = NamedVariable[varIndex].variable; + } + + // otherwise parse object|index|attribute tokens + else + { + err = getPremiseVariable(tok, nToks, &n, &v1); + if ( err > 0 ) return err; + } + } // --- get relational operator n++; + if ( n >= nToks ) return error_setInpError(ERR_ITEMS, ""); relation = findExactMatch(tok[n], RelOpWords); if ( relation < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); - n++; - // --- initialize RHS variable + // --- initialize RHS variable v2 v2.attribute = -1; - v2.link = -1; - v2.node = -1; + v2.object = -1; + v2.index = -1; + n++; + if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); - // --- check that more tokens remain - if ( n >= nToks ) return error_setInpError(ERR_ITEMS, ""); - - // --- see if a RHS variable is supplied - if ( findmatch(tok[n], ObjectWords) >= 0 && n + 3 >= nToks ) + // --- check for named RHS variable + varIndex = getVariableIndex(tok[n]); + if (varIndex >= 0) { - err = getPremiseVariable(tok, &n, &v2); - if ( err > 0 ) return ERR_RULE; - if ( v1.attribute != v2.attribute) - report_writeWarningMsg(WARN11, Rules[r].ID); + v2 = NamedVariable[varIndex].variable; } - // --- otherwise get value to which LHS variable is compared to + // --- check for object|index|attribute variable else { - err = getPremiseValue(tok[n], v1.attribute, &value); - n++; + obj = findmatch(tok[n], ObjectWords); + if (obj >= 0) + { + err = getPremiseVariable(tok, nToks, &n, &v2); + if ( err > 0 ) return ERR_RULE; + if (exprIndex < 0 && v1.attribute != v2.attribute) + report_writeWarningMsg(WARN11, Rules[r].ID); + } + + // --- check for a single RHS value + else + { + err = getPremiseValue(tok[n], v1.attribute, &value); + if ( err > 0 ) return err; + } } - if ( err > 0 ) return err; // --- make sure another clause is not on same line + n++; if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE; // --- create the premise object p = (struct TPremise *) malloc(sizeof(struct TPremise)); if ( !p ) return ERR_MEMORY; p->type = type; + p->exprIndex = exprIndex; p->lhsVar = v1; p->rhsVar = v2; p->relation = relation; @@ -425,19 +663,19 @@ int addPremise(int r, int type, char* tok[], int nToks) //============================================================================= -int getPremiseVariable(char* tok[], int* k, struct TVariable* v) +int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v) // -// Input: tok = array of string tokens containing premise statement +// Input: tok = array of string tokens +// nToks = number of tokens // k = index of current token // Output: returns an error code; updates k to new current token and // places identity of specified variable in v -// Purpose: parses a variable (e.g., Node 123 Depth) specified in a -// premise clause of a control rule. +// Purpose: parses a variable (e.g., Node 123 Depth) used in a control rule. // { int n = *k; - int node = -1; - int link = -1; + int object = -1; + int index = -1; int obj, attrib; // --- get object type @@ -446,11 +684,19 @@ int getPremiseVariable(char* tok[], int* k, struct TVariable* v) // --- get object index from its name n++; + if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); switch (obj) { + case r_GAGE: + index = project_findObject(GAGE, tok[n]); + if (index < 0) return error_setInpError(ERR_NAME, tok[n]); + object = r_GAGE; + break; + case r_NODE: - node = project_findObject(NODE, tok[n]); - if ( node < 0 ) return error_setInpError(ERR_NAME, tok[n]); + index = project_findObject(NODE, tok[n]); + if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]); + object = r_NODE; break; case r_LINK: @@ -459,21 +705,32 @@ int getPremiseVariable(char* tok[], int* k, struct TVariable* v) case r_ORIFICE: case r_WEIR: case r_OUTLET: - link = project_findObject(LINK, tok[n]); - if ( link < 0 ) return error_setInpError(ERR_NAME, tok[n]); + index = project_findObject(LINK, tok[n]); + if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]); + object = r_LINK; break; default: n--; } n++; + if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); // --- get attribute index from its name - attrib = findmatch(tok[n], AttribWords); + if (object == r_GAGE) + attrib = getGageAttrib(tok[n]); + else + attrib = findmatch(tok[n], AttribWords); if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); // --- check that attribute belongs to object type - if ( obj == r_NODE ) switch (attrib) + if (obj == r_GAGE) + { + + } + + else if ( obj == r_NODE ) switch (attrib) { case r_DEPTH: + case r_MAXDEPTH: case r_HEAD: case r_VOLUME: case r_INFLOW: break; @@ -481,30 +738,36 @@ int getPremiseVariable(char* tok[], int* k, struct TVariable* v) } // --- check for link TIMEOPEN & TIMECLOSED attributes - else if ( link >= 0 && - ( (attrib == r_TIMEOPEN || - attrib == r_TIMECLOSED) + else if ( object == r_LINK && index >= 0 && + ( (attrib == r_TIMEOPEN || attrib == r_TIMECLOSED) )) { - + // nothing to do here } else if ( obj == r_LINK || obj == r_CONDUIT ) switch (attrib) { case r_STATUS: case r_DEPTH: - case r_FLOW: break; + case r_FULLFLOW: + case r_FULLDEPTH: + case r_FLOW: + case r_LENGTH: + case r_SLOPE: + case r_VELOCITY: break; default: return error_setInpError(ERR_KEYWORD, tok[n]); } else if ( obj == r_PUMP ) switch (attrib) { case r_FLOW: + case r_SETTING: case r_STATUS: break; default: return error_setInpError(ERR_KEYWORD, tok[n]); } else if ( obj == r_ORIFICE || obj == r_WEIR || obj == r_OUTLET ) switch (attrib) { + case r_FLOW: case r_SETTING: break; default: return error_setInpError(ERR_KEYWORD, tok[n]); } @@ -520,8 +783,8 @@ int getPremiseVariable(char* tok[], int* k, struct TVariable* v) } // --- populate variable structure - v->node = node; - v->link = link; + v->object = object; + v->index = index; v->attribute = attrib; *k = n; return 0; @@ -529,6 +792,33 @@ int getPremiseVariable(char* tok[], int* k, struct TVariable* v) //============================================================================= +int getGageAttrib(char* token) +// +// Input: token = a string token +// Output: returns an attribute code or -1 if an error occurred +// Purpose: determines the atrribute code for a rain gage variable. +// Note: a valid token is INTENSITY for current rainfall intensity +// (attribute code = 0) or nHR_PRECIP for total rain depth +// over past n hours (attribute code = n). +// +{ + int attrib; + + // --- check if token is currrent rainfall intensity + if (match(token, IntensityWord)) + return 0; + + // --- token is past rain depth - read number of past hours + attrib = atoi(token); + + // --- check that number of hours is in allowable range + if (attrib < 1 || attrib > 24) + return -1; + return attrib; +} + +//============================================================================= + int getPremiseValue(char* token, int attrib, double* value) // // Input: token = a string token @@ -544,7 +834,7 @@ int getPremiseValue(char* token, int attrib, double* value) { case r_STATUS: *value = findmatch(token, StatusWords); - if ( *value < 0.0 ) *value = findmatch(token, ConduitWords); + if ( *value < 0.0 ) *value = findmatch(token, ConduitWords); if ( *value < 0.0 ) return error_setInpError(ERR_KEYWORD, token); break; @@ -626,9 +916,9 @@ int addAction(int r, char* tok[], int nToks) switch (obj) { case r_CONDUIT: - if ( Link[link].type != CONDUIT ) - return error_setInpError(ERR_NAME, tok[2]); - break; + if ( Link[link].type != CONDUIT ) + return error_setInpError(ERR_NAME, tok[2]); + break; case r_PUMP: if ( Link[link].type != PUMP ) return error_setInpError(ERR_NAME, tok[2]); @@ -830,11 +1120,11 @@ double getPIDSetting(struct TAction* a, double dt) // a->e2 = error from two time steps ago { double e0, setting; - double p, i, d, update; - double tolerance = 0.0001; + double p, i, d, update; + double tolerance = 0.0001; - // --- convert time step from days to minutes - dt *= 1440.0; + // --- convert time step from days to minutes + dt *= 1440.0; // --- determine relative error in achieving controller set point e0 = SetPoint - ControlValue; @@ -844,24 +1134,24 @@ double getPIDSetting(struct TAction* a, double dt) else e0 = e0/ControlValue; } - // --- reset previous errors to 0 if controller gets stuck - if (fabs(e0 - a->e1) < tolerance) - { - a->e2 = 0.0; - a->e1 = 0.0; - } + // --- reset previous errors to 0 if controller gets stuck + if (fabs(e0 - a->e1) < tolerance) + { + a->e2 = 0.0; + a->e1 = 0.0; + } // --- use the recursive form of the PID controller equation to // determine the new setting for the controlled link - p = (e0 - a->e1); - if ( a->ki == 0.0 ) i = 0.0; - else i = e0 * dt / a->ki; - d = a->kd * (e0 - 2.0*a->e1 + a->e2) / dt; - update = a->kp * (p + i + d); - if ( fabs(update) < tolerance ) update = 0.0; - setting = Link[a->link].targetSetting + update; - - // --- update previous errors + p = (e0 - a->e1); + if ( a->ki == 0.0 ) i = 0.0; + else i = e0 * dt / a->ki; + d = a->kd * (e0 - 2.0*a->e1 + a->e2) / dt; + update = a->kp * (p + i + d); + if ( fabs(update) < tolerance ) update = 0.0; + setting = Link[a->link].targetSetting + update; + + // --- update previous errors a->e2 = a->e1; a->e1 = e0; @@ -959,10 +1249,21 @@ int evaluatePremise(struct TPremise* p, double tStep) double lhsValue, rhsValue; int result = FALSE; - lhsValue = getVariableValue(p->lhsVar); + // --- check if left hand side (lhs) of premise is an expression + if (p->exprIndex >= 0) + lhsValue = mathexpr_eval(Expression[p->exprIndex].expression, + getNamedVariableValue); + + // --- otherwise get value of the lhs variable + else + lhsValue = getVariableValue(p->lhsVar); + + // --- if right hand side (rhs) of premise is a variable then get its value if ( p->value == MISSING ) rhsValue = getVariableValue(p->rhsVar); else rhsValue = p->value; if ( lhsValue == MISSING || rhsValue == MISSING ) return FALSE; + + // --- compare the lhs of the premise to the rhs switch (p->lhsVar.attribute) { case r_TIME: @@ -982,8 +1283,13 @@ int evaluatePremise(struct TPremise* p, double tStep) double getVariableValue(struct TVariable v) { - int i = v.node; - int j = v.link; + int i = -1; // a node index + int j = -1; // a link index + + if (v.object == r_GAGE) + return getRainValue(v); + if (v.object == r_NODE) i = v.index; + if (v.object == r_LINK) j = v.index; switch ( v.attribute ) { @@ -1011,7 +1317,9 @@ double getVariableValue(struct TVariable v) else return Link[j].setting; case r_SETTING: - if ( j < 0 || (Link[j].type != ORIFICE && Link[j].type != WEIR) ) + if ( j < 0 || (Link[j].type != PUMP && + Link[j].type != ORIFICE && + Link[j].type != WEIR) ) return MISSING; else return Link[j].setting; @@ -1019,12 +1327,34 @@ double getVariableValue(struct TVariable v) if ( j < 0 ) return MISSING; else return Link[j].direction*Link[j].newFlow*UCF(FLOW); + case r_FULLFLOW: + case r_FULLDEPTH: + case r_VELOCITY: + case r_LENGTH: + case r_SLOPE: + if ( j < 0 ) return MISSING; + else if (Link[j].type != CONDUIT) return MISSING; + switch (v.attribute) + { + case r_FULLFLOW: return Link[j].qFull * UCF(FLOW); + case r_FULLDEPTH: return Link[j].xsect.yFull * UCF(LENGTH); + case r_VELOCITY: + return link_getVelocity(j, Link[j].newFlow, Link[j].newDepth) + * UCF(LENGTH); + case r_LENGTH: return Conduit[Link[j].subIndex].length * UCF(LENGTH); + case r_SLOPE: return Conduit[Link[j].subIndex].slope; + default: return MISSING; + } case r_DEPTH: if ( j >= 0 ) return Link[j].newDepth*UCF(LENGTH); else if ( i >= 0 ) return Node[i].newDepth*UCF(LENGTH); else return MISSING; + case r_MAXDEPTH: + if (i >= 0) return Node[i].fullDepth*UCF(LENGTH); + else return MISSING; + case r_HEAD: if ( i < 0 ) return MISSING; return (Node[i].newDepth + Node[i].invertElev) * UCF(LENGTH); @@ -1053,6 +1383,23 @@ double getVariableValue(struct TVariable v) //============================================================================= +double getRainValue(struct TVariable v) +// +// Input: v = a rule premise variable for a rain gage +// Output: returns current or past rainfall amount +// Purpose: retrieves either the current rainfall intensity or the past +// rainfall total for a rain gage. +// +{ + if (v.index < 0) return MISSING; + else if (Gage[v.index].isUsed == FALSE) return 0.0; + else if (v.attribute == 0) + return Gage[v.index].rainfall; + else return gage_getPastRain(v.index, v.attribute); +} + +//============================================================================= + int compareTimes(double lhsValue, int relation, double rhsValue, double halfStep) // // Input: lhsValue = date/time value on left hand side of relation diff --git a/src/solver/culvert.c b/src/solver/culvert.c index 37c7c8d56..aa8d67de0 100644 --- a/src/solver/culvert.c +++ b/src/solver/culvert.c @@ -2,9 +2,8 @@ // culvert.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 05/10/18 (Build 5.1.013) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Culvert equations for SWMM5 @@ -12,6 +11,8 @@ // Computes flow reduction in a culvert-type conduit due to // inlet control using equations from the FHWA HEC-5 circular. // +// Update History +// ============== // Build 5.1.013: // - C parameter corrected for Arch, Corrugated Metal, Mitered culvert. //----------------------------------------------------------------------------- @@ -108,7 +109,7 @@ static const double Params[58][5] = { // Arch, Corrugated Metal {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall - {1.0, 0.0300, 1.00, 0.0473, 0.75}, //Mitered to slope //(5.1.013) + {1.0, 0.0300, 1.00, 0.0473, 0.75}, //Mitered to slope {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Thin wall projecting // Circular Culvert diff --git a/src/solver/datetime.c b/src/solver/datetime.c index e139ff207..efa104172 100644 --- a/src/solver/datetime.c +++ b/src/solver/datetime.c @@ -2,17 +2,17 @@ // datetime.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 08/01/16 (Build 5.1.011) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // DateTime functions. // -// Build 5.1.011 +// Update History +// ============== +// Build 5.1.011: // - decodeTime() no longer rounds up. // - New getTimeStamp function added. -// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE diff --git a/src/solver/datetime.h b/src/solver/datetime.h index 8c84b3b79..4d1dd6a8f 100644 --- a/src/solver/datetime.h +++ b/src/solver/datetime.h @@ -2,9 +2,8 @@ // datetime.h // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 08/01/16 (Build 5.1.011) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // The DateTime type is used to store date and time values. It is @@ -14,7 +13,9 @@ // passed since 12/31/1899. The fractional part of a DateTime value is the // fraction of a 24 hour day that has elapsed. // -// Build 5.1.011 +// Update History +// ============== +// Build 5.1.011: // - New getTimeStamp function added. //----------------------------------------------------------------------------- diff --git a/src/solver/dwflow.c b/src/solver/dwflow.c index de096ad38..eda519236 100644 --- a/src/solver/dwflow.c +++ b/src/solver/dwflow.c @@ -2,29 +2,24 @@ // dwflow.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 03/19/15 (Build 5.1.008) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// 03/01/20 (Build 5.1.014) -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // M. Tryby (EPA) // R. Dickinson (CDM) // // Solves the momentum equation for flow in a conduit under dynamic wave // flow routing. // +// Update History +// ============== // Build 5.1.008: // - Bug in finding if conduit was upstrm/dnstrm full was fixed. -// // Build 5.1.012: // - Modified uniform loss rate term of conduit momentum equation. -// // Build 5.1.013: // - Preissmann slot surcharge option implemented. // - Changed sign of uniform loss rate term (dq6) in flow updating equation. -// // Build 5.1.014: // - Conduit evap. and seepage loss initialized to 0 in dwflow_findConduitFlow. // - Most current flow (qLast) used instead of previous time period flow @@ -46,8 +41,8 @@ static double findLocalLosses(int link, double a1, double a2, double aMid, double q); static double getWidth(TXsect* xsect, double y); -static double getSlotWidth(TXsect* xsect, double y); //(5.1.013) -static double getArea(TXsect* xsect, double y, double wSlot); //(5.1.013) +static double getSlotWidth(TXsect* xsect, double y); +static double getArea(TXsect* xsect, double y, double wSlot); static double getHydRad(TXsect* xsect, double y); static double checkNormalFlow(int j, double q, double y1, double y2, @@ -82,7 +77,7 @@ void dwflow_findConduitFlow(int j, int steps, double omega, double dt) double rho; // upstream weighting factor double sigma; // inertial damping factor double length; // effective conduit length (ft) - double wSlot; // Preissmann slot width (ft) //(5.1.013) + double wSlot; // Preissmann slot width (ft) double dq1, dq2, dq3, dq4, dq5, // terms in momentum eqn. dq6; // term for evap and infil losses double denom; // denominator of flow update formula @@ -102,8 +97,8 @@ void dwflow_findConduitFlow(int j, int steps, double omega, double dt) barrels = Conduit[k].barrels; qOld = Link[j].oldFlow / barrels; qLast = Conduit[k].q1; - Conduit[k].evapLossRate = 0.0; //(5.1.014) - Conduit[k].seepLossRate = 0.0; //(5.1.014) + Conduit[k].evapLossRate = 0.0; + Conduit[k].seepLossRate = 0.0; // --- get most current heads at upstream and downstream ends of conduit n1 = Link[j].node1; @@ -123,7 +118,7 @@ void dwflow_findConduitFlow(int j, int steps, double omega, double dt) y2 = MAX(y2, FUDGE); // --- flow depths can't exceed full depth of conduit if slot not used - if ( SurchargeMethod != SLOT ) //(5.1.013) + if ( SurchargeMethod != SLOT ) { y1 = MIN(y1, xsect->yFull); y2 = MIN(y2, xsect->yFull); @@ -141,16 +136,16 @@ void dwflow_findConduitFlow(int j, int steps, double omega, double dt) findSurfArea(j, qLast, length, &h1, &h2, &y1, &y2); // --- compute area at each end of conduit & hyd. radius at upstream end - wSlot = getSlotWidth(xsect, y1); //(5.1.013) - a1 = getArea(xsect, y1, wSlot); //(5.1.013) + wSlot = getSlotWidth(xsect, y1); + a1 = getArea(xsect, y1, wSlot); r1 = getHydRad(xsect, y1); - wSlot = getSlotWidth(xsect, y2); //(5.1.013) - a2 = getArea(xsect, y2, wSlot); //(5.1.013) + wSlot = getSlotWidth(xsect, y2); + a2 = getArea(xsect, y2, wSlot); // --- compute area & hyd. radius at midpoint yMid = 0.5 * (y1 + y2); - wSlot = getSlotWidth(xsect, yMid); //(5.1.013) - aMid = getArea(xsect, yMid, wSlot); //(5.1.013) + wSlot = getSlotWidth(xsect, yMid); + aMid = getArea(xsect, yMid, wSlot); rMid = getHydRad(xsect, yMid); // --- alternate approach not currently used, but might produce better @@ -234,11 +229,11 @@ void dwflow_findConduitFlow(int j, int steps, double omega, double dt) } // --- 6. term for evap and seepage losses per unit length - dq6 = link_getLossRate(j, qLast) * 2.5 * dt * v / link_getLength(j); //(5.1.014) + dq6 = link_getLossRate(j, qLast) * 2.5 * dt * v / link_getLength(j); // --- combine terms to find new conduit flow denom = 1.0 + dq1 + dq5; - q = (qOld - dq2 + dq3 + dq4 + dq6) / denom; //(5.1.013) + q = (qOld - dq2 + dq3 + dq4 + dq6) / denom; // --- compute derivative of flow w.r.t. head Link[j].dqdh = 1.0 / denom * GRAVITY * dt * aWtd / length * barrels; @@ -288,7 +283,7 @@ void dwflow_findConduitFlow(int j, int steps, double omega, double dt) Conduit[k].q2 = q; Link[j].newDepth = MIN(yMid, xsect->yFull); aMid = (a1 + a2) / 2.0; -// aMid = MIN(aMid, xsect->aFull); //Slot can have aMid > aFull //(5.1.013) +// aMid = MIN(aMid, xsect->aFull); //Slot can have aMid > aFull Conduit[k].fullState = link_getFullState(a1, a2, xsect->aFull); Link[j].newVolume = aMid * link_getLength(j) * barrels; Link[j].newFlow = q * barrels; @@ -454,7 +449,6 @@ void findSurfArea(int j, double q, double length, double* h1, double* h2, normalDepth = (flowDepth1 + flowDepth2) / 2.0; criticalDepth = normalDepth; -//// Following code segment modified for release 5.1.013. //// //(5.1.013) // --- find conduit's flow classification fullDepth = xsect->yFull; if (flowDepth1 >= fullDepth && flowDepth2 >= fullDepth) @@ -463,7 +457,6 @@ void findSurfArea(int j, double q, double length, double* h1, double* h2, } else Link[j].flowClass = getFlowClass(j, q, *h1, *h2, *y1, *y2, &criticalDepth, &normalDepth, &fasnh); -/////////////////////////////////////////////////////////////// // --- add conduit's surface area to its end nodes depending on flow class switch ( Link[j].flowClass ) @@ -576,8 +569,6 @@ double findLocalLosses(int j, double a1, double a2, double aMid, double q) //============================================================================= -//// New function added to release 5.1.013. //// //(5.1.013) - double getSlotWidth(TXsect* xsect, double y) { double yNorm = y / xsect->yFull; @@ -595,8 +586,6 @@ double getSlotWidth(TXsect* xsect, double y) //============================================================================= -//// This function was re-written for release 5.1.013. //// //(5.1.013) - double getWidth(TXsect* xsect, double y) // // Input: xsect = ptr. to conduit cross section @@ -614,8 +603,6 @@ double getWidth(TXsect* xsect, double y) //============================================================================= -//// This function was re-written for release 5.1.013. //// //(5.1.013) - double getArea(TXsect* xsect, double y, double wSlot) // // Input: xsect = ptr. to conduit cross section @@ -630,8 +617,6 @@ double getArea(TXsect* xsect, double y, double wSlot) //============================================================================= -//// This function was re-written for release 5.1.013. //// //(5.1.013) - double getHydRad(TXsect* xsect, double y) // // Input: xsect = ptr. to conduit cross section diff --git a/src/solver/dynwave.c b/src/solver/dynwave.c index 464282d86..096182302 100644 --- a/src/solver/dynwave.c +++ b/src/solver/dynwave.c @@ -2,16 +2,9 @@ // dynwave.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (5.1.001) -// 03/28/14 (5.1.002) -// 09/15/14 (5.1.007) -// 03/19/15 (5.1.008) -// 08/01/16 (5.1.011) -// 05/10/18 (5.1.013) -// 03/01/20 (5.1.014) -// 07/10/20 (5.1.015) -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // M. Tryby (EPA) // R. Dickinson (CDM) // @@ -22,45 +15,42 @@ // to solve the explicit form of the continuity and momentum equations // for conduits. // +// Update History +// ============== // Build 5.1.002: // - Only non-ponded nodal surface area is saved for use in // surcharge algorithm. -// // Build 5.1.007: // - Node losses added to node outflow variable instead of treated // as a separate item when computing change in node flow volume. -// // Build 5.1.008: // - Module-specific constants moved here from project.c. // - Support added for user-specified minimum variable time step. // - Node crown elevations found here instead of in flowrout.c module. // - OpenMP use to parallelize findLinkFlows() & findNodeDepths(). // - Bug in finding complete list of capacity limited links fixed. -// // Build 5.1.011: // - Added test for failed memory allocation. // - Fixed illegal array index bug for Ideal Pumps. -// // Build 5.1.013: // - Include omp.h protected against lack of compiler support for OpenMP. // - SurchargeMethod option used to decide how node surcharging is handled. // - Storage nodes allowed to pressurize if their surcharge depth > 0. // - Minimum flow needed to compute a Courant time step modified. -// // Build 5.1.014: // - updateNodeFlows() modified to subtract conduit evap. and seepage losses // from downstream node inflow instead of upstream node outflow. -// // Build 5.1.015: // - Roll back the 5.1.014 change for conduit losses in updateNodeFlows(). -// +// Build 5.2.0: +// - Support added for reporting most frequent non-converging links. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE #include "headers.h" #include #include -#if defined(_OPENMP) //(5.1.013) +#if defined(_OPENMP) #include #endif @@ -71,9 +61,9 @@ static const double MINTIMESTEP = 0.001; // min. time step (sec) static const double OMEGA = 0.5; // under-relaxation parameter static const double DEFAULT_SURFAREA = 12.566; // Min. nodal surface area (~4 ft diam.) static const double DEFAULT_HEADTOL = 0.005; // Default head tolerance (ft) -static const double EXTRAN_CROWN_CUTOFF = 0.96; // crown cutoff for EXTRAN //(5.1.013) -static const double SLOT_CROWN_CUTOFF = 0.985257; // crown cutoff for SLOT //(5.1.013) -static const int DEFAULT_MAXTRIALS = 8; // Max. trials per time step +static const double EXTRAN_CROWN_CUTOFF = 0.96; // crown cutoff for EXTRAN +static const double SLOT_CROWN_CUTOFF = 0.985257; // crown cutoff for SLOT +static const int DEFAULT_MAXTRIALS = 8; // Max. trials per time step //----------------------------------------------------------------------------- @@ -111,6 +101,7 @@ static void findNonConduitFlow(int link, double dt); static void findNonConduitSurfArea(int link); static double getModPumpFlow(int link, double q, double dt); static void updateNodeFlows(int link); +static void updateConvergenceStats(); static int findNodeDepths(double dt); static void setNodeDepth(int node, double dt); @@ -163,9 +154,9 @@ void dynwave_init() Link[i].dqdh = 0.0; } - // --- set crown cutoff for finding top width of closed conduits //(5.1.013) - if ( SurchargeMethod == SLOT ) CrownCutoff = SLOT_CROWN_CUTOFF; //(5.1.013) - else CrownCutoff = EXTRAN_CROWN_CUTOFF; //(5.1.013) + // --- set crown cutoff for finding top width of closed conduits + if ( SurchargeMethod == SLOT ) CrownCutoff = SLOT_CROWN_CUTOFF; + else CrownCutoff = EXTRAN_CROWN_CUTOFF; } //============================================================================= @@ -262,7 +253,7 @@ int dynwave_execute(double tStep) findBypassedLinks(); } } - if ( !converged ) NonConvergeCount++; + if ( !converged ) updateConvergenceStats(); // --- identify any capacity-limited conduits findLimitedLinks(); @@ -271,6 +262,16 @@ int dynwave_execute(double tStep) //============================================================================= +void updateConvergenceStats() +{ + int i; + NonConvergeCount++; + for (i = 0; i < Nobjects[NODE]; i++) + stats_updateConvergenceStats(i, Xnode[i].converged); +} + +//============================================================================= + void initRoutingStep() { int i; @@ -313,12 +314,6 @@ void initNodeStates() Xnode[i].newSurfArea = node_getSurfArea(i, Node[i].newDepth); } -/* //// Removed for release 5.1.013. /// //(5.1.013) - if ( Xnode[i].newSurfArea < MinSurfArea ) - { - Xnode[i].newSurfArea = MinSurfArea; - } -*/ // --- initialize nodal inflow & outflow Node[i].inflow = 0.0; Node[i].outflow = Node[i].losses; @@ -550,19 +545,19 @@ void updateNodeFlows(int i) k = Link[i].subIndex; uniformLossRate = Conduit[k].evapLossRate + Conduit[k].seepLossRate; barrels = Conduit[k].barrels; - uniformLossRate *= barrels; //(5.1.014) + uniformLossRate *= barrels; } // --- update total inflow & outflow at upstream/downstream nodes if ( q >= 0.0 ) { - Node[n1].outflow += q + uniformLossRate; //(5.1.015) - Node[n2].inflow += q; //(5.1.015) + Node[n1].outflow += q + uniformLossRate; + Node[n2].inflow += q; } else { - Node[n1].inflow -= q; //(5.1.015) - Node[n2].outflow -= q - uniformLossRate; //(5.1.015) + Node[n1].inflow -= q; + Node[n2].outflow -= q - uniformLossRate; } // --- add surf. area contributions to upstream/downstream nodes @@ -584,13 +579,7 @@ void updateNodeFlows(int i) //============================================================================= -//// --- modified for release 5.1.015 ---- //// //(5.1.015) int findNodeDepths(double dt) -// -// Input: dt = time step (sec) -// Output: returns TRUE if no change in depth at all nodes, FALSE otherwise -// Purpose: finds new depth at all nodes and checks if convergence achieved. -// { int i; double yOld; // previous node depth (ft) @@ -615,10 +604,11 @@ int findNodeDepths(double dt) } } } - // --- return FALSE if any non-Outfall node failed to converge + + // --- return FALSE if any non-Outfall node failed to converge for (i = 0; i < Nobjects[NODE]; i++) { - if (Node[i].type == OUTFALL) continue; + if ( Node[i].type == OUTFALL ) continue; if (Xnode[i].converged == FALSE) return FALSE; } return TRUE; @@ -636,7 +626,7 @@ void setNodeDepth(int i, double dt) { int canPond; // TRUE if node can pond overflows int isPonded; // TRUE if node is currently ponded - int isSurcharged = FALSE; // TRUE if node is surcharged //(5.1.013) + int isSurcharged = FALSE; // TRUE if node is surcharged double dQ; // inflow minus outflow at node (cfs) double dV; // change in node volume (ft3) double dy; // change in node depth (ft) @@ -660,13 +650,13 @@ void setNodeDepth(int i, double dt) yLast = Node[i].newDepth; Node[i].overflow = 0.0; surfArea = Xnode[i].newSurfArea; - surfArea = MAX(surfArea, MinSurfArea); //(5.1.013) + surfArea = MAX(surfArea, MinSurfArea); // --- determine average net flow volume into node over the time step dQ = Node[i].inflow - Node[i].outflow; dV = 0.5 * (Node[i].oldNetInflow + dQ) * dt; -//// Following code segment added to release 5.1.013. //// //(5.1.013) + // --- determine if node is EXTRAN surcharged if (SurchargeMethod == EXTRAN) { @@ -683,10 +673,9 @@ void setNodeDepth(int i, double dt) // --- surcharge occurs when node depth exceeds top of its highest link else isSurcharged = (yCrown > 0.0 && yLast > yCrown); } -///////////////////////////////////////////////////////////// // --- if node not surcharged, base depth change on surface area - if (!isSurcharged) //(5.1.013) + if (!isSurcharged) { dy = dV / surfArea; yNew = yOld + dy; @@ -850,7 +839,7 @@ double getLinkStep(double tMin, int *minLink) // --- skip conduits with negligible flow, area or Fr k = Link[i].subIndex; q = fabs(Link[i].newFlow) / Conduit[k].barrels; - if ( q <= FUDGE //(5.1.013) + if ( q <= FUDGE || Conduit[k].a1 <= FUDGE || Link[i].froude <= 0.01 ) continue; diff --git a/src/solver/enums.h b/src/solver/enums.h index 4bc50e3f8..911d80d3b 100644 --- a/src/solver/enums.h +++ b/src/solver/enums.h @@ -2,41 +2,35 @@ // enums.h // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 04/14/14 (Build 5.1.004) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 08/05/15 (Build 5.1.010) -// 08/01/16 (Build 5.1.011) -// 05/10/18 (Build 5.1.013) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // -// Enumerated variables +// Enumerated constants // +// Update History +// ============== // Build 5.1.004: // - IGNORE_RDII for the ignore RDII option added. -// // Build 5.1.007: // - s_GWF for [GWF] input file section added. // - s_ADJUST for [ADJUSTMENTS] input file section added. -// // Build 5.1.008: // - Enumerations for fullness state of a conduit added. // - NUM_THREADS added for number of parallel threads option. // - Runoff flow categories added to represent mass balance components. -// // Build 5.1.010: // - New ROADWAY_WEIR type of weir added. // - Potential evapotranspiration (PET) added as a system output variable. -// // Build 5.1.011: // - s_EVENT added to InputSectionType enumeration. -// // Build 5.1.013: // - SURCHARGE_METHOD and RULE_STEP options added. -// - WEIR_CURVE added as a curve type. -// +// - WEIR_CURVE added as a curve type. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for variable speed pumps. +// - Support added for conical & pyramidal storage shapes. //----------------------------------------------------------------------------- #ifndef ENUMS_H @@ -63,12 +57,14 @@ SNOWMELT, // snowmelt parameter set SHAPE, // custom conduit shape LID, // LID treatment units + STREET, // street cross section + INLET, // street inlet design MAX_OBJ_TYPES}; //------------------------------------- // Names of Node sub-types //------------------------------------- - #define MAX_NODE_TYPES 4 + #define MAX_NODE_TYPES 5 enum NodeType { JUNCTION, OUTFALL, @@ -117,18 +113,18 @@ // Cross section shape types //------------------------------------- enum XsectType { - DUMMY, // 0 + DUMMY, // 0 CIRCULAR, // 1 closed FILLED_CIRCULAR, // 2 closed RECT_CLOSED, // 3 closed - RECT_OPEN, // 4 - TRAPEZOIDAL, // 5 - TRIANGULAR, // 6 + RECT_OPEN, // 4 + TRAPEZOIDAL, // 5 + TRIANGULAR, // 6 PARABOLIC, // 7 - POWERFUNC, // 8 - RECT_TRIANG, // 9 + POWERFUNC, // 8 + RECT_TRIANG, // 9 RECT_ROUND, // 10 - MOD_BASKET, // 11 + MOD_BASKET, // 11 HORIZ_ELLIPSE, // 12 closed VERT_ELLIPSE, // 13 closed ARCH, // 14 closed @@ -141,7 +137,8 @@ SEMICIRCULAR, // 21 closed IRREGULAR, // 22 CUSTOM, // 23 closed - FORCE_MAIN}; // 24 closed + FORCE_MAIN, // 24 closed + STREET_XSECT}; // 25 //------------------------------------- // Measurement units types @@ -197,7 +194,7 @@ //------------------------------------- // Computed node quantities //------------------------------------- - #define MAX_NODE_RESULTS 7 + #define MAX_NODE_RESULTS 7 enum NodeResultType { NODE_DEPTH, // water depth above invert NODE_HEAD, // hydraulic head @@ -366,7 +363,6 @@ enum CompatibilityType { PARTIAL_DAMPING, // partial damping FULL_DAMPING}; // full damping -//// Added to release 5.1.013. //// //(5.1.013) enum SurchargeMethodType { EXTRAN, // original EXTRAN method SLOT}; // Preissmann slot method @@ -396,7 +392,9 @@ enum CompatibilityType { enum StorageType { TABULAR, // area v. depth from table - FUNCTIONAL}; // area v. depth from power function + FUNCTIONAL, // area v. depth from power function + CONICAL, // area v. depth from elliptical cone + PYRAMIDAL}; // area v. depth from rectangular pyramid enum ReactorType { CSTR, // completely mixed reactor @@ -414,9 +412,10 @@ enum CompatibilityType { enum PumpCurveType { TYPE1_PUMP, // flow varies stepwise with wet well volume - TYPE2_PUMP, // flow varies stepwise with inlet depth + TYPE2_PUMP, // flow varies stepwise with inlet depth TYPE3_PUMP, // flow varies with head delivered TYPE4_PUMP, // flow varies with inlet depth + TYPE5_PUMP, // variable speed version of TYPE3 pump IDEAL_PUMP}; // outflow equals inflow enum OrificeType { @@ -437,11 +436,12 @@ enum CompatibilityType { RATING_CURVE, // flow rate v. head for outlet link CONTROL_CURVE, // control setting v. controller variable SHAPE_CURVE, // width v. depth for custom x-section - WEIR_CURVE, // discharge coeff. v. head for weir //(5.1.013) + WEIR_CURVE, // discharge coeff. v. head for weir PUMP1_CURVE, // flow v. wet well volume for pump PUMP2_CURVE, // flow v. depth for pump (discrete) PUMP3_CURVE, // flow v. head for pump (continuous) - PUMP4_CURVE}; // flow v. depth for pump (continuous) + PUMP4_CURVE, // flow v. depth for pump (continuous) + PUMP5_CURVE}; // variable speed version of TYPE3 pump enum InputSectionType { s_TITLE, s_OPTION, s_FILE, s_RAINGAGE, @@ -457,14 +457,15 @@ enum CompatibilityType { s_COORDINATE, s_VERTICES, s_POLYGON, s_LABEL, s_SYMBOL, s_BACKDROP, s_TAG, s_PROFILE, s_MAP, s_LID_CONTROL, s_LID_USAGE, s_GWF, - s_ADJUST, s_EVENT}; + s_ADJUST, s_EVENT, s_STREET, s_INLET_USAGE, + s_INLET}; enum InputOptionType { FLOW_UNITS, INFIL_MODEL, ROUTE_MODEL, START_DATE, START_TIME, END_DATE, END_TIME, REPORT_START_DATE, REPORT_START_TIME, SWEEP_START, SWEEP_END, START_DRY_DAYS, - WET_STEP, DRY_STEP, ROUTE_STEP, RULE_STEP, //(5.1.013) + WET_STEP, DRY_STEP, ROUTE_STEP, RULE_STEP, REPORT_STEP, ALLOW_PONDING, INERT_DAMPING, SLOPE_WEIGHTING, VARIABLE_STEP, NORMAL_FLOW_LTD, LENGTHENING_STEP, MIN_SURFAREA, COMPATIBILITY, @@ -473,7 +474,7 @@ enum CompatibilityType { IGNORE_SNOWMELT, IGNORE_GWATER, IGNORE_ROUTING, IGNORE_QUALITY, MAX_TRIALS, HEAD_TOL, SYS_FLOW_TOL, LAT_FLOW_TOL, IGNORE_RDII, - MIN_ROUTE_STEP, NUM_THREADS, SURCHARGE_METHOD}; //(5.1.013) + MIN_ROUTE_STEP, NUM_THREADS, SURCHARGE_METHOD}; enum NoYesType { NO, diff --git a/src/solver/error.c b/src/solver/error.c index 17c0a0ed5..470da12bd 100644 --- a/src/solver/error.c +++ b/src/solver/error.c @@ -2,23 +2,22 @@ // error.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 03/19/15 (Build 5.1.008) -// 08/05/15 (Build 5.1.010) -// 04/14/20 (Build 5.1.015) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Error messages // +// Update History +// ============== // Build 5.1.008: // - Text of Error 217 for control rules modified. -// // Build 5.1.010: // - Text of Error 318 for rainfall data files modified. -// // Build 5.1.015: // - Added new Error 140 for storage nodes. +// Build 5.2.0: +// - Added new Error 235 for invalid infiltration parameters. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -59,7 +58,7 @@ "\n ERROR 138: Node %s has initial depth greater than maximum depth." #define ERR139 "\n ERROR 139: Regulator %s is the outlet of a non-storage node." #define ERR140 \ -"\n ERROR 140: Storage node %s has negative volume at full depth." //(5.1.015) +"\n ERROR 140: Storage node %s has negative volume at full depth." #define ERR141 \ "\n ERROR 141: Outfall %s has more than 1 inlet link or an outlet link." #define ERR143 "\n ERROR 143: Regulator %s has invalid cross-section shape." @@ -107,7 +106,7 @@ #define ERR209 "\n ERROR 209: undefined object %s " #define ERR211 "\n ERROR 211: invalid number %s " #define ERR213 "\n ERROR 213: invalid date/time %s " -#define ERR217 "\n ERROR 217: control rule clause invalid or out of sequence " //(5.1.008) +#define ERR217 "\n ERROR 217: control rule clause invalid or out of sequence " #define ERR219 "\n ERROR 219: data provided for unidentified transect " #define ERR221 "\n ERROR 221: transect station out of sequence " #define ERR223 "\n ERROR 223: Transect %s has too few stations." @@ -115,7 +114,8 @@ #define ERR227 "\n ERROR 227: Transect %s has no Manning's N." #define ERR229 "\n ERROR 229: Transect %s has invalid overbank locations." #define ERR231 "\n ERROR 231: Transect %s has no depth." -#define ERR233 "\n ERROR 233: invalid treatment function expression " +#define ERR233 "\n ERROR 233: invalid math expression " +#define ERR235 "\n ERROR 235: invalid infiltration parameters." #define ERR301 "\n ERROR 301: files share same names." #define ERR303 "\n ERROR 303: cannot open input file." @@ -128,7 +128,7 @@ #define ERR315 "\n ERROR 315: cannot open rainfall interface file %s." #define ERR317 "\n ERROR 317: cannot open rainfall data file %s." #define ERR318 \ -"\n ERROR 318: the following line is out of sequence in rainfall data file %s." //(5.1.010) +"\n ERROR 318: the following line is out of sequence in rainfall data file %s." #define ERR319 "\n ERROR 319: unknown format for rainfall data file %s." #define ERR320 "\n ERROR 320: invalid format for rainfall interface file." #define ERR321 "\n ERROR 321: no data in rainfall interface file for gage %s." @@ -202,7 +202,7 @@ char* ErrorMsgs[] = ERR327, ERR329, ERR330, ERR331, ERR333, ERR335, ERR336, ERR337, ERR338, ERR339, ERR341, ERR343, ERR345, ERR351, ERR353, ERR355, ERR357, ERR361, ERR363, ERR401, ERR402, ERR403, ERR405, ERR501, ERR502, ERR503, ERR504, - ERR505, ERR506, ERR507, ERR508, ERR509, ERR140}; //(5.1.015) + ERR505, ERR506, ERR507, ERR508, ERR509, ERR140, ERR235}; int ErrorCodes[] = { 0, 101, 103, 105, 107, 108, 109, 110, 111, @@ -217,7 +217,7 @@ int ErrorCodes[] = 327, 329, 330, 331, 333, 335, 336, 337, 338, 339, 341, 343, 345, 351, 353, 355, 357, 361, 363, 401, 402, 403, 405, 501, 502, 503, 504, - 505, 506, 507, 508, 509, 140}; //(5.1.015) + 505, 506, 507, 508, 509, 140, 235}; char ErrString[256]; diff --git a/src/solver/error.h b/src/solver/error.h index 20737960c..692696b30 100644 --- a/src/solver/error.h +++ b/src/solver/error.h @@ -2,9 +2,8 @@ // error.h // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 04/14/20 (Build 5.1.015) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Error codes @@ -107,7 +106,7 @@ enum ErrorType { ERR_TRANSECT_MANNING, //227 62 ERR_TRANSECT_OVERBANK, //229 63 ERR_TRANSECT_NO_DEPTH, //231 64 - ERR_TREATMENT_EXPR, //233 65 + ERR_MATH_EXPR, //233 65 //... File Name/Opening Errors ERR_FILE_NAME, //301 66 @@ -121,7 +120,7 @@ enum ErrorType { ERR_RAIN_FILE_SCRATCH, //313 72 ERR_RAIN_FILE_OPEN, //315 73 ERR_RAIN_FILE_DATA, //317 74 - ERR_RAIN_FILE_SEQUENCE, //318 75 + ERR_RAIN_FILE_SEQUENCE, //318 75 ERR_RAIN_FILE_FORMAT, //319 76 ERR_RAIN_IFACE_FORMAT, //320 77 ERR_RAIN_FILE_GAGE, //321 78 @@ -148,7 +147,7 @@ enum ErrorType { ERR_RDII_FILE_SCRATCH, //341 91 ERR_RDII_FILE_OPEN, //343 92 ERR_RDII_FILE_FORMAT, //345 93 - + //... Routing File Errors ERR_ROUTING_FILE_OPEN, //351 94 ERR_ROUTING_FILE_FORMAT, //353 95 @@ -170,16 +169,17 @@ enum ErrorType { ERR_API_INPUTNOTOPEN, //502 105 ERR_API_SIM_NRUNNING, //503 106 ERR_API_WRONG_TYPE, //504 107 - ERR_API_OBJECT_INDEX, //505 108 + ERR_API_OBJECT_INDEX, //505 108 ERR_API_POLLUT_INDEX, //506 109 ERR_API_INFLOWTYPE, //507 110 ERR_API_TSERIES_INDEX, //508 111 ERR_API_PATTERN_INDEX, //509 112 //... Additional Errors - ERR_STORAGE_VOLUME, //140 113 //(5.1.015) + ERR_STORAGE_VOLUME, //140 113 + ERR_INFIL_PARAMS, //235 114 MAXERRMSG}; - + char* error_getMsg(int i); int error_getCode(int i); int error_setInpError(int errcode, char* s); diff --git a/src/solver/exfil.c b/src/solver/exfil.c index 58ef6f5b2..4f4b08804 100644 --- a/src/solver/exfil.c +++ b/src/solver/exfil.c @@ -2,24 +2,22 @@ // exfil.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 08/05/15 (Build 5.1.010) -// 08/01/16 (Build 5.1.011) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Storage unit exfiltration functions. // +// Update History +// ============== // Build 5.1.008: // - Monthly conductivity adjustment applied to exfiltration rate. -// // Build 5.1.010: // - New modified Green-Ampt infiltration option used. -// // Build 5.1.011: // - Fixed units conversion error for storage units with surface area curves. -// +// Build 5.2.0: +// - Support added for conical & pyramidal storage shapes. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -92,48 +90,64 @@ void exfil_initState(int k) grnampt_initState(exfil->btmExfil); grnampt_initState(exfil->bankExfil); - // --- shape given by a Storage Curve - i = Storage[k].aCurve; - if ( i >= 0 ) + switch (Storage[k].shape) { - // --- get bottom area - aCurve = &Curve[i]; - Storage[k].exfil->btmArea = table_lookupEx(aCurve, 0.0); - - // --- find min/max bank depths and max. bank area - table_getFirstEntry(aCurve, &d, &a); + // --- shape given by a Storage Curve + case TABULAR: + i = Storage[k].aCurve; + exfil->btmArea = 0.0; exfil->bankMinDepth = 0.0; exfil->bankMaxDepth = 0.0; exfil->bankMaxArea = 0.0; - alast = a; - while ( table_getNextEntry(aCurve, &d, &a) ) + if ( i >= 0 ) { - if ( a < alast ) break; - else if ( a > alast ) + // --- get bottom area + aCurve = &Curve[i]; + Storage[k].exfil->btmArea = table_lookupEx(aCurve, 0.0); + + // --- find min/max bank depths and max. bank area + table_getFirstEntry(aCurve, &d, &a); + alast = a; + while ( table_getNextEntry(aCurve, &d, &a) ) { - exfil->bankMaxArea = a; - exfil->bankMaxDepth = d; + if ( a < alast ) break; + else if ( a > alast ) + { + exfil->bankMaxArea = a; + exfil->bankMaxDepth = d; + } + else if ( exfil->bankMaxArea == 0.0 ) + exfil->bankMinDepth = d; + else break; + alast = a; } - else if ( exfil->bankMaxArea == 0.0 ) exfil->bankMinDepth = d; - else break; - alast = a; - } - - // --- convert from user units to internal units - exfil->btmArea /= UCF(LENGTH) * UCF(LENGTH); - exfil->bankMaxArea /= UCF(LENGTH) * UCF(LENGTH); - exfil->bankMinDepth /= UCF(LENGTH); - exfil->bankMaxDepth /= UCF(LENGTH); - } - // --- functional storage shape curve - else - { - exfil->btmArea = Storage[k].aConst; - if ( Storage[k].aExpon == 0.0 ) exfil->btmArea +=Storage[k].aCoeff; - exfil->bankMinDepth = 0.0; - exfil->bankMaxDepth = BIG; - exfil->bankMaxArea = BIG; + // --- convert from user units to internal units + exfil->btmArea /= UCF(LENGTH) * UCF(LENGTH); + exfil->bankMaxArea /= UCF(LENGTH) * UCF(LENGTH); + exfil->bankMinDepth /= UCF(LENGTH); + exfil->bankMaxDepth /= UCF(LENGTH); + } + break; + + // --- functional storage shape curve + case FUNCTIONAL: + exfil->btmArea = Storage[k].a0; + if ( Storage[k].a2 == 0.0 ) + exfil->btmArea +=Storage[k].a1; + exfil->bankMinDepth = 0.0; + exfil->bankMaxDepth = BIG; + exfil->bankMaxArea = BIG; + break; + + // --- conical & pyramidal shapes + case CONICAL: + case PYRAMIDAL: + exfil->btmArea = Storage[k].a0; + exfil->bankMinDepth = 0.0; + exfil->bankMaxDepth = BIG; + exfil->bankMaxArea = BIG; + break; } } } diff --git a/src/solver/exfil.h b/src/solver/exfil.h index 8315c08c4..6fb3d7fff 100644 --- a/src/solver/exfil.h +++ b/src/solver/exfil.h @@ -2,9 +2,9 @@ // exfil.h // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 09/15/14 (Build 5.1.007) -// Author: L. Rossman (US EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // // Public interface for exfiltration functions. //----------------------------------------------------------------------------- @@ -12,7 +12,6 @@ #ifndef EXFIL_H #define EXFIL_H - //---------------------------- // EXFILTRATION OBJECT //---------------------------- @@ -33,5 +32,4 @@ int exfil_readStorageParams(int k, char* tok[], int ntoks, int n); void exfil_initState(int k); double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area); - -#endif //EXFIL_H +#endif diff --git a/src/solver/findroot.h b/src/solver/findroot.h index c0ae10dff..3b54c6456 100644 --- a/src/solver/findroot.h +++ b/src/solver/findroot.h @@ -9,12 +9,10 @@ #ifndef FINDROOT_H #define FINDROOT_H - int findroot_Newton(double x1, double x2, double* rts, double xacc, void (*func) (double x, double* f, double* df, void* p), - void* p); + void* p); double findroot_Ridder(double x1, double x2, double xacc, - double (*func)(double, void* p), void* p); - + double (*func)(double, void* p), void* p); #endif //FINDROOT_H diff --git a/src/solver/flowrout.c b/src/solver/flowrout.c index 675e39300..7a1f478aa 100644 --- a/src/solver/flowrout.c +++ b/src/solver/flowrout.c @@ -2,33 +2,26 @@ // flowrout.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/19/14 (Build 5.1.001) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 03/14/17 (Build 5.1.012) -// 03/01/20 (Build 5.1.014) -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // M. Tryby (EPA) // // Flow routing functions. // -// +// Update History +// ============== // Build 5.1.007: // - updateStorageState() modified in response to node outflow being // initialized with current evap & seepage losses in routing_execute(). -// // Build 5.1.008: // - Determination of node crown elevations moved to dynwave.c. // - Support added for new way of recording conduit's fullness state. -// // Build 5.1.012: // - Overflow computed in updateStorageState() must be non-negative. // - Terminal storage nodes now updated corectly. -// // Build 5.1.014: // - Arguments to function link_getLossRate changed. -// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -179,10 +172,11 @@ int flowrout_execute(int links[], int routingModel, double tStep) // --- retrieve inflow at upstream end of link qin = getLinkInflow(j, tStep); - // route flow through link + // --- route flow through link if ( routingModel == SF ) steps += steadyflow_execute(j, &qin, &qout, tStep); - else steps += kinwave_execute(j, &qin, &qout, tStep); + else + steps += kinwave_execute(j, &qin, &qout, tStep); Link[j].newFlow = qout; // adjust outflow at upstream node and inflow at downstream node @@ -641,8 +635,8 @@ void setNewNodeState(int j, double dt) // --- update terminal storage nodes if ( Node[j].type == STORAGE ) { - if ( Node[j].updated == FALSE ) - updateStorageState(j, Nobjects[LINK], NULL, dt); + if ( Node[j].updated == FALSE ) + updateStorageState(j, Nobjects[LINK], NULL, dt); return; } @@ -727,7 +721,7 @@ void updateNodeDepth(int i, double y) if ( Node[i].type == STORAGE ) return; // --- if non-outfall node is flooded, then use full depth - if ( Node[i].type != OUTFALL && + if ( Node[i].type != OUTFALL && Node[i].degree > 0 && Node[i].overflow > 0.0 ) y = Node[i].fullDepth; // --- if current new depth below y @@ -770,7 +764,7 @@ int steadyflow_execute(int j, double* qin, double* qout, double tStep) else { // --- adjust flow for evap and infil losses - q -= link_getLossRate(j, q); //(5.1.014) + q -= link_getLossRate(j, q); // --- flow can't exceed full flow if ( q > Link[j].qFull ) diff --git a/src/solver/forcmain.c b/src/solver/forcmain.c index b2c70fd75..2e6464793 100644 --- a/src/solver/forcmain.c +++ b/src/solver/forcmain.c @@ -2,8 +2,8 @@ // forcemain.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Special Non-Manning Force Main functions diff --git a/src/solver/funcs.h b/src/solver/funcs.h index b9da07048..e3b769fb7 100644 --- a/src/solver/funcs.h +++ b/src/solver/funcs.h @@ -2,41 +2,37 @@ // funcs.h // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.000) -// 09/15/14 (Build 5.1.007) -// 04/02/15 (Build 5.1.008) -// 08/05/15 (Build 5.1.010) -// 05/10/18 (Build 5.1.013) -// 03/01/20 (Build 5.1.014) -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // M. Tryby (EPA) // // Global interfacing functions. // +// Update History +// ============== // Build 5.1.007: // - climate_readAdjustments() added. -// // Build 5.1.008: // - Function list was re-ordered and blank lines added for readability. // - Pollutant buildup/washoff functions for the new surfqual.c module added. // - Several other functions added, re-named or have modified arguments. -// // Build 5.1.010: // - New roadway_getInflow() function added. -// // Build 5.1.013: // - Additional arguments added to function stats_updateSubcatchStats. -// // Build 5.1.014: // - Arguments to link_getLossRate function changed. -// +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for reporting most frequent non-converging links. +// - Support added for named variables & math expressions in control rules. +// - Support added for tracking a gage's prior n-hour rainfall total. //----------------------------------------------------------------------------- #ifndef FUNCS_H #define FUNCS_H - void project_open(char *f1, char *f2, char *f3); void project_close(void); @@ -86,12 +82,14 @@ void report_writeQualError(TRoutingTotals* totals); void report_writeMaxStats(TMaxStats massBalErrs[], TMaxStats CourantCrit[], int nMaxStats); void report_writeMaxFlowTurns(TMaxStats flowTurns[], int nMaxStats); +void report_writeNonconvergedStats(TMaxStats maxNonconverged[], + int nMaxStats); void report_writeSysStats(TSysStats* sysStats); void report_writeErrorMsg(int code, char* msg); void report_writeErrorCode(void); void report_writeInputErrorMsg(int k, int sect, char* line, long lineCount); -void report_writeWarningMsg(char* msg, char* id); +void report_writeWarningMsg(char* msg, char* id); void report_writeTseriesErrorMsg(int code, TTable *tseries); void inputrpt_writeInput(void); @@ -255,6 +253,8 @@ void massbal_updateLoadingTotals(int type, int pollut, double w); void massbal_updateGwaterTotals(double vInfil, double vUpperEvap, double vLowerEvap, double vLowerPerc, double vGwater); void massbal_updateRoutingTotals(double tStep); +void massbal_updateNodeTotals(double tStep); + void massbal_initTimeStepTotals(void); void massbal_addInflowFlow(int type, double q); @@ -280,14 +280,16 @@ void stats_report(void); void stats_updateCriticalTimeCount(int node, int link); void stats_updateFlowStats(double tStep, DateTime aDate, int stepCount, int steadyState); -void stats_updateSubcatchStats(int subcatch, double rainVol, +void stats_updateSubcatchStats(int subcatch, double rainVol, double runonVol, double evapVol, double infilVol, - double impervVol, double pervVol, double runoffVol, double runoff); //(5.1.013) + double impervVol, double pervVol, double runoffVol, double runoff); void stats_updateGwaterStats(int j, double infil, double evap, double latFlow, double deepFlow, double theta, double waterTable, double tStep); void stats_updateMaxRunoff(void); void stats_updateMaxNodeDepth(int node, double depth); +void stats_updateConvergenceStats(int node, int converged); + //----------------------------------------------------------------------------- // Raingage Methods @@ -299,6 +301,8 @@ void gage_setState(int gage, DateTime aDate); double gage_getPrecip(int gage, double *rainfall, double *snowfall); void gage_setReportRainfall(int gage, DateTime aDate); DateTime gage_getNextRainDate(int gage, DateTime aDate); +void gage_updatePastRain(int j, int tStep); +double gage_getPastRain(int gage, int hrs); //----------------------------------------------------------------------------- // Subcatchment Methods @@ -361,12 +365,12 @@ void node_getResults(int node, double wt, float x[]); int inflow_readExtInflow(char* tok[], int ntoks); int inflow_readDwfInflow(char* tok[], int ntoks); int inflow_readDwfPattern(char* tok[], int ntoks); -int inflow_setExtInflow(int j, int param, int type, - int tSeries, int basePat, double cf, +int inflow_setExtInflow(int j, int param, int type, + int tSeries, int basePat, double cf, double baseline, double sf); -int inflow_validate(int param, int type, int tSeries, - int basePat, double *cf); - +int inflow_validate(int param, int type, int tSeries, + int basePat, double *cf); + void inflow_initDwfInflow(TDwfInflow* inflow); void inflow_initDwfPattern(int pattern); @@ -419,7 +423,7 @@ double link_getYnorm(int link, double q); double link_getVelocity(int link, double q, double y); double link_getFroude(int link, double v, double y); double link_getPower(int link); -double link_getLossRate(int link, double q); //(5.1.014) +double link_getLossRate(int link, double q); char link_getFullState(double a1, double a2, double aFull); void link_getResults(int link, double wt, float x[]); @@ -431,6 +435,7 @@ int xsect_isOpen(int type); int xsect_setParams(TXsect *xsect, int type, double p[], double ucf); void xsect_setIrregXsectParams(TXsect *xsect); void xsect_setCustomXsectParams(TXsect *xsect); +void xsect_setStreetXsectParams(TXsect *xsect); double xsect_getAmax(TXsect* xsect); double xsect_getSofA(TXsect* xsect, double area); @@ -464,6 +469,7 @@ int transect_create(int n); void transect_delete(void); int transect_readParams(int* count, char* tok[], int ntoks); void transect_validate(int j); +void transect_createStreetTransect(int i); //----------------------------------------------------------------------------- // Custom Shape Cross-Section Methods @@ -475,8 +481,12 @@ int shape_validate(TShape *shape, TTable *curve); //----------------------------------------------------------------------------- int controls_create(int n); void controls_delete(void); +void controls_init(void); +void controls_addToCount(char* s); +int controls_addVariable(char* tok[], int ntoks); +int controls_addExpression(char* tok[], int ntoks); int controls_addRuleClause(int rule, int keyword, char* Tok[], int nTokens); -int controls_evaluate(DateTime currentTime, DateTime elapsedTime, +int controls_evaluate(DateTime currentTime, DateTime elapsedTime, double tStep); //----------------------------------------------------------------------------- @@ -500,8 +510,8 @@ double table_inverseLookup(TTable* table, double y); double table_getSlope(TTable *table, double x); double table_getMaxY(TTable *table, double x); -double table_getArea(TTable* table, double x); -double table_getInverseArea(TTable* table, double a); +double table_getStorageVolume(TTable* table, double x); +double table_getStorageDepth(TTable* table, double v); void table_tseriesInit(TTable *table); double table_tseriesLookup(TTable* table, double t, char extend); diff --git a/src/solver/gage.c b/src/solver/gage.c index b8b0e65d0..fc51cc626 100644 --- a/src/solver/gage.c +++ b/src/solver/gage.c @@ -2,20 +2,20 @@ // gage.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/10 (Build 5.1.001) -// 09/15/14 (Build 5.1.007) -// 05/10/18 (Build 5.1.013) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Rain gage functions. // +// Update History +// ============== // Build 5.1.007: // - Support for monthly rainfall adjustments added. -// // Build 5.1.013: // - Validation no longer performed on unused gages. -// +// Build 5.2.0: +// - Support added for tracking a gage's prior n-hour rainfall total. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -37,6 +37,8 @@ const double OneSecond = 1.1574074e-5; // gage_setState (called by runoff_execute & getRainfall in rdii.c) // gage_getPrecip (called by subcatch_getRunoff) // gage_getNextRainDate (called by runoff_getTimeStep) +// gage_updatePastRain (called by runoff_execute) +// gage_getPastRain (called by getRainValue in controls.c) //----------------------------------------------------------------------------- // Local functions @@ -46,7 +48,7 @@ static int readGageFileFormat(char* tok[], int ntoks, double x[]); static int getFirstRainfall(int gage); static int getNextRainfall(int gage); static double convertRainfall(int gage, double rain); - +static void initPastRain(int gage); //============================================================================= @@ -269,7 +271,7 @@ void gage_initState(int j) // Purpose: initializes state of rain gage. // { - // --- initialize actual and reported rainfall //(5.1.013) + // --- initialize actual and reported rainfall Gage[j].rainfall = 0.0; Gage[j].reportRainfall = 0.0; if ( IgnoreRainfall ) return; @@ -309,6 +311,7 @@ void gage_initState(int j) else if ( !getNextRainfall(j) ) Gage[j].nextDate = NO_DATE; } else Gage[j].startDate = NO_DATE; + initPastRain(j); } //============================================================================= @@ -388,6 +391,86 @@ void gage_setState(int j, DateTime t) //============================================================================= +void initPastRain(int j) +{ + // --- initialize past hourly rain accumulation + int i; + for (i = 0; i <= 24; i++) + Gage[j].pastRain[i] = 0.0; + Gage[j].pastInterval = 0; +} + +//============================================================================= + +void gage_updatePastRain(int j, int tStep) +// +// Input: j = rain gage index +// tStep = current runoff time step (sec) +// Output: none +// Purpose: updates past 24 hourly rain totals. +// +// Note: pastRain[0] is past rain volume prior to 1 hour, +// pastRain[n] is past rain volume after n hours, +// pastInterval is time since last hour was reached. +{ + int i, t; + double r; + + // --- current rainfall intensity (in/sec or mm/sec) + r = Gage[j].rainfall / 3600.; + + // --- process each hourly interval of current time step + while (tStep > 0) + { + // --- time for most recent rainfall interval to reach 1 hr + t = 3600 - Gage[j].pastInterval; + + // --- remaining time step is greater than this time + if (tStep > t) + { + // --- add current rain to most recent interval + Gage[j].pastRain[0] += t * r; + + // --- shift all prior hourly rain amounts by 1 hour + for (i = 24; i > 0; i-- ) + Gage[j].pastRain[i] = Gage[j].pastRain[i-1]; + + // --- begin a new most recent interval + Gage[j].pastInterval = 0; + Gage[j].pastRain[0] = 0.0; + tStep -= t; + } + // --- time to reach 1 hr in most recent interval is greater + // than remaining time step so update most recent interval + else + { + Gage[j].pastRain[0] += tStep * r; + Gage[j].pastInterval += tStep; + tStep = 0; + } + } +} + +//============================================================================= + +double gage_getPastRain(int j, int n) +// +// Input: j = rain gage index +// n = number of hours prior to current date +// Output: cumulative rain volume (inches or mm) in last n hours +// Purpose: retrieves rainfall total over some previous number of hours. +// +{ + int i; + double result = 0.0; + if (n < 1 || n > 24) return 0.0; + for (i = 1; i <= n; i++) + result += Gage[j].pastRain[i]; + return result; +} + +//============================================================================= + DateTime gage_getNextRainDate(int j, DateTime aDate) // // Input: j = rain gage index diff --git a/src/solver/globals.h b/src/solver/globals.h index 400f35b9b..9156eb252 100644 --- a/src/solver/globals.h +++ b/src/solver/globals.h @@ -2,41 +2,30 @@ // globals.h // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/19/14 (Build 5.1.000) -// 04/14/14 (Build 5.1.004) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 08/01/16 (Build 5.1.011) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// 04/01/20 (Build 5.1.015) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Global Variables // +// Update History +// ============== // Build 5.1.004: // - Ignore RDII option added. -// // Build 5.1.007: // - Monthly climate variable adjustments added. -// // Build 5.1.008: // - Number of parallel threads for dynamic wave routing added. // - Minimum dynamic wave routing variable time step added. -// // Build 5.1.011: // - Changed WarningCode to Warnings (# warnings issued) -// - Added error message text as a variable. +// - Added error message text as a variable. // - Added elapsed simulation time (in decimal days) variable. // - Added variables associated with detailed routing events. -// // Build 5.1.012: // - InSteadyState variable made local to routing_execute in routing.c. -// // Build 5.1.013: // - CrownCutoff and RuleStep added as analysis option variables. -// // Build 5.1.015: // - Fixes bug in summary statistics when Report Start date > Start Date. //----------------------------------------------------------------------------- @@ -60,8 +49,8 @@ EXTERN TFile EXTERN long Nperiods, // Number of reporting periods - TotalStepCount, // Total routing steps used //(5.1.015) - ReportStepCount, // Reporting routing steps used //(5.1.015) + TotalStepCount, // Total routing steps used + ReportStepCount, // Reporting routing steps used NonConvergeCount; // Number of non-converging steps EXTERN char @@ -83,7 +72,7 @@ EXTERN int RouteModel, // Flow routing method ForceMainEqn, // Flow equation for force mains LinkOffsets, // Link offset convention - SurchargeMethod, // EXTRAN or SLOT method //(5.1.013) + SurchargeMethod, // EXTRAN or SLOT method AllowPonding, // Allow water to pond at nodes InertDamping, // Degree of inertial damping NormalFlowLtd, // Normal flow limited @@ -101,7 +90,7 @@ EXTERN int WetStep, // Runoff wet time step (sec) DryStep, // Runoff dry time step (sec) ReportStep, // Reporting time step (sec) - RuleStep, // Rule evaluation time step (sec) //(5.1.013) + RuleStep, // Rule evaluation time step (sec) SweepStart, // Day of year when sweeping starts SweepEnd, // Day of year when sweeping ends MaxTrials, // Max. trials for DW routing @@ -124,7 +113,7 @@ EXTERN double HeadTol, // DW routing head tolerance (ft) SysFlowTol, // Tolerance for steady system flow LatFlowTol, // Tolerance for steady nodal inflow - CrownCutoff; // Fractional pipe crown cutoff //(5.1.013) + CrownCutoff; // Fractional pipe crown cutoff EXTERN DateTime StartDate, // Starting date diff --git a/src/solver/gwater.c b/src/solver/gwater.c index 8de1ba85e..7fa7d1350 100644 --- a/src/solver/gwater.c +++ b/src/solver/gwater.c @@ -2,28 +2,24 @@ // gwater.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/19/14 (Build 5.1.000) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 08/05/15 (Build 5.1.010) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Groundwater functions. // +// Update History +// ============== // Build 5.1.007: // - User-supplied function for deep GW seepage flow added. // - New variable names for use in user-supplied GW flow equations added. -// // Build 5.1.008: // - More variable names for user-supplied GW flow equations added. // - Subcatchment area made into a shared variable. // - Evaporation loss initialized to 0. // - Support for collecting GW statistics added. -// // Build 5.1.010: // - Unsaturated hydraulic conductivity added to GW flow equation variables. -// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -306,7 +302,7 @@ int gwater_readFlowExpression(char* tok[], int ntoks) // (getVariableIndex is the function that converts a GW // variable's name into an index number) expr = mathexpr_create(exprStr, getVariableIndex); - if ( expr == NULL ) return error_setInpError(ERR_TREATMENT_EXPR, ""); + if ( expr == NULL ) return error_setInpError(ERR_MATH_EXPR, ""); // --- save expression tree with the subcatchment if ( k == 1 ) Subcatch[j].gwLatFlowExpr = expr; diff --git a/src/solver/headers.h b/src/solver/headers.h index eafda8a2c..0b0577867 100644 --- a/src/solver/headers.h +++ b/src/solver/headers.h @@ -2,15 +2,14 @@ // headers.h // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Header files included in most SWMM5 modules. // // DO NOT CHANGE THE ORDER OF THE #INCLUDE STATEMENTS //----------------------------------------------------------------------------- - #include #include "consts.h" #include "macros.h" diff --git a/src/solver/hotstart.c b/src/solver/hotstart.c index 33c005e45..71a6d2a94 100644 --- a/src/solver/hotstart.c +++ b/src/solver/hotstart.c @@ -2,17 +2,12 @@ // hotstart.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 03/28/14 (Build 5.1.002) -// 04/23/14 (Build 5.1.005) -// 03/19/15 (Build 5.1.008) -// 08/01/16 (Build 5.1.011) -// 04/01/20 (Build 5.1.015) -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // // Hot Start file functions. - +// // A SWMM hot start file contains the state of a SWMM project after // a simulation has been run, allowing it to be used to initialize // a subsequent simulation that picks up where the previous run ended. @@ -29,15 +24,15 @@ // insure that these components are of the same sub-type and maintain // the same order as when the hot start file was created. // +// Update History +// ============== // Build 5.1.008: // - Storage node hydraulic residence time (HRT) was added to the file. // - Link control settings are now applied when reading a hot start file. // - Runoff read from file assigned to newRunoff property instead of oldRunoff. // - Array indexing bug when reading snowpack state from file fixed. -// // Build 5.1.011: // - Link control setting bug when reading a hot start file fixed. -// // Build 5.1.015: // - Support added for multiple infiltration methods within a project. //----------------------------------------------------------------------------- @@ -381,7 +376,7 @@ void saveRunoff(void) // Infiltration state (max. of 6 elements) for (j=0; jtp = 0.0; infil->Fe = 0.0; - infil->Fmh = 0.0; //(5.1.015) } //============================================================================= @@ -407,8 +397,8 @@ double horton_getInfil(THorton *infil, double tstep, double irate, double depth) double fa, fp = 0.0; double Fp, F1, t1, tlim, ex, kt; double FF, FF1, r; - double f0 = infil->f0 * InfilFactor; //(5.1.013) - double fmin = infil->fmin * InfilFactor; //(5.1.013) + double f0 = infil->f0 * InfilFactor; + double fmin = infil->fmin * InfilFactor; double Fmax = infil->Fmax; double tp = infil->tp; double df = f0 - fmin; @@ -503,7 +493,6 @@ double horton_getInfil(THorton *infil, double tstep, double irate, double depth) //============================================================================= -//// -------- Modified for release 5.1.015 -------- //// //(5.1.015) double modHorton_getInfil(THorton *infil, double tstep, double irate, double depth) // @@ -541,6 +530,9 @@ double modHorton_getInfil(THorton *infil, double tstep, double irate, // --- case where there is water to infiltrate if ( fa > ZERO ) { + // --- saturated condition + if ( infil->Fmax > 0.0 && infil->Fe >= infil->Fmax ) return 0.0; + // --- potential infiltration fp = f0 - kd * infil->Fe; fp = MAX(fp, fmin); @@ -548,27 +540,16 @@ double modHorton_getInfil(THorton *infil, double tstep, double irate, // --- actual infiltration f = MIN(fa, fp); - // --- limit cumulative infiltration to Fmax - if (infil->Fmax > 0.0) - { - if (infil->Fmh + f * tstep > infil->Fmax) - f = (infil->Fmax - infil->Fmh) / tstep; - f = MAX(f, 0.0); - infil->Fmh += f * tstep; - } - // --- new cumulative infiltration minus seepage infil->Fe += MAX((f - fmin), 0.0) * tstep; + if ( infil->Fmax > 0.0 ) infil->Fe = MAX(infil->Fe, infil->Fmax); } // --- reduce cumulative infiltration for dry condition else if (kr > 0.0) { - df = exp(-kr * tstep); - infil->Fe *= df; + infil->Fe *= exp(-kr * tstep); infil->Fe = MAX(infil->Fe, 0.0); - infil->Fmh *= df; - infil->Fmh = MAX(infil->Fmh, 0.0); } return f; } @@ -600,7 +581,7 @@ int grnampt_setParams(TGrnAmpt *infil, double p[]) { double ksat; // sat. hyd. conductivity in in/hr - if ( p[0] < 0.0 || p[1] <= 0.0 || p[2] < 0.0 ) return FALSE; + if ( p[0] < 0.0 || p[1] <= 0.0 || p[2] < 0.0 || p[2] > 1.0) return FALSE; infil->S = p[0] / UCF(RAINDEPTH); // Capillary suction head (ft) infil->Ks = p[1] / UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) infil->IMDmax = p[2]; // Max. init. moisture deficit @@ -664,7 +645,7 @@ double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, // { // --- find saturated upper soil zone water volume - Fumax = infil->IMDmax * infil->Lu * sqrt(InfilFactor); //(5.1.013) + Fumax = infil->IMDmax * infil->Lu * sqrt(InfilFactor); // --- reduce time until next event infil->T -= tstep; @@ -692,8 +673,8 @@ double grnampt_getUnsatInfil(TGrnAmpt *infil, double tstep, double irate, // { double ia, c1, F2, dF, Fs, kr, ts; - double ks = infil->Ks * InfilFactor; //(5.1.013) - double lu = infil->Lu * sqrt(InfilFactor); //(5.1.013) + double ks = infil->Ks * InfilFactor; + double lu = infil->Lu * sqrt(InfilFactor); // --- get available infiltration rate (rainfall + ponded water) ia = irate + depth / tstep; @@ -798,8 +779,8 @@ double grnampt_getSatInfil(TGrnAmpt *infil, double tstep, double irate, // { double ia, c1, dF, F2; - double ks = infil->Ks * InfilFactor; //(5.1.013) - double lu = infil->Lu * sqrt(InfilFactor); //(5.1.013) + double ks = infil->Ks * InfilFactor; + double lu = infil->Lu * sqrt(InfilFactor); // --- get available infiltration rate (rainfall + ponded water) ia = irate + depth / tstep; diff --git a/src/solver/infil.h b/src/solver/infil.h index c7d6fb4e4..e1d98690e 100644 --- a/src/solver/infil.h +++ b/src/solver/infil.h @@ -2,31 +2,25 @@ // infil.h // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 09/15/14 (Build 5.1.007) -// 08/05/15 (Build 5.1.010) -// 05/10/18 (Build 5.1.013) -// 04/01/20 (Build 5.1.015) -// Author: L. Rossman (US EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // // Public interface for infiltration functions. // +// Update History +// ============== // Build 5.1.010: // - New Modified Green Ampt infiltration option added. -// // Build 5.1.013: // - New function infil_setInfilFactor() added. -// // Build 5.1.015: // - Support added for multiple infiltration methods within a project. -// - Fixed max. infiltration feature for modified Horton method. //----------------------------------------------------------------------------- #ifndef INFIL_H #define INFIL_H - //--------------------- // Enumerated Constants //--------------------- @@ -50,7 +44,6 @@ typedef struct //----------------------------- double tp; // present time on infiltration curve (sec) double Fe; // cumulative infiltration (ft) - double Fmh; // mod Horton cumulative infiltration (ft) //(5.1.015) } THorton; @@ -116,5 +109,4 @@ void grnampt_initState(TGrnAmpt *infil); double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, double depth, int modelType); - -#endif //INFIL_H +#endif diff --git a/src/solver/inflow.c b/src/solver/inflow.c index 42976c13e..9fdf4de51 100644 --- a/src/solver/inflow.c +++ b/src/solver/inflow.c @@ -2,8 +2,8 @@ // inflow.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Manages any Direct External or Dry Weather Flow inflows diff --git a/src/solver/inlet.c b/src/solver/inlet.c new file mode 100644 index 000000000..d8eef5b1d --- /dev/null +++ b/src/solver/inlet.c @@ -0,0 +1,1641 @@ +//----------------------------------------------------------------------------- +// inlet.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman +// +// Street/Channel Inlet Functions +// +// Computes capture efficiency of inlets placed in Street conduits +// or Rectangular/Trapezoidal channels using FHWA HEC-22 methods (see +// Brown, S.A. et al., Urban Drainage Design Manual, Federal Highway +// Administration Hydraulic Engineering Circular No. 22, 3rd Edition, +// FHWA-NHI-10-009, August 2013). +// +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include "headers.h" +#include "street.h" +#include +#include +#include + +//----------------------------------------------------------------------------- +// Enumerations +//----------------------------------------------------------------------------- + +enum InletType { + GRATE_INLET, CURB_INLET, COMBO_INLET, SLOTTED_INLET, + DROP_GRATE_INLET, DROP_CURB_INLET, CUSTOM_INLET +}; + +enum GrateType { + P50, P50x100, P30, CURVED_VANE, TILT_BAR_45, + TILT_BAR_30, RETICULINE, GENERIC +}; + +enum InletPlacementType { AUTOMATIC, ON_GRADE, ON_SAG }; + +enum ThroatAngleType { HORIZONTAL_THROAT, INCLINED_THROAT, VERTICAL_THROAT }; + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static char* InletTypeWords[] = + {"GRATE", "CURB", "", "SLOTTED", "DROP_GRATE", "DROP_CURB", "CUSTOM", NULL}; + +static char* GrateTypeWords[] = + {"P_BAR-50", "P_BAR-50x100", "P_BAR-30", "CURVED_VANE", "TILT_BAR-45", "TILT_BAR-30", + "RETICULINE", "GENERIC", NULL}; + +static char* ThroatAngleWords[] = + {"HORIZONTAL", "INCLINED", "VERTICAL", NULL}; + +static char *PlacementTypeWords[] = + {"AUTOMATIC", "ON_GRADE", "ON_SAG"}; + +// Coefficients for cubic polynomials fitted to Splash Over Velocity v. +// Grate Length curves in Chart 5B of HEC-22 manual taken from Denver +// UDFCD manual. +static const double SplashCoeffs[][4] = { + {2.22, 4.03, 0.65, 0.06}, //P_BAR-50 + {0.74, 2.44, 0.27, 0.02}, //P_BAR-50x100 + {1.76, 3.12, 0.45, 0.03}, //P_BAR-30 + {0.30, 4.85, 1.31, 0.15}, //Curved_Vane + {0.99, 2.64, 0.36, 0.03}, //Tilt_Bar-45 + {0.51, 2.34, 0.2, 0.01}, //Tilt_Bar-30 + {0.28, 2.28, 0.18, 0.01}}; //Reticuline + +// Grate opening ratios (Chart 9B of HEC-22 manual) +static const double GrateOpeningRatios[] = { + 0.90, //P_BAR-50 + 0.80, //P_BAR-50x100 + 0.60, //P_BAR-30 + 0.35, //Curved_Vane + 0.17, //Tilt_Bar-45 (assumed) + 0.34, //Tilt_Bar-30 + 0.80, //Reticuline + 1.00}; //Generic + +//----------------------------------------------------------------------------- +// Imported Variables +//----------------------------------------------------------------------------- +extern TLinkStats* LinkStats; // defined in STATS.C + +//----------------------------------------------------------------------------- +// Local Shared Variables +//----------------------------------------------------------------------------- +// Variables as named in the HEC-22 manual. +static double Sx; // street cross slope +static double SL; // conduit longitudinal slope +static double Sw; // street gutter slope +static double a; // street gutter depression (ft) +static double W; // street gutter width (ft) +static double T; // top width of flow spread (ft) +static double n; // Manning's roughness coeff. + +// Additional variables +static int Nsides; // 1- or 2-sided street +static double Tcrown; // distance from street curb to crown (ft) +static double Beta; // = 1.486 * sqrt(SL) / n +static double Qfactor; // factor f in Izzard's eqn. Q = f*T^2.67 +static TXsect *xsect; // cross-section data of inlet's conduit +static char *InletDegree; // # inflow links to inlet-connected nodes +static double *InletFlow; // captured inlet flow received by each node +static TInlet* FirstInlet; // head of list of deployed inlets + +//----------------------------------------------------------------------------- +// External functions (declared in inlet.h) +//----------------------------------------------------------------------------- +// inlet_create called by createObjects in project.c +// inlet_delete called by deleteObjects in project.c +// inlet_readDesignParams called by parseLine in input.c +// inlet_readUsageParams called by parseLine in input.c +// inlet_validate called by project_validate +// inlet_findInletFlows called by routing_execute +// inlet_convertOverflows called by routing_execute +// inlet_writeStatsReport called by statsrpt_writeReport +// inlet_capturedFlow called by findLinkMassFlow in qualrout.c + + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int readGrateInletParams(int inletIndex, char* tok[], int ntoks); +static int readCurbInletParams(int inletIndex, char* tok[], int ntoks); +static int readSlottedInletParams(int inletIndex, char* tok[], int ntoks); +static int readCustomInletParams(int inletIndex, char* tok[], int ntoks); + +static void initInletStats(TInlet *inlet); +static void updateInletStats(TInlet *inlet, double q); +static void writeStreetStatsHeader(); +static void writeStreetStats(int link); + +static int getInletPlacement(TInlet *inlet, int node); +static void getConduitGeometry(TInlet *inlet); +static double getFlowSpread(double flow); +static double getEo(double slopeRatio, double spread, double gutterWidth); + +static double getOnGradeCapturedFlow(TInlet *inlet, double flow, double depth); +static double getOnGradeInletCapture(int inletIndex, double flow, double depth); +static double getGrateInletCapture(int inletIndex, double flow); +static double getCurbInletCapture(double flow, double length); +static double getGutterFlowRatio(double gutterWidth); +static double getGutterAreaRatio(double grateWidth, double area); +static double getSplashOverVelocity(int grateType, double grateLength); + +static double getOnSagCapturedFlow(TInlet *inlet, double flow, double depth); +static double getOnSagInletCapture(int inletIndex, double depth); +static void findOnSagGrateFlows(int inletIndex, double depth, + double *weirFlow, double *orificeFlow); +static void findOnSagCurbFlows(int inletIndex, double depth, + double openingLength, double *weirFlow, + double *orificeFlow); +static double getCurbOrificeFlow(double flowDepth, double openingHeight, + double openingLength, int throatAngle); +static double getOnSagSlottedFlow(int inletIndex, double depth); + +//============================================================================= + +int inlet_create(int numInlets) +// +// Input: numInlets = number of inlet designs to create +// Output: none +// Purpose: creats a collection of inlet designs. +// +{ + int i; + + InletDesigns = NULL; + InletDegree = NULL; + InletFlow = NULL; + InletDesignCount = 0; + UsesInlets = FALSE; + FirstInlet = NULL; + InletDesigns = (TInletDesign *)calloc(numInlets, sizeof(TInletDesign)); + if (InletDesigns == NULL) return ERR_MEMORY; + InletDesignCount = numInlets; + + InletDegree = (char *)calloc(Nobjects[NODE], sizeof(char)); + if (InletDegree == NULL) return ERR_MEMORY; + InletFlow = (double *)calloc(Nobjects[NODE], sizeof(double)); + if (InletFlow == NULL) return ERR_MEMORY; + + for (i = 0; i < InletDesignCount; i++) + { + InletDesigns[i].customInlet.onGradeCurve = -1; + InletDesigns[i].customInlet.onSagCurve = -1; + InletDesigns[i].curbInlet.length = 0.0; + InletDesigns[i].grateInlet.length = 0.0; + InletDesigns[i].slottedInlet.length = 0.0; + InletDesigns[i].type = CUSTOM_INLET; + } + return 0; +} + +//============================================================================= + +void inlet_delete() +// +// Input: none +// Output: none +// Purpose: frees all memory allocated for inlet analysis. +// +{ + TInlet *inlet = FirstInlet; + TInlet *nextInlet; + while (inlet) + { + nextInlet = inlet->nextInlet; + free(inlet); + inlet = nextInlet; + } + FirstInlet = NULL; + FREE(InletFlow); + FREE(InletDegree); + FREE(InletDesigns); +} + +//============================================================================= + +int inlet_readDesignParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts a set of inlet design parameters from a tokenized line +// of the [INLETS] section of a SWMM input file. +// +// Format of input line is: +// ID GRATE Length Width GrateType (OpenArea) (SplashVeloc) +// ID CURB Length Height (ThroatType) +// ID SLOTTED Length Width +// ID DROP_GRATE Length Width GrateType (OpenArea) (SplashVeloc) +// ID DROP_CURB Length Height +// ID CUSTOM DiversionCurve RatingCurve +// +{ + int i; + + // --- check for minimum number of tokens + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that design ID already registered in project + i = project_findObject(INLET, tok[0]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[0]); + InletDesigns[i].ID = project_findID(INLET, tok[0]); + + // --- retrieve type of inlet design + InletDesigns[i].type = findmatch(tok[1], InletTypeWords); + + // --- read inlet's design parameters + switch (InletDesigns[i].type) + { + case GRATE_INLET: + case DROP_GRATE_INLET: + return readGrateInletParams(i, tok, ntoks); + case CURB_INLET: + case DROP_CURB_INLET: + return readCurbInletParams(i, tok, ntoks); + case SLOTTED_INLET: + return readSlottedInletParams(i, tok, ntoks); + case CUSTOM_INLET: + return readCustomInletParams(i, tok, ntoks); + default: return error_setInpError(ERR_KEYWORD, tok[1]); + } + return 0; +} +//============================================================================= + +int inlet_readUsageParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts inlet usage parameters from a tokenized line +// of the [INLET_USAGE] section of a SWMM input file. +// +// Format of input line is: +// linkID inletID nodeID (#Inlets %Clog Qmax aLocal wLocal placement) +// where +// linkID = ID name of link containing the inlet +// inletID = ID name of inlet design being used +// nodeID = ID name of node receiving captured flow +// #Inlets = number of identical inlets used (default = 1) +// %Clog = percent that inlet is clogged +// Qmax = maximum flow that inlet can capture (default = 0 (no limit)) +// aLocal = local gutter depression (ft or m) (default = 0) +// wLocal = width of local gutter depression (ft or m) (default = 0) +// placement = ON_GRADE, ON_SAG, or AUTO (the default) +// +{ + int linkIndex, designIndex, nodeIndex, numInlets = 1; + int placement = AUTOMATIC; + double flowLimit = 0.0, pctClogged = 0.0; + double aLocal = 0.0, wLocal = 0.0; + TInlet *inlet; + + // --- check that inlet's link exists + if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); + linkIndex = project_findObject(LINK, tok[0]); + if (linkIndex < 0) return error_setInpError(ERR_NAME, tok[0]); + + // --- check that inlet design type exists + designIndex = project_findObject(INLET, tok[1]); + if (designIndex < 0) return error_setInpError(ERR_NAME, tok[1]); + + // --- check that receiving node exists + nodeIndex = project_findObject(NODE, tok[2]); + if (nodeIndex < 0) return error_setInpError(ERR_NAME, tok[2]); + + // --- get number of inlets + if (ntoks > 3) + if (!getInt(tok[3], &numInlets) || numInlets < 0) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- get flow limit & percent clogged + if (ntoks > 4) + if (!getDouble(tok[4], &pctClogged) || pctClogged < 0.0 + || pctClogged > 100.) + return error_setInpError(ERR_NUMBER, tok[4]); + if (ntoks > 5) + if (!getDouble(tok[5], &flowLimit) || flowLimit < 0.0) + return error_setInpError(ERR_NUMBER, tok[5]); + + // --- get local depression parameters + if (ntoks > 6) + if (!getDouble(tok[6], &aLocal) || aLocal < 0.0) + return error_setInpError(ERR_NUMBER, tok[6]); + if (ntoks > 7) + if (!getDouble(tok[7], &wLocal) || wLocal < 0.0) + return error_setInpError(ERR_NUMBER, tok[7]); + + // --- get inlet placement + if (ntoks > 8) + { + placement = findmatch(tok[8], PlacementTypeWords); + if (placement < 0) return error_setInpError(ERR_KEYWORD, tok[8]); + } + + // --- create an inlet usage object for the link + inlet = Link[linkIndex].inlet; + if (inlet == NULL) + { + inlet = (TInlet *)malloc(sizeof(TInlet)); + if (!inlet) return error_setInpError(ERR_MEMORY, ""); + Link[linkIndex].inlet = inlet; + inlet->nextInlet = FirstInlet; + FirstInlet = inlet; + } + + // --- save inlet usage parameters + inlet->linkIndex = linkIndex; + inlet->designIndex = designIndex; + inlet->nodeIndex = nodeIndex; + inlet->numInlets = numInlets; + inlet->placement = placement; + inlet->clogFactor = 1.0 - (pctClogged / 100.); + inlet->flowLimit = flowLimit / UCF(FLOW); + inlet->localDepress = aLocal / UCF(LENGTH); + inlet->localWidth = wLocal / UCF(LENGTH); + inlet->flowFactor = 0.0; + initInletStats(inlet); + UsesInlets = TRUE; + return 0; +} + +//============================================================================= + +void inlet_validate() +// +// Input: none +// Output: none +// Purpose: checks that inlets have been assigned to conduits with proper +// cross section shapes and counts the number of inlets that each +// node receives either bypased or captured flow from. +// +{ + int i, inletType, isValid; + TInlet *inlet; + TInlet *prevInlet; + + // --- traverse the list of inlets placed in conduits + memset(InletDegree, 0, Nobjects[NODE] * sizeof(char)); + prevInlet = FirstInlet; + inlet = FirstInlet; + while (inlet) + { + // --- check that inlet's conduit can accept the inlet's type + isValid = FALSE; + i = inlet->linkIndex; + xsect = &Link[i].xsect; + inletType = InletDesigns[inlet->designIndex].type; + if (xsect->type == TRAPEZOIDAL && + (inletType == DROP_GRATE_INLET || + inletType == DROP_CURB_INLET || + inletType == CUSTOM_INLET)) + isValid = TRUE; + else if (xsect->type == STREET_XSECT && + inletType != DROP_GRATE_INLET && + inletType != DROP_CURB_INLET) + isValid = TRUE; + + // --- if inlet placement is valid then + if (isValid) + { + // --- update inlet count for inlet's bypass and capture nodes + inlet->backflow = 0.0; + InletDegree[Link[i].node2]++; + InletDegree[inlet->nodeIndex]++; + + // --- compute street inlet's flow factor + // (where Q = flowFactor * Spread^2.67) + getConduitGeometry(inlet); + inlet->flowFactor = (0.56/n) * pow(SL,0.5) * pow(Sx,1.67); + + // --- save reference to current inlet & continue to next inlet + prevInlet = inlet; + inlet = inlet->nextInlet; + } + + // --- if inlet placement is not valid then issue a warning message + // and remove the inlet from the conduit + else + { + report_writeWarningMsg(WARN12, Link[i].ID); + if (inlet == FirstInlet) + { + FirstInlet = inlet->nextInlet; + prevInlet = FirstInlet; + free(inlet); + inlet = FirstInlet; + } + else + { + prevInlet->nextInlet = inlet->nextInlet; + free(inlet); + inlet = prevInlet->nextInlet; + } + Link[i].inlet = NULL; + } + } +} + +//============================================================================= + +void inlet_findInletFlows(double tStep) +// +// Input: tStep = current flow routing time step (sec) +// Output: none +// Purpose: computes flow captured by each inlet and adjusts the +// lateral flows of the inlet's bpass and capture nodes accordingly. +// +// This function is called after regular lateral flows to all nodes have been +// set but before a flow routing step has been taken. +{ + int i, j, m, p, placement; + double q, w; + TInlet *inlet; + + // --- For non-DW routing find conduit flow into each node + // (used to limit max. amount of on-sag capture) + if (RouteModel != DW) + { + for (j = 0; j < Nobjects[NODE]; j++) + Node[j].inflow = 0.0; + for (i = 0; i < Nobjects[LINK]; i++) + Node[Link[i].node2].inflow += Link[i].newFlow; + } + + // --- loop through each inlet + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- identify index of inlet's downstream node + i = inlet->linkIndex; + j = Link[i].node2; + + // --- get inlet's placement (ON_GRADE or ON_SAG) + placement = getInletPlacement(inlet, j); + + // --- find flow captured by on-grade inlet + q = fabs(Link[i].newFlow); + if (placement == ON_GRADE) + { + inlet->outflow = getOnGradeCapturedFlow(inlet, q, Node[j].newDepth); + } + + // --- find flow captured by on-sag inlet + else + { + inlet->outflow = getOnSagCapturedFlow(inlet, q, Node[j].newDepth); + } + if (fabs(inlet->outflow) < FUDGE) inlet->outflow = 0.0; + + // --- add to total flow captured by inlet's node + InletFlow[j] += inlet->outflow; + } + + // --- make second pass through each inlet + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- identify indexes of inlet's bypass (j) and capture (m) nodes + i = inlet->linkIndex; + j = Link[i].node2; + m = inlet->nodeIndex; + + // --- for on-sag placement under non-DW routing, captured flow + // is limited to inlet's share of bypass node's inflow plus + // any stored volume + if (RouteModel != DW && placement == ON_SAG) + { + q = Node[j].newVolume / tStep; + q += MAX(Node[j].inflow, 0.0); + if (InletFlow[j] > q) + inlet->outflow *= q / InletFlow[j]; + } + + // --- adjust lateral flows at bypass and capture nodes + // (subtract captured flow from bypass node, add it to capture + // node, and add any backflow to bypass node) + Node[j].newLatFlow -= (inlet->outflow - inlet->backflow); + Node[m].newLatFlow += inlet->outflow; + + // --- account for pollutant transfer between bypass and capture nodes + for (p = 0; p < Nobjects[POLLUT]; p++) + { + w = inlet->outflow * Node[j].oldQual[p]; + Node[m].newQual[p] += w; + + if (RouteModel != DW) + { + w = inlet->backflow * Node[m].oldQual[p]; + Node[j].newQual[p] += w; + } + } + + // --- update inlet's performance if reporting has begun + if (getDateTime(NewRoutingTime) > ReportStart) + updateInletStats(inlet, fabs(Link[i].newFlow)); + } +} + +//============================================================================= + +void inlet_convertOverflows() +// +// Input: none +// Output: none +// Purpose: converts any overflows at capture nodes to inlet backflow. +// +// This function is called after a flow routing time step has been taken. +{ + int i, j, m; + TInlet *inlet; + + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- identify inlet's bypass and capture nodes + i = inlet->linkIndex; + j = Link[i].node2; + m = inlet->nodeIndex; + + // --- save capture node's overflow as inlet's backflow + if (InletDegree[m] > 0) + { + inlet->backflow = Node[m].overflow / (double)InletDegree[m]; + if (fabs(inlet->backflow) < FUDGE) inlet->backflow = 0.0; + } + + // --- remove all captured flow at the bypass node + InletFlow[j] = 0.0; + } + + // --- remove overflows at all inlet capture nodes + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + Node[inlet->nodeIndex].overflow = 0.0; + } +} + +//============================================================================= + +void inlet_writeStatsReport() +// +// Input: none +// Output: none +// Purpose: writes table of street & inlet flow statistics to SWMM's report file. +// +{ + int j, header = FALSE; + + if (Nobjects[STREET] == 0) return; + for (j = 0; j < Nobjects[LINK]; j++) + { + if (Link[j].xsect.type == STREET_XSECT) + { + if (!header) + { + writeStreetStatsHeader(); + header = TRUE; + } + writeStreetStats(j); + } + } + report_writeLine(""); +} + +//============================================================================= + +double inlet_capturedFlow(int i) +// +// Input: i = a link index +// Output: returns captured flow rate (cfs) +// Purpose: gets the current flow captured by an inlet. +// +{ + if (Link[i].inlet) return Link[i].inlet->outflow; + return 0.0; +} + +//============================================================================= + +int readGrateInletParams(int i, char* tok[], int ntoks) +{ +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts a grate's inlet parameters from a set of string tokens. +// + int grateType; + double width, length, areaRatio = 0.0, vSplash = 0.0; + + // --- check for enough tokens + if (ntoks < 5) return error_setInpError(ERR_ITEMS, ""); + + // --- retrieve length & width + if (!getDouble(tok[2], &length) || length <= 0.0) + return error_setInpError(ERR_NUMBER, tok[2]); + if (!getDouble(tok[3], &width) || width <= 0.0) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- retrieve grate type + grateType = findmatch(tok[4], GrateTypeWords); + if (grateType < 0) return error_setInpError(ERR_KEYWORD, tok[4]); + + // --- only read open area & splash velocity for GENERIC type grate + if (grateType == GENERIC) + { + if (ntoks < 6) return error_setInpError(ERR_ITEMS, ""); + if (!getDouble(tok[5], &areaRatio) || areaRatio <= 0.0 + || areaRatio > 1.0) return error_setInpError(ERR_NUMBER, tok[5]); + if (ntoks > 6) + { + if (!getDouble(tok[6], &vSplash) || vSplash < 0.0) + return error_setInpError(ERR_NUMBER, tok[6]); + } + } + + // --- save grate inlet parameters + InletDesigns[i].grateInlet.length = length / UCF(LENGTH); + InletDesigns[i].grateInlet.width = width / UCF(LENGTH); + InletDesigns[i].grateInlet.type = grateType; + InletDesigns[i].grateInlet.fracOpenArea = areaRatio; + InletDesigns[i].grateInlet.splashVeloc = vSplash; + + // --- check if grate is part of a combo inlet (grate is a street and + // not a drop grate and data for a curb opening inlet has already + // been provided) + if (InletDesigns[i].type == GRATE_INLET && + InletDesigns[i].curbInlet.length > 0.0) + InletDesigns[i].type = COMBO_INLET; + return 0; +} + +//============================================================================= + +int readCurbInletParams(int i, char* tok[], int ntoks) +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts curb opening inlet parameters from a set of string tokens. +// +{ + int throatAngle; + double height, length; + + // --- check for enough tokens + if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); + + // --- retrieve length & width of opening + if (!getDouble(tok[2], &length) || length <= 0.0) + return error_setInpError(ERR_NUMBER, tok[2]); + if (!getDouble(tok[3], &height) || height <= 0.0) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- retrieve type of throat angle + if (InletDesigns[i].type == CURB_INLET) + { + throatAngle = HORIZONTAL_THROAT; + if (ntoks > 4) + { + throatAngle = findmatch(tok[4], ThroatAngleWords); + if (throatAngle < 0) return error_setInpError(ERR_KEYWORD, tok[4]); + } + } + else throatAngle = VERTICAL_THROAT; + + // ---- save curb opening inlet parameters + InletDesigns[i].curbInlet.length = length / UCF(LENGTH); + InletDesigns[i].curbInlet.height = height / UCF(LENGTH); + InletDesigns[i].curbInlet.throatAngle = throatAngle; + + // --- check if curb inlet is part of a combo inlet (opening is for a + // street and not a drop inlet and data for a grate inlet has already + // been provided) + if (InletDesigns[i].type == CURB_INLET && + InletDesigns[i].grateInlet.length > 0.0) + InletDesigns[i].type = COMBO_INLET; + return 0; +} + +//============================================================================= + +int readSlottedInletParams(int i, char* tok[], int ntoks) +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts slotted drain inlet parameters from a set of string tokens. +// +{ + double width, length; + + // --- check for enough tokens + if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); + + // --- retrieve length and width + if (!getDouble(tok[2], &length) || length <= 0.0) + return error_setInpError(ERR_NUMBER, tok[2]); + if (!getDouble(tok[3], &width) || width <= 0.0) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- save slotted inlet parameters + InletDesigns[i].slottedInlet.length = length / UCF(LENGTH); + InletDesigns[i].slottedInlet.width = width / UCF(LENGTH); + return 0; +} + +//============================================================================= + +int readCustomInletParams(int i, char* tok[], int ntoks) +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts custom inlet parameters from a set of string tokens. +// +{ + int c2, c3; // capture curve indexes + + if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); + if (match(tok[2], "*")) c2 = -1; + else + { + c2 = project_findObject(CURVE, tok[2]); + if (c2 < 0) return error_setInpError(ERR_NAME, tok[2]); + } + InletDesigns[i].customInlet.onGradeCurve = c2; + if (match(tok[3], "*")) + { + c3 = -1; + if (c2 == -1) return error_setInpError(ERR_NAME, tok[3]); + } + else + { + c3 = project_findObject(CURVE, tok[3]); + if (c3 < 0) return error_setInpError(ERR_NAME, tok[3]); + } + InletDesigns[i].customInlet.onSagCurve = c3; + return 0; +} + +//============================================================================= + +void initInletStats(TInlet *inlet) +// +// Input: inlet = an inlet object placed in a conduit link +// Output: none +// Purpose: initializes the performance statistics of an inlet. +// +{ + if (inlet) + { + inlet->outflow = 0.0; + inlet->backflow = 0.0; + inlet->stats.capturePeriods = 0; + inlet->stats.backflowPeriods = 0; + inlet->stats.peakFlow = 0.0; + inlet->stats.peakFlowCapture = 0; + inlet->stats.avgFlowCapture = 0; + inlet->stats.bypassFreq = 0; + } +} + +//============================================================================= + +void updateInletStats(TInlet *inlet, double q) +// +// Input: inlet = an inlet object placed in a conduit link +// q = inlet's approach flow (cfs) +// Output: none +// Purpose: updates the performance statistics of an inlet. +// +{ + double qCapture = inlet->outflow, + qBackflow = inlet->backflow, + qNet = qCapture - qBackflow, + qBypass = q - qNet, + fCapture = 0.0; + + // --- check for no flow condition + if (q < MIN_RUNOFF_FLOW && qBackflow <= 0.0) return; + inlet->stats.flowPeriods++; + + // --- there is positive net flow from inlet to capture node + if (qNet > 0.0) + { + inlet->stats.capturePeriods++; + fCapture = qNet / q; + fCapture = MIN(fCapture, 1.0); + inlet->stats.avgFlowCapture += fCapture; + if (qBypass > MIN_RUNOFF_FLOW) inlet->stats.bypassFreq++; + } + + // --- otherwise inlet receives backflow from capture node + else inlet->stats.backflowPeriods++; + + // --- update peak flow stats + if (q > inlet->stats.peakFlow) + { + inlet->stats.peakFlow = q; + inlet->stats.peakFlowCapture = fCapture * 100.0; + } +} + +//============================================================================= + +void writeStreetStatsHeader() +{ + report_writeLine(""); + report_writeLine("*******************"); + report_writeLine("Street Flow Summary"); + report_writeLine("*******************"); + report_writeLine(""); + fprintf(Frpt.file, +"\n ------------------------------------------------------------------------------------------------------------" +"\n Peak Maximum Maximum Peak Flow Average Bypass BackFlow" +"\n Flow Spread Depth Inlet Capture Capture Frequency Frequency"); + if (UnitSystem == US) fprintf(Frpt.file, +"\n Street Conduit %3s ft ft Design %% %% %% %%", + FlowUnitWords[FlowUnits]); + else fprintf(Frpt.file, +"\n Conduit %3s m m Design %% %% %% %%", + FlowUnitWords[FlowUnits]); + fprintf(Frpt.file, +"\n ------------------------------------------------------------------------------------------------------------"); +} + +//============================================================================= + +void writeStreetStats(int link) +// +// Input: link = index of a conduit link containing an inlet +// Output: none +// Purpose: writes flow statistics for a Street conduit and its inlet +// to SWMM's report file. +// +{ + int k, t; + double maxSpread, maxDepth, peakFlow; + double fp, cp, afc = 0.0, bpf = 0.0; + TInlet *inlet; + + // --- retrieve street parameters + k = Link[link].subIndex; + t = Link[link].xsect.transect; + + // --- get depth & spread at peak flow + // (based on flow routing result and street's transect geometry) + peakFlow = LinkStats[link].maxFlow; + maxDepth = LinkStats[link].maxDepth; + maxSpread = xsect_getWofY(&Link[link].xsect, maxDepth) / Street[t].sides; +/* + // Alternate method from HEC-22's use of Izzard's form of the Manning eqn.) + Sx = Street[t].slope; + a = Street[t].gutterDepression; + W = Street[t].gutterWidth; + n = Street[t].roughness; + Qfactor = (0.56 / n) * sqrt(Conduit[k].slope) * pow(Sx, 1.67); + maxSpread = getFlowSpread(peakFlow / Street[t].sides); + maxDepth = maxSpread * Sx + a; +*/ + // --- write street stats + fprintf(Frpt.file, "\n %-16s", Link[link].ID); + fprintf(Frpt.file, " %9.2f", peakFlow * UCF(FLOW)); + fprintf(Frpt.file, " %9.2f", maxSpread * UCF(LENGTH)); + fprintf(Frpt.file, " %9.2f", maxDepth * UCF(LENGTH)); + + // --- write inlet stats + inlet = Link[link].inlet; + if (inlet) + { + fprintf(Frpt.file, " %-16s", InletDesigns[inlet->designIndex].ID); + fp = inlet->stats.flowPeriods / 100.0; + if (fp > 0.0) + { + cp = inlet->stats.capturePeriods / 100.0; + fprintf(Frpt.file, " %9.2f", inlet->stats.peakFlowCapture); + if (cp > 0.0) + { + afc = inlet->stats.avgFlowCapture / cp; + bpf = inlet->stats.bypassFreq / cp; + } + fprintf(Frpt.file, " %9.2f", afc); + fprintf(Frpt.file, " %9.2f", bpf); + fprintf(Frpt.file, " %9.2f", inlet->stats.backflowPeriods / fp); + } + } +} + +//============================================================================= + +int getInletPlacement(TInlet *inlet, int j) +// +// Input: inlet = an inlet object placed in a conduit link +// j = index of inlet's bypass node +// Output: returns type of inlet placement +// Purpose: determines actual placement for an inlet with AUTOMATIC placement. +// +{ + if (inlet->placement == AUTOMATIC) + { + if (Node[j].degree > 0) return ON_GRADE; + else return ON_SAG; + } + else return inlet->placement; +} + +//============================================================================= + +void getConduitGeometry(TInlet *inlet) +// +// Input: inlet = an inlet object placed in a conduit link +// Output: none +// Purpose: assigns properties of an inlet's conduit to +// module-level shared variables used by other functions. +// +{ + int linkIndex = inlet->linkIndex; + int t, k = Link[linkIndex].subIndex; + + SL = Conduit[k].slope; // longitudinal slope + Beta = Conduit[k].beta; // 1.486 * sqrt(SL) / n + xsect = &Link[linkIndex].xsect; + + // --- if conduit has a Street cross section + if (xsect->type == STREET_XSECT) + { + t = Link[linkIndex].xsect.transect; + Sx = Street[t].slope; // street cross slope + a = Street[t].gutterDepression; // gutter depression + W = Street[t].gutterWidth; // gutter width + n = Street[t].roughness; // street roughness + Nsides = Street[t].sides; // 1 or 2 sided street + Tcrown = Street[t].width; // distance from curb to crown + Qfactor = inlet->flowFactor; // factor used in Izzard's eqn. + + // --- add inlet's local depression to street's continuous depression + if (inlet && inlet->localDepress * inlet->localWidth > 0) + { + a += inlet->localDepress; // inlet depression + W = inlet->localWidth; // inlet depressed width + } + + // --- slope of depressed gutter section + if (W * a > 0.0) Sw = Sx + a / W; + else Sw = Sx; + } + + // --- conduit has rectangular or trapezoidal cross section + else + { + a = 0.0; + W = 0.0; + n = Conduit[k].roughness; + Nsides = 1; + Sx = 0.01; + Sw = Sx; + } +} + +//============================================================================= + +double getFlowSpread(double Q) +// +// Input: Q = conduit flow rate (cfs) +// Output: returns width of flow spread (ft) +// Purpose: computes width of flow spread across a Street cross section using +// HEC-22 equations derived from Izzard's form of the Manning eqn. +// +{ + int iter; + double f, f1, Sr, Ts1, Ts2, Tw, Qs, Eo; + + f = Qfactor; // = (0.56/n) * SL^0.5 * Sx^1.67 + + // --- no depressed curb + if (a == 0.0) + { + Ts1 = pow(Q / f, 0.375); //HEC-22 Eq(4-2) + } + else + { + // --- check if spread is within curb width + f1 = f * pow((a / W) / Sx, 1.67); + Tw = pow(Q / f1, 0.375); //HEC-22 Eq(4-2) + if (Tw <= W) Ts1 = Tw; + else + { + // --- spread extends beyond curb width + Sr = (Sx + a / W) / Sx; + iter = 1; + Ts1 = pow(Q / f, 0.375) - W; + if (Ts1 <= 0) Ts1 = Tw - W; + while (iter < 11) + { + Eo = getEo(Sr, Ts1, W); + Qs = (1.0 - Eo) * Q; //HEC-22 Eq(4-6) + Ts2 = pow(Qs / f, 0.375); //HEC-22 Eq(4-2) + if (fabs(Ts2 - Ts1) < 0.01) break; + Ts1 = Ts2; + iter++; + } + Ts1 = Ts2 + W; + } + } + return MIN(Ts1, Tcrown); +} + +//============================================================================= + +double getEo(double Sr, double Ts, double w) +// +// Input: Sr = ratio of gutter slope to street cross slope +// Ts = amount of flow spread outside of gutter width (ft) +// w = gutter width (ft) +// Output: returns ratio of gutter flow to total flow in street cross section +// Purpose: solves HEC-22 Eq. (4-4) for Eo with Ts/w substituted for +// (T/w) - 1 where Ts = T - w. +// +{ + double x; + x = Sr / (Ts / w); + x = pow((1.0 + x), 2.67) - 1.0; + x = 1.0 + Sr / x; + return 1.0 / x; +} + +//============================================================================= + +double getOnGradeCapturedFlow(TInlet *inlet, double q, double d) +// +// Input: inlet = an inlet object placed in a conduit link +// q = flow in link prior to any inlet capture (cfs) +// d = flow depth seen by inlet (ft) +// Output: returns flow captured by the inlet (cfs) +// Purpose: computes flow captured by an inlet placed on-grade. +// +// An inlet object placed in a conduit can have multiple inlets of +// the same type distributed along the conduit's length that all +// send their captured flow to the same sewer node. This function +// finds the total captured flow as each individual inlet is analyzed +// sequentially, where its approach flow has been reduced by the +// amount of flow captured by prior inlets. +{ + int i, + linkIndex; // index of link containing inlets + double qApproach, // single inlet's approach flow (cfs) + qc, // single inlet's captured flow (cfs) + qCaptured, // total flow captured by link's inlets (cfs) + qBypassed, // total flow bypassed by link's inlets (cfs) + qMax; // max. flow that a single inlet can capture (cfs) + + if (inlet->numInlets == 0) return 0.0; + linkIndex = inlet->linkIndex; + + // --- check that link has flow + qApproach = q; + if (qApproach < MIN_RUNOFF_FLOW) return 0.0; + + // --- store conduit geometry in shared variables + getConduitGeometry(inlet); + + // --- adjust flow for 2-sided street + qApproach /= Nsides; + qBypassed = qApproach; + qCaptured = 0.0; + + // --- set limit on max. flow captured per inlet + qMax = BIG; + if (inlet->flowLimit > 0.0) qMax = inlet->flowLimit; + + // --- evaluate each inlet + for (i = 1; i <= inlet->numInlets; i++) + { + qc = getOnGradeInletCapture(inlet->designIndex, qBypassed, d) * + inlet->clogFactor; + qc = MIN(qc, qMax); + qCaptured += qc; + qBypassed -= qc; + if (qBypassed < MIN_RUNOFF_FLOW) break; + } + return qCaptured *= Nsides; +} + +//============================================================================= + +double getOnGradeInletCapture(int i, double Q, double d) +// +// Input: i = an InletDesigns index +// Q = flow rate seen by inlet (cfs) +// d = flow depth seen by inlet (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by a single on-grade inlet. +// +{ + int c; + double Q1 = Q, Qc = 0.0, Lsweep = 0.0, Lcurb = 0.0, Lgrate = 0.0; + + // --- custom inlet -- use onGrade curve if present or onSag curve otherwise + c = InletDesigns[i].customInlet.onGradeCurve; + if (c >= 0) + { + Qc = table_lookupEx(&Curve[c], Q * UCF(FLOW)) / UCF(FLOW); + return MIN(Qc, Q); + } + c = InletDesigns[i].customInlet.onSagCurve; + if (c >= 0) + { + Qc = table_lookupEx(&Curve[c], d * UCF(LENGTH)) / UCF(FLOW); + return MIN(Qc, Q); + } + + // --- drop curb inlet (in non-Street conduit) only operates in on sag mode + if (InletDesigns[i].type == DROP_CURB_INLET) + { + Qc = getOnSagInletCapture(i, d); + return MIN(Qc, Q); + } + + // --- drop grate inlet (in non-Street conduit) + if (InletDesigns[i].type == DROP_GRATE_INLET) + { + Qc = getGrateInletCapture(i, Q); + return MIN(Qc, Q); + } + + // --- Remaining inlet types apply to Street conduits + + // --- find flow spread + T = getFlowSpread(Q); + + // --- slotted inlet (behaves as a curb opening inlet per HEC-22) + if (InletDesigns[i].type == SLOTTED_INLET) + { + Qc = getCurbInletCapture(Q, InletDesigns[i].slottedInlet.length); + return MIN(Qc, Q); + } + + Lcurb = InletDesigns[i].curbInlet.length; + Lgrate = InletDesigns[i].grateInlet.length; + + // --- curb opening inlet + if (Lcurb > 0.0) + { + Lsweep = Lcurb - Lgrate; + if (Lsweep > 0.0) + { + Qc = getCurbInletCapture(Q1, Lsweep); + Q1 -= Qc; + } + } + + // --- grate inlet + if (Lgrate > 0.0 && Q1 > 0.0) + { + if (Q1 != Q) T = getFlowSpread(Q1); + Qc += getGrateInletCapture(i, Q1); + } + return Qc; +} + +//============================================================================= + +double getGrateInletCapture(int i, double Q) +// +// Input: i = inlet type index +// Q = flow rate seen by inlet (cfs) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-grade grate inlet. +// +{ + int grateType; + double Lg, // grate length (ft) + Wg, // grate width (ft) + A, // total cross section flow area (ft2) + Y, // flow depth (ft) + Eo, // ratio of gutter to total flow + V, // flow velocity (ft/s) + Vo, // splash-over velocity (ft/s) + Qo = Q, // flow over street area (cfs) + Rf = 1.0, // ratio of intercepted to total frontal flow + Rs = 0.0; // ratio of intercepted to total side flow + +// xsect, a, W, & Sx were from getConduitGeometry(). T was from getFlowSpread(). + + Lg = InletDesigns[i].grateInlet.length; + Wg = InletDesigns[i].grateInlet.width; + + // --- flow ratio for drop inlet + if (xsect->type == TRAPEZOIDAL) + { + A = xsect_getAofS(xsect, Q / Beta); + Y = xsect_getYofA(xsect, A); + T = xsect_getWofY(xsect, Y); + Eo = Beta * pow(Y*Wg, 1.67) / pow(Wg + 2*Y, 0.67) / Q; + if (Wg > 0.99*xsect->yBot && xsect->sBot > 0.0) + { + Wg = xsect->yBot; + Sx = 1.0 / xsect->sBot; + } + } + + // --- flow ratio & area for conventional street gutter + else if (a == 0.0) + { + A = T * T * Sx / 2.0; + Eo = getGutterFlowRatio(Wg); // flow ratio based on grate width + if (T >= Tcrown) Qo = Qfactor * pow(Tcrown, 2.67); + } + + // --- flow ratio & area for composite street gutter + else + { + // --- spread confined to gutter + if (T <= W) A = T * T * a / W / 2.0; + + // --- spread beyond gutter width + else A = (T * T * Sx + a * W) / 2.0; + + // flow ratio based on gutter width corrected for grate width + Eo = getGutterFlowRatio(W); + if (Eo < 1.0) + { + if (T >= Tcrown) + Qo = Qfactor * pow(Tcrown, 2.67) / (1.0 - Eo); + Eo = Eo * getGutterAreaRatio(Wg, A); //HEC-22 Eq(4-20a) + } + } + + // --- flow and splash-over velocities + V = Qo / A; + grateType = InletDesigns[i].grateInlet.type; + if (grateType < 0 || grateType == GENERIC) + Vo = InletDesigns[i].grateInlet.splashVeloc; + else + Vo = getSplashOverVelocity(grateType, Lg); + + // --- frontal flow capture efficiency + if (V > Vo) Rf = 1.0 - 0.09 * (V - Vo); //HEC-22 Eq(4-18) + + // --- side flow capture efficiency + if (Eo < 1.0) + { + Rs = 1.0 / (1.0 + (0.15 * pow(V, 1.8) / + Sx / pow(Lg, 2.3))); //HEC-22 Eq(4-19) + } + + // --- return total flow captured + return Q * (Rf * Eo + Rs * (1.0 - Eo)); //HEC-22 Eq(4-21) +} + +//============================================================================= + +double getCurbInletCapture(double Q, double L) +// +// Input: Q = flow rate seen by inlet (cfs) +// L = length of inlet opening (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-sag inlet. +// +{ + double Se = Sx, // equivalent gutter slope + Lt, // length for full capture + Sr, // ratio of gutter slope to cross slope + Eo = 0.0, // ratio of gutter to total flow + E = 1.0; // capture efficiency + +// a, W, Sx, Sw, SL, & n were from getConduitGeometry(). T was from getFlowSpread(). + + // --- for depressed gutter section + if (a > 0.0) + { + Sr = (Sx + a / W) / Sx; + Eo = getEo(Sr, T-W, W); + Se = Sx + Sw * Eo; //HEC-22 Eq(4-24) + } + + // --- opening length for full capture + Lt = 0.6 * pow(Q, 0.42) * pow(SL, 0.3) * + pow(1.0/(n*Se), 0.6); //HEC-22 Eq(4-22a) + + // --- capture efficiency for actual opening length + if (L < Lt) + { + E = 1.0 - (L/Lt); + E = 1 - pow(E, 1.8); //HEC-22 Eq(4-23) + } + E = MIN(E, 1.0); + E = MAX(E, 0.0); + return E * Q; +} + +//============================================================================= + +double getGutterFlowRatio(double w) +// +// Input: w = gutter width (ft) +// Output: returns a flow ratio +// Purpose: computes the ratio of flow over a width of gutter to the total +// flow in a street cross section. +// +{ + if (T <= w) return 1.0; + else if (a > 0.0) + return getEo(Sw / Sx, T - w, w); + else + return 1.0 - pow((1.0 - w / T), 2.67); //HEC-22 Eq(4-16) +} + +//============================================================================= + +double getGutterAreaRatio(double Wg, double A) +// +// Input: Wg = width of grate inlet (ft) +// A = gutter area (ft2) +// Output: returns an area ratio +// Purpose: computes the ratio of the flow area above a grate to the flow +// area above the gutter in a street cross section. +// +{ + double Aw, // flow area beyond gutter width (ft2) + Ag; // flow area beyond grate width (ft2) + + if (Wg >= W) return 1.0; + Aw = SQR((T - W)) * Sx; + Ag = Aw + SQR((W - Wg)) * Sw; + return (A - Ag) / (A - Aw); +} + +//============================================================================= + +double getSplashOverVelocity(int grateType, double L) +// +// Input: grateType = grate inlet type code +// L = length of grate inlet (ft) +// Output: returns a splash over velocity +// Purpose: computes the splash over velocity for a standard type of grate +// inlet as a function of its length. +// +{ + return SplashCoeffs[grateType][0] + + SplashCoeffs[grateType][1] * L - + SplashCoeffs[grateType][2] * L * L + + SplashCoeffs[grateType][3] * L * L * L; +} + +//============================================================================= + +double getOnSagCapturedFlow(TInlet *inlet, double q, double d) +// +// Input: inlet = an inlet object placed in a conduit link +// q = flow in link prior to any inlet capture (cfs) +// d = flow depth seen by inlet (ft) +// Output: returns flow captured by the inlet (cfs) +// Purpose: computes flow captured by an inlet placed on-sag. +// +{ + int c, numInlets, linkIndex, designIndex; + double qCaptured = 0.0, qMax = HUGE; + + numInlets = inlet->numInlets; + if (numInlets == 0) return 0.0; + linkIndex = inlet->linkIndex; + designIndex = inlet->designIndex; + + // --- store conduit geometry in shared variables + getConduitGeometry(inlet); + + // --- set flow limit per inlet + if (inlet->flowLimit > 0.0) qMax = inlet->flowLimit; + + // --- find nominal flow captured by inlet + // --- inlet has a custom rating curve + c = InletDesigns[designIndex].customInlet.onSagCurve; + if (c >= 0) + qCaptured = table_lookupEx(&Curve[c], d * UCF(LENGTH)) / UCF(FLOW); + else + { + // --- inlet has a custom diversion curve + c = InletDesigns[designIndex].customInlet.onGradeCurve; + if (c >= 0) + { + qCaptured = table_lookupEx(&Curve[c], q * UCF(FLOW)) / UCF(FLOW); + qCaptured = MIN(qCaptured, q); + } + + // --- use HEC-22 method for all other inlet types + else + qCaptured = getOnSagInletCapture(designIndex, fabs(d)); + } + + // --- find actual flow captured by the inlet + qCaptured *= inlet->clogFactor; + qCaptured = MIN(qCaptured, qMax); + qCaptured *= Nsides * inlet->numInlets; + return qCaptured; +} + +//============================================================================= + +double getOnSagInletCapture(int i, double d) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-sag inlet. +// +{ + double Lsweep = 0.0, Lcurb = 0.0, Lgrate = 0.0; + double Qsw = 0.0, //Sweeper curb opening weir flow + Qso = 0.0, //Sweeper curb opening orifice flow + Qgw = 0.0, //Grate weir flow + Qgo = 0.0, //Grate orifice flow + Qcw = 0.0, //Curb opening weir flow + Qco = 0.0; //Curb opening orifice flow + + if (InletDesigns[i].slottedInlet.length > 0.0) + return getOnSagSlottedFlow(i, d); + + Lgrate = InletDesigns[i].grateInlet.length; + if (Lgrate > 0.0) findOnSagGrateFlows(i, d, &Qgw, &Qgo); + + Lcurb = InletDesigns[i].curbInlet.length; + if (Lcurb > 0.0) + { + Lsweep = Lcurb - Lgrate; + if (Lsweep > 0.0) findOnSagCurbFlows(i, d, Lsweep, &Qsw, &Qso); + if (Qgo > 0.0) findOnSagCurbFlows(i, d, Lgrate, &Qcw, &Qco); + } + return Qgw + Qgo + Qsw + Qso + Qco; +} + +//============================================================================= + +void findOnSagGrateFlows(int i, double d, double *Qw, double *Qo) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// Output: Qw = flow captured in weir mode (cfs) +// Qo = flow captured in orifice mode (cfs) +// Purpose: finds the flow captured by an on-sag grate inlet. +// +{ + int grateType = InletDesigns[i].grateInlet.type; + double Lg = InletDesigns[i].grateInlet.length; + double Wg = InletDesigns[i].grateInlet.width; + double P, // grate perimeter (ft) + Ao, // grate opening area (ft2) + di; // average flow depth across grate (ft) + + // --- for drop grate inlets + if (InletDesigns[i].type == DROP_GRATE_INLET) + { + di = d; + P = 2.0 * (Lg + Wg); + } + + // --- for gutter grate inlets: + else + { + // --- check for spread within grate width + if (d <= Wg * Sw) Wg = d / Sw; + + // --- avergage depth over grate + di = d - (Wg / 2.0) * Sw; + + // --- effective grate perimeter & area + P = Lg + 2.0 * Wg; + } + + if (grateType == GENERIC) + Ao = Lg * Wg * InletDesigns[i].grateInlet.fracOpenArea; + else + Ao = Lg * Wg * GrateOpeningRatios[grateType]; + + // --- weir flow applies (based on depth where result of + // weir eqn. equals result of orifice eqn.) + + if (d <= 1.79 * Ao / P) + { + *Qw = 3.0 * P * pow(di, 1.5); //HEC-22 Eq(4-26) + } + + // --- orifice flow applies + else + { + *Qo = 0.67 * Ao * sqrt(2.0 * 32.16 * di); //HEC-22 Eq(4-27) + } +} + +//============================================================================= + +void findOnSagCurbFlows(int i, double d, double L, double *Qw, double *Qo) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// L = length of curb opening (ft) +// Output: Qw = flow captured in weir mode (cfs) +// Qo = flow captured in orifice mode (cfs) +// Purpose: finds the flow captured by an on-sag curb opening inlet. +// +{ + int throatAngle = InletDesigns[i].curbInlet.throatAngle; + double h = InletDesigns[i].curbInlet.height; + double Qweir, Qorif, P; + double dweir, dorif, r; + + // --- check for orifice flow + if (L <= 0.0) return; + if (InletDesigns[i].type == DROP_CURB_INLET) L = L * 4.0; + dorif = 1.4 * h; + if (d > dorif) + { + *Qo = getCurbOrificeFlow(d, h, L, throatAngle); + return; + } + + // --- for uniform cross slope or very long opening + if (a == 0.0 || L > 12.0) + { + // --- check for weir flow + dweir = h; + if (d < dweir) + { + *Qw = 3.0 * L * pow(d, 1.5); //HEC-22 Eq(4-30) + return; + } + else Qweir = 3.0 * L * pow(dweir, 1.5); + } + + // --- for depressed gutter + else + { + // --- check for weir flow + P = L + 1.8 * W; + dweir = h + a; + if (d < dweir) + { + *Qw = 2.3 * P * pow(d, 1.5); //HEC-22 Eq(4-28) + return; + } + else Qweir = 2.3 * P * pow(dweir, 1.5); + } + + // --- interpolate between Qwier at depth dweir and Qorif at depth dorif + Qorif = getCurbOrificeFlow(dorif, h, L, throatAngle); + r = (d - dweir) / (dorif - dweir); + *Qw = r * Qweir; + *Qo = (1 - r) * Qorif; +} + +//============================================================================= + +double getCurbOrificeFlow(double di, double h, double L, int throatAngle) +// +// Input: di = water level at lip of inlet opening (ft) +// h = height of curb opening (ft) +// L = length of curb opening (ft) +// throatAngle = type of throat angle in curb opening +// Output: return flow captured by inlet (cfs) +// Purpose: finds the flow captured by an on-sag curb opening inlet under +// orifice flow conditions. +// +{ + double d = di; + if (throatAngle == HORIZONTAL_THROAT) + d = di - h / 2.0; + else if (throatAngle == INCLINED_THROAT) + d = di + (h / 2.0) * 0.7071; + return 0.67 * h * L * sqrt(2.0 * 32.16 * d); //HEC-22 Eq(4-31a) +} + +//============================================================================= + +double getOnSagSlottedFlow(int i, double d) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-sag slotted inlet. +// +{ + double L = InletDesigns[i].slottedInlet.length; + double w = InletDesigns[i].slottedInlet.width; + double qw, qo; + + if (d <= 0.2) return 2.48 * L * pow(d, 1.5); //HEC-22 Eq(4-32) + if (d >= 0.4) return 0.8 * L * w * sqrt(64.32 * d); //HEC-22 Eq(4-33) + qw = 3.9305351 * L; + qo = 4.057822076 * L * w; + return qw + (d / 0.2 - 1.0) * (qo - qw); +} diff --git a/src/solver/inlet.h b/src/solver/inlet.h new file mode 100644 index 000000000..f5c1cd466 --- /dev/null +++ b/src/solver/inlet.h @@ -0,0 +1,107 @@ +//----------------------------------------------------------------------------- +// inlet.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman +// +// Definition of Street/Channel Inlet Objects +// +//----------------------------------------------------------------------------- +#ifndef INLET_H +#define INLET_H + +// Grate inlet +typedef struct +{ + int type; // type of grate used + double length; // length (parallel to flow) (ft) + double width; // width (perpendicular to flow) (ft) + double fracOpenArea; // fraction of grate area that is open + double splashVeloc; // splash-over velocity (ft/s) +} TGrateInlet; + +// Slotted drain inlet +typedef struct +{ + double length; // length (parallel to flow) (ft) + double width; // width (perpendicular to flow) (ft) +} TSlottedInlet; + +// Curb opening inlet +typedef struct +{ + double length; // length of curb opening (ft) + double height; // height of curb opening (ft) + int throatAngle; // type of throat angle +} TCurbInlet; + +// Custom inlet +typedef struct +{ + int onGradeCurve; // flow diversion curve index + int onSagCurve; // flow rating curve index +} TCustomInlet; + +// Inlet design object +typedef struct +{ + char * ID; // name assigned to inlet design + int type; // type of inlet used (grate, curb, etc) + TGrateInlet grateInlet; // length = 0 if not used + TSlottedInlet slottedInlet; // length = 0 if not used + TCurbInlet curbInlet; // length = 0 if not used + TCustomInlet customInlet; // curve index = -1 if not used +} TInletDesign; + +// Inlet performance statistics +typedef struct +{ + int flowPeriods; // # periods with approach flow + int capturePeriods; // # periods with captured flow + int backflowPeriods; // # periods with backflow + double peakFlow; // peak flow seen by inlet (cfs) + double peakFlowCapture; // capture efficiency at peak flow + double avgFlowCapture; // average capture efficiency + double bypassFreq; // frequency of bypass flow +} TInletStats; + +// Inlet usage object +typedef struct TInlet TInlet; +struct TInlet +{ + int linkIndex; // index of conduit link with the inlet + int designIndex; // index of inlet's design + int nodeIndex; // index of node receiving captured flow + int numInlets; // # inlets on each side of street or in channel + int placement; // whether inlet is on-grade or on-sag + double clogFactor; // fractional degree of inlet clogging + double flowLimit; // inlet flow restriction (cfs) + double localDepress; // local gutter depression (ft) + double localWidth; // local depression width (ft) + + double flowFactor; // flow = flowFactor * (flow spread)^2.67 + double outflow; // captured flow rate (cfs) + double backflow; // backflow into inlet (cfs) + TInletStats stats; // inlet performance statistics + TInlet * nextInlet; // next inlet in list +}; + +// Shared inlet variables +TInletDesign * InletDesigns; // array of available inlet designs +int InletDesignCount; // number of inlet designs +int UsesInlets; // TRUE if project uses inlets + +// Shared inlet functions +int inlet_create(int nInlets); +void inlet_delete(); +int inlet_readDesignParams(char* tok[], int ntoks); +int inlet_readUsageParams(char* tok[], int ntoks); +void inlet_validate(); +void inlet_findInletFlows(double tStep); +void inlet_convertOverflows(); +void inlet_writeStatsReport(); +double inlet_capturedFlow(int link); + +#endif diff --git a/src/solver/input.c b/src/solver/input.c index 390b6f088..2d8ed48e8 100644 --- a/src/solver/input.c +++ b/src/solver/input.c @@ -2,23 +2,23 @@ // input.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 09/15/14 (Build 5.1.007) -// 08/01/16 (Build 5.1.011) -// 04/01/20 (Build 5.1.015) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Input data processing functions. // +// Update History +// ============== // Build 5.1.007: // - Support added for climate adjustment input data. -// // Build 5.1.011: // - Support added for reading hydraulic event dates. -// // Build 5.1.015: // - Support added for multiple infiltration methods within a project. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for named variables & math expressions in control rules. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -27,6 +27,7 @@ #include #include #include "headers.h" +#include "street.h" #include "lid.h" //----------------------------------------------------------------------------- @@ -87,6 +88,7 @@ int input_countObjects() for (i = 0; i < MAX_OBJ_TYPES; i++) Nobjects[i] = 0; for (i = 0; i < MAX_NODE_TYPES; i++) Nnodes[i] = 0; for (i = 0; i < MAX_LINK_TYPES; i++) Nlinks[i] = 0; + controls_init(); // --- make pass through data file counting number of each object while ( fgets(line, MAXLINE, Finp.file) != NULL ) @@ -407,6 +409,7 @@ int addObject(int objType, char* id) case s_CONTROL: if ( match(id, w_RULE) ) Nobjects[CONTROL]++; + else controls_addToCount(id); break; case s_TRANSECT: @@ -436,6 +439,22 @@ int addObject(int objType, char* id) break; case s_EVENT: NumEvents++; break; + + case s_STREET: + if ( !project_addObject(STREET, id, Nobjects[STREET]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[STREET]++; + break; + + case s_INLET: + // --- an INLET object can span several lines + if (project_findObject(INLET, id) < 0) + { + if ( !project_addObject(INLET, id, Nobjects[INLET]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[INLET]++; + } + break; } return errcode; } @@ -481,7 +500,7 @@ int parseLine(int sect, char *line) return subcatch_readSubareaParams(Tok, Ntokens); case s_INFIL: - return infil_readParams(InfilModel, Tok, Ntokens); //(5.1.015) + return infil_readParams(InfilModel, Tok, Ntokens); case s_AQUIFER: j = Mobjects[AQUIFER]; @@ -600,6 +619,15 @@ int parseLine(int sect, char *line) case s_EVENT: return readEvent(Tok, Ntokens); + case s_STREET: + return street_readParams(Tok, Ntokens); + + case s_INLET: + return inlet_readDesignParams(Tok, Ntokens); + + case s_INLET_USAGE: + return inlet_readUsageParams(Tok, Ntokens); + default: return 0; } } @@ -620,6 +648,11 @@ int readControl(char* tok[], int ntoks) // --- check for minimum number of tokens if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + if (match(tok[0], w_VARIABLE)) + return controls_addVariable(tok, ntoks); + if (match(tok[0], w_EXPRESSION)) + return controls_addExpression(tok, ntoks); + // --- get index of control rule keyword keyword = findmatch(tok[0], RuleKeyWords); if ( keyword < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); diff --git a/src/solver/inputrpt.c b/src/solver/inputrpt.c index 31798c90b..74cf82a30 100644 --- a/src/solver/inputrpt.c +++ b/src/solver/inputrpt.c @@ -2,18 +2,23 @@ // inputrpt.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Report writing functions for input data summary. // +// Update History +// ============== +// Build 5.2.0: +// - Support added for Streets and Inlets. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE #include #include #include "headers.h" +#include "street.h" #include "lid.h" #define WRITE(x) (report_writeLine((x))) @@ -237,6 +242,9 @@ void inputrpt_writeInput() else if ( Link[i].xsect.type == IRREGULAR ) fprintf(Frpt.file, "%-16s ", Transect[Link[i].xsect.transect].ID); + else if ( Link[i].xsect.type == STREET_XSECT ) + fprintf(Frpt.file, "%-16s ", + Street[Link[i].xsect.transect].ID); else fprintf(Frpt.file, "%-16s ", XsectTypeWords[Link[i].xsect.type]); fprintf(Frpt.file, "%8.2f %8.2f %8.2f %8.2f %3d %8.2f", @@ -314,4 +322,36 @@ void inputrpt_writeInput() } } WRITE(""); + + if (Nobjects[STREET] > 0) + { + WRITE(""); + WRITE(""); + WRITE("**************"); + WRITE("Street Summary"); + WRITE("**************"); + for (i = 0; i < Nobjects[STREET]; i++) + { + fprintf(Frpt.file, "\n\n Street %s", Street[i].ID); + fprintf(Frpt.file, "\n Area: "); + for (m = 1; m < Street[i].transect.nTbl; m++) + { + if (m % 5 == 1) fprintf(Frpt.file, "\n "); + fprintf(Frpt.file, "%10.4f ", Street[i].transect.areaTbl[m]); + } + fprintf(Frpt.file, "\n Hrad: "); + for (m = 1; m < Street[i].transect.nTbl; m++) + { + if (m % 5 == 1) fprintf(Frpt.file, "\n "); + fprintf(Frpt.file, "%10.4f ", Street[i].transect.hradTbl[m]); + } + fprintf(Frpt.file, "\n Width: "); + for (m = 1; m < Street[i].transect.nTbl; m++) + { + if (m % 5 == 1) fprintf(Frpt.file, "\n "); + fprintf(Frpt.file, "%10.4f ", Street[i].transect.widthTbl[m]); + } + } + } + WRITE(""); } diff --git a/src/solver/keywords.c b/src/solver/keywords.c index 7ae5b2d5a..895c885fc 100644 --- a/src/solver/keywords.c +++ b/src/solver/keywords.c @@ -2,14 +2,8 @@ // keywords.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.000) -// 04/14/14 (Build 5.1.004) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 08/05/15 (Build 5.1.010) -// 08/01/16 (Build 5.1.011) -// 05/10/18 (Build 5.1.013) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Exportable keyword dictionary @@ -19,24 +13,26 @@ // must be terminated by NULL. The actual text of each keyword // is defined in text.h. // +// Update History +// ============== // Build 5.1.007: // - Keywords for Ignore RDII option and groundwater flow equation // and climate adjustment input sections added. -// // Build 5.1.008: // - Keyword arrays placed in alphabetical order for better readability. // - Keywords added for Minimum Routing Step and Number of Threads options. -// // Build 5.1.010: // - New Modified Green Ampt keyword added to InfilModelWords. // - New Roadway weir keyword added to WeirTypeWords. -// // Build 5.1.011: // - New section keyword for [EVENTS] added. -// // Build 5.1.013: // - New option keywords w_SURCHARGE_METHOD, w_RULE_STEP, w_AVERAGES // and w_WEIR added. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for variable speed pumps. +// - Support added for conical & pyramidal storage shapes. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -45,8 +41,9 @@ char* BuildupTypeWords[] = { w_NONE, w_POW, w_EXP, w_SAT, w_EXT, NULL}; char* CurveTypeWords[] = { w_STORAGE, w_DIVERSION, w_TIDAL, w_RATING, - w_CONTROLS, w_SHAPE, w_WEIR, //(5.1.013) - w_PUMP1, w_PUMP2, w_PUMP3, w_PUMP4, NULL}; + w_CONTROLS, w_SHAPE, w_WEIR, + w_PUMP1, w_PUMP2, w_PUMP3, w_PUMP4, + w_PUMP5, NULL}; char* DividerTypeWords[] = { w_CUTOFF, w_TABULAR, w_WEIR, w_OVERFLOW, NULL}; char* EvapTypeWords[] = { w_CONSTANT, w_MONTHLY, w_TIMESERIES, w_TEMPERATURE, w_FILE, w_RECOVERY, @@ -79,7 +76,7 @@ char* OptionWords[] = { w_FLOW_UNITS, w_INFIL_MODEL, w_REPORT_START_TIME, w_SWEEP_START, w_SWEEP_END, w_START_DRY_DAYS, w_WET_STEP, w_DRY_STEP, - w_ROUTE_STEP, w_RULE_STEP, //(5.1.013) + w_ROUTE_STEP, w_RULE_STEP, w_REPORT_STEP, w_ALLOW_PONDING, w_INERT_DAMPING, w_SLOPE_WEIGHTING, w_VARIABLE_STEP, @@ -93,7 +90,7 @@ char* OptionWords[] = { w_FLOW_UNITS, w_INFIL_MODEL, w_MAX_TRIALS, w_HEAD_TOL, w_SYS_FLOW_TOL, w_LAT_FLOW_TOL, w_IGNORE_RDII, w_MIN_ROUTE_STEP, - w_NUM_THREADS, w_SURCHARGE_METHOD, //(5.1.013) + w_NUM_THREADS, w_SURCHARGE_METHOD, NULL }; char* OrificeTypeWords[] = { w_SIDE, w_BOTTOM, NULL}; char* OutfallTypeWords[] = { w_FREE, w_NORMAL, w_FIXED, w_TIDAL, @@ -101,14 +98,15 @@ char* OutfallTypeWords[] = { w_FREE, w_NORMAL, w_FIXED, w_TIDAL, char* PatternTypeWords[] = { w_MONTHLY, w_DAILY, w_HOURLY, w_WEEKEND, NULL}; char* PondingUnitsWords[] = { w_PONDED_FEET, w_PONDED_METERS }; char* ProcessVarWords[] = { w_HRT, w_DT, w_FLOW, w_DEPTH, w_AREA, NULL}; -char* PumpTypeWords[] = { w_TYPE1, w_TYPE2, w_TYPE3, w_TYPE4, w_IDEAL }; +char* PumpTypeWords[] = { w_TYPE1, w_TYPE2, w_TYPE3, w_TYPE4, w_TYPE5, w_IDEAL }; char* QualUnitsWords[] = { w_MGperL, w_UGperL, w_COUNTperL, NULL}; char* RainTypeWords[] = { w_INTENSITY, w_VOLUME, w_CUMULATIVE, NULL}; char* RainUnitsWords[] = { w_INCHES, w_MMETER, NULL}; -char* RelationWords[] = { w_TABULAR, w_FUNCTIONAL, NULL}; +char* RelationWords[] = { w_TABULAR, w_FUNCTIONAL, + w_CONICAL, w_PYRAMIDAL, NULL}; char* ReportWords[] = { w_INPUT, w_CONTINUITY, w_FLOWSTATS, w_CONTROLS, w_SUBCATCH, w_NODE, w_LINK, - w_NODESTATS, w_AVERAGES, NULL}; //(5.1.013) + w_NODESTATS, w_AVERAGES, NULL}; char* RouteModelWords[] = { w_NONE, w_STEADY, w_KINWAVE, w_XKINWAVE, w_DYNWAVE, NULL}; char* RuleKeyWords[] = { w_RULE, w_IF, w_AND, w_OR, w_THEN, w_ELSE, @@ -140,9 +138,10 @@ char* SectWords[] = { ws_TITLE, ws_OPTION, ws_MAP, ws_LID_CONTROL, ws_LID_USAGE, ws_GWF, ws_ADJUST, ws_EVENT, - NULL}; + ws_STREET, ws_INLET_USAGE, + ws_INLET, NULL}; char* SnowmeltWords[] = { w_PLOWABLE, w_IMPERV, w_PERV, w_REMOVAL, NULL}; -char* SurchargeWords[] = { w_EXTRAN, w_SLOT, NULL}; //(5.1.013) +char* SurchargeWords[] = { w_EXTRAN, w_SLOT, NULL}; char* TempKeyWords[] = { w_TIMESERIES, w_FILE, w_WINDSPEED, w_SNOWMELT, w_ADC, NULL}; char* TransectKeyWords[] = { w_NC, w_X1, w_GR, NULL}; @@ -165,4 +164,5 @@ char* XsectTypeWords[] = { w_DUMMY, w_CIRCULAR, w_CATENARY, w_SEMIELLIPTICAL, w_BASKETHANDLE, w_SEMICIRCULAR, w_IRREGULAR, w_CUSTOM, - w_FORCE_MAIN, NULL}; + w_FORCE_MAIN, w_STREET, + NULL}; diff --git a/src/solver/keywords.h b/src/solver/keywords.h index af89e0d58..55de83701 100644 --- a/src/solver/keywords.h +++ b/src/solver/keywords.h @@ -2,17 +2,16 @@ // keywords.h // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/19/14 (Build 5.1.000) -// 03/19/15 (Build 5.1.008) -// 05/10/18 (Build 5.1.013) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Exportable keyword dictionary // +// Update History +// ============== // Build 5.1.008: // - Keyword arrays listed in alphabetical order. -// // Build 5.1.013: // - New keyword array defined for surcharge method. //----------------------------------------------------------------------------- @@ -59,7 +58,7 @@ extern char* RouteModelWords[]; extern char* RuleKeyWords[]; extern char* SectWords[]; extern char* SnowmeltWords[]; -extern char* SurchargeWords[]; //(5.1.013) +extern char* SurchargeWords[]; extern char* TempKeyWords[]; extern char* TransectKeyWords[]; extern char* TreatTypeWords[]; diff --git a/src/solver/kinwave.c b/src/solver/kinwave.c index c0fdf6fce..498e0eac3 100644 --- a/src/solver/kinwave.c +++ b/src/solver/kinwave.c @@ -2,21 +2,19 @@ // kinwave.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 03/19/15 (Build 5.1.008) -// 03/01/20 (Build 5.1.014) -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // M. Tryby (EPA) // // Kinematic wave flow routing functions. // +// Update History +// ============== // Build 5.1.008: // - Conduit inflow passed to function that computes conduit losses. -// // Build 5.1.014: // - Arguments to function link_getLossRate changed. -// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -102,7 +100,7 @@ int kinwave_execute(int j, double* qinflow, double* qoutflow, double tStep) qin = (*qinflow) / Conduit[k].barrels / Qfull; // --- compute evaporation and infiltration loss rate - q3 = link_getLossRate(j, qin*Qfull) / Qfull; //(5.1.014) + q3 = link_getLossRate(j, qin*Qfull) / Qfull; // --- normalize previous areas a1 = Conduit[k].a1 / Afull; diff --git a/src/solver/landuse.c b/src/solver/landuse.c index 380605f66..458ea253d 100644 --- a/src/solver/landuse.c +++ b/src/solver/landuse.c @@ -2,19 +2,19 @@ // landuse.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 03/19/15 (Build 5.1.008) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Pollutant buildup and washoff functions. // +// Update History +// ============== // Build 5.1.008: // - landuse_getWashoffMass() re-named to landuse_getWashoffQual() and // modified to return concentration instead of mass load. // - landuse_getRunoffLoad() re-named to landuse_getWashoffLoad() and // modified to work with landuse_getWashoffQual(). -// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE diff --git a/src/solver/lid.c b/src/solver/lid.c index 57dd9c60d..5c5ba181b 100644 --- a/src/solver/lid.c +++ b/src/solver/lid.c @@ -2,19 +2,9 @@ // lid.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 05/19/14 (Build 5.1.006) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 04/30/15 (Build 5.1.009) -// 08/05/15 (Build 5.1.010) -// 08/01/16 (Build 5.1.011) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// 03/01/20 (Build 5.1.014) -// 04/01/20 (Build 5.1.015) -// Author: L. Rossman (US EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // // This module handles all data processing involving LID (Low Impact // Development) practices used to treat runoff for individual subcatchments @@ -43,42 +33,37 @@ // levels for a specific LID unit to be written to a text file named by the // user for viewing outside of the SWMM program. // +// Update History +// ============== // Build 5.1.008: // - More input error reporting added. // - Rooftop Disconnection added to the types of LIDs. // - LID drain flows are now tracked separately. // - LID drain flows can now be routed to separate outlets. // - Check added to insure LID flows not returned to nonexistent pervious area. -// // Build 5.1.009: // - Fixed bug where LID's could return outflow to non-LID area when LIDs // make up entire subcatchment. -// // Build 5.1.010: // - Support for new Modified Green Ampt infiltration model added. // - Imported variable HasWetLids now properly initialized. // - Initial state of reporting (lidUnit->rptFile->wasDry) changed to // prevent duplicate printing of first line of detailed report file. -// // Build 5.1.011: // - The top of the storage layer is no longer used as a limit for an // underdrain offset thus allowing upturned drains to be modeled. // - Column headings for the detailed LID report file were modified. -// // Build 5.1.012: // - Redefined initialization of wasDry for LID reporting. -// // Build 5.1.013: // - Support added for LID units treating pervious area runoff. // - Support added for open/closed head levels and multiplier v. head // control curve for underdrain flow. // - Support added for unclogging permeable pavement at fixed intervals. // - Support added for pollutant removal in underdrain flow. -// // Build 5.1.014: // - Fixed bug in creating LidProcs when there are no subcatchments. // - Fixed bug in adding underdrain pollutant loads to mass balances. -// // Build 5.1.015: // - Support added for mutiple infiltration methods within a project. //----------------------------------------------------------------------------- @@ -94,7 +79,7 @@ #define ERR_SWALE_SURF " - check swale surface parameters" #define ERR_GREEN_AMPT " - check subcatchment Green-Ampt parameters" #define ERR_DRAIN_OFFSET " - drain offset exceeds storage height" -#define ERR_DRAIN_HEADS " - invalid drain open/closed heads" //(5.1.013) +#define ERR_DRAIN_HEADS " - invalid drain open/closed heads" #define ERR_SWALE_WIDTH " - invalid swale width" //----------------------------------------------------------------------------- @@ -107,14 +92,14 @@ enum LidLayerTypes { PAVE, // pavement layer DRAINMAT, // drainage mat layer DRAIN, // underdrain system - REMOVALS}; // pollutant removals //(5.1.013) + REMOVALS}; // pollutant removals //// Note: DRAINMAT must be placed before DRAIN so the two keywords can /// be distinguished from one another when parsing a line of input. char* LidLayerWords[] = {"SURFACE", "SOIL", "STORAGE", "PAVEMENT", "DRAINMAT", "DRAIN", - "REMOVALS", NULL}; //(5.1.013) + "REMOVALS", NULL}; char* LidTypeWords[] = {"BC", //bio-retention cell @@ -219,7 +204,7 @@ static int readSoilData(int j, char* tok[], int ntoks); static int readStorageData(int j, char* tok[], int ntoks); static int readDrainData(int j, char* tok[], int ntoks); static int readDrainMatData(int j, char* toks[], int ntoks); -static int readRemovalsData(int j, char* toks[], int ntoks); //(5.1.013) +static int readRemovalsData(int j, char* toks[], int ntoks); static int addLidUnit(int j, int k, int n, double x[], char* fname, int drainSubcatch, int drainNode); @@ -231,7 +216,7 @@ static void validateLidGroup(int j); static int isLidPervious(int k); static double getImpervAreaRunoff(int j); -static double getPervAreaRunoff(int j); //(5.1.013) +static double getPervAreaRunoff(int j); static double getSurfaceDepth(int subcatch); static void findNativeInfil(int j, double tStep); @@ -295,14 +280,14 @@ void lid_create(int lidCount, int subcatchCount) LidProcs[j].drain.offset = 0.0; LidProcs[j].drainMat.thickness = 0.0; LidProcs[j].drainMat.roughness = 0.0; - LidProcs[j].drainRmvl = NULL; //(5.1.013) - LidProcs[j].drainRmvl = (double *) // - calloc(Nobjects[POLLUT], sizeof(double)); // - if (LidProcs[j].drainRmvl == NULL) // - { // - ErrorCode = ERR_MEMORY; // - return; // - } // + LidProcs[j].drainRmvl = NULL; + LidProcs[j].drainRmvl = (double *) + calloc(Nobjects[POLLUT], sizeof(double)); + if (LidProcs[j].drainRmvl == NULL) + { + ErrorCode = ERR_MEMORY; + return; + } } } @@ -318,7 +303,7 @@ void lid_delete() int j; for (j = 0; j < GroupCount; j++) freeLidGroup(j); FREE(LidGroups); - for (j = 0; j < LidCount; j++) FREE(LidProcs[j].drainRmvl); //(5.1.013) + for (j = 0; j < LidCount; j++) FREE(LidProcs[j].drainRmvl); FREE(LidProcs); GroupCount = 0; LidCount = 0; @@ -376,7 +361,7 @@ int lid_readProcParams(char* toks[], int ntoks) // LID_ID STORAGE // LID_ID DRAIN // LID_ID DRAINMAT -// LID_ID REMOVALS //(5.1.013) +// LID_ID REMOVALS // { int j, m; @@ -412,7 +397,7 @@ int lid_readProcParams(char* toks[], int ntoks) case PAVE: return readPavementData(j, toks, ntoks); case DRAIN: return readDrainData(j, toks, ntoks); case DRAINMAT: return readDrainMatData(j, toks, ntoks); - case REMOVALS: return readRemovalsData(j, toks, ntoks); //(5.1.013) + case REMOVALS: return readRemovalsData(j, toks, ntoks); } return error_setInpError(ERR_KEYWORD, toks[1]); } @@ -428,7 +413,7 @@ int lid_readGroupParams(char* toks[], int ntoks) // // Format of input data line is: // Subcatch_ID LID_ID Number Area Width InitSat FromImp ToPerv -// (RptFile DrainTo FromPerv) //(5.1.013) +// (RptFile DrainTo FromPerv) // where: // Subcatch_ID = name of subcatchment // LID_ID = name of LID process @@ -440,11 +425,11 @@ int lid_readGroupParams(char* toks[], int ntoks) // ToPerv (x[4]) = 1 if outflow goes to pervious sub-area; 0 if not // RptFile = name of detailed results file (optional) // DrainTo = name of subcatch/node for drain flow (optional) -// FromPerv (x[5]) = % of pervious runoff sent to LID //(5.1.013) +// FromPerv (x[5]) = % of pervious runoff sent to LID // { int i, j, k, n; - double x[6]; //(5.1.013) + double x[6]; char* fname = NULL; int drainSubcatch = -1, drainNode = -1; @@ -489,13 +474,13 @@ int lid_readGroupParams(char* toks[], int ntoks) } } - //... read percent of pervious area treated by LID unit //(5.1.013) - x[5] = 0.0; // - if (ntoks >= 11) // - { // - if (!getDouble(toks[10], &x[5]) || x[5] < 0.0 || x[5] > 100.0) // - return error_setInpError(ERR_NUMBER, toks[10]); // - } // + //... read percent of pervious area treated by LID unit + x[5] = 0.0; + if (ntoks >= 11) + { + if (!getDouble(toks[10], &x[5]) || x[5] < 0.0 || x[5] > 100.0) + return error_setInpError(ERR_NUMBER, toks[10]); + } //... create a new LID unit and add it to the subcatchment's LID group return addLidUnit(j, k, n, x, fname, drainSubcatch, drainNode); @@ -556,7 +541,7 @@ int addLidUnit(int j, int k, int n, double x[], char* fname, lidUnit->initSat = x[2] / 100.0; lidUnit->fromImperv = x[3] / 100.0; lidUnit->toPerv = (x[4] > 0.0); - lidUnit->fromPerv = x[5] / 100.0; //(5.1.013) + lidUnit->fromPerv = x[5] / 100.0; lidUnit->drainSubcatch = drainSubcatch; lidUnit->drainNode = drainNode; @@ -631,11 +616,11 @@ int readPavementData(int j, char* toks[], int ntoks) // // Format of data is: // LID_ID PAVEMENT Thickness VoidRatio FracImperv Permeability ClogFactor -// (RegenDays RegenDegree) //(5.1.013) +// (RegenDays RegenDegree) // { int i; - double x[7]; //(5.1.013) + double x[7]; if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); for (i = 2; i < 7; i++) @@ -644,19 +629,19 @@ int readPavementData(int j, char* toks[], int ntoks) return error_setInpError(ERR_NUMBER, toks[i]); } - // ... read optional clogging regeneration properties //(5.1.013) - x[5] = 0.0; // - if (ntoks > 7) // - { // - if (!getDouble(toks[7], &x[5]) || x[5] < 0.0) // - return error_setInpError(ERR_NUMBER, toks[7]); // - } // - x[6] = 0.0; // - if (ntoks > 8) // - { // - if (!getDouble(toks[8], &x[6]) || x[6] < 0.0 || x[6] > 1.0) // - return error_setInpError(ERR_NUMBER, toks[8]); // - } // + // ... read optional clogging regeneration properties + x[5] = 0.0; + if (ntoks > 7) + { + if (!getDouble(toks[7], &x[5]) || x[5] < 0.0) + return error_setInpError(ERR_NUMBER, toks[7]); + } + x[6] = 0.0; + if (ntoks > 8) + { + if (!getDouble(toks[8], &x[6]) || x[6] < 0.0 || x[6] > 1.0) + return error_setInpError(ERR_NUMBER, toks[8]); + } //... convert void ratio to void fraction x[1] = x[1]/(x[1] + 1.0); @@ -666,8 +651,8 @@ int readPavementData(int j, char* toks[], int ntoks) LidProcs[j].pavement.impervFrac = x[2]; LidProcs[j].pavement.kSat = x[3] / UCF(RAINFALL); LidProcs[j].pavement.clogFactor = x[4]; - LidProcs[j].pavement.regenDays = x[5]; //(5.1.013) - LidProcs[j].pavement.regenDegree = x[6]; // + LidProcs[j].pavement.regenDays = x[5]; + LidProcs[j].pavement.regenDegree = x[6]; return 0; } @@ -754,36 +739,36 @@ int readDrainData(int j, char* toks[], int ntoks) // Output: returns error code // // Format of data is: -// LID_ID DRAIN coeff expon offset delay hOpen hClose curve //(5.1.013) +// LID_ID DRAIN coeff expon offset delay hOpen hClose curve // { int i; - double x[6]; //(5.1.013) + double x[6]; //... read numerical parameters if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 0; i < 6; i++) x[i] = 0.0; //(5.1.013) - for (i = 2; i < 8; i++) // + for (i = 0; i < 6; i++) x[i] = 0.0; + for (i = 2; i < 8; i++) { - if ( ntoks > i && ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) //(5.1.013) + if ( ntoks > i && ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) return error_setInpError(ERR_NUMBER, toks[i]); } - i = -1; //(5.1.013) - if ( ntoks >= 9 ) // - { // - i = project_findObject(CURVE, toks[8]); // - if (i < 0) return error_setInpError(ERR_NAME, toks[8]); // - } // + i = -1; + if ( ntoks >= 9 ) + { + i = project_findObject(CURVE, toks[8]); + if (i < 0) return error_setInpError(ERR_NAME, toks[8]); + } //... save parameters to LID drain layer structure LidProcs[j].drain.coeff = x[0]; LidProcs[j].drain.expon = x[1]; LidProcs[j].drain.offset = x[2] / UCF(RAINDEPTH); LidProcs[j].drain.delay = x[3] * 3600.0; - LidProcs[j].drain.hOpen = x[4] / UCF(RAINDEPTH); //(5.1.013) - LidProcs[j].drain.hClose = x[5] / UCF(RAINDEPTH); // - LidProcs[j].drain.qCurve = i; // + LidProcs[j].drain.hOpen = x[4] / UCF(RAINDEPTH); + LidProcs[j].drain.hClose = x[5] / UCF(RAINDEPTH); + LidProcs[j].drain.qCurve = i; return 0; } @@ -823,8 +808,6 @@ int readDrainMatData(int j, char* toks[], int ntoks) //============================================================================= -//// This function was added to release 5.1.013. //// //(5.1.013) - int readRemovalsData(int j, char* toks[], int ntoks) // // Purpose: reads pollutant removal data for a LID process from line of input @@ -904,10 +887,10 @@ void lid_writeSummary() k = lidUnit->lidIndex; pctArea = lidUnit->area * lidUnit->number / Subcatch[j].area * 100.0; fprintf(Frpt.file, "\n %-16s %-16s", Subcatch[j].ID, LidProcs[k].ID); - fprintf(Frpt.file, "%6d %10.2f %10.2f %10.2f %10.2f %10.2f", //(5.1.013) + fprintf(Frpt.file, "%6d %10.2f %10.2f %10.2f %10.2f %10.2f", lidUnit->number, lidUnit->area * SQR(UCF(LENGTH)), lidUnit->fullWidth * UCF(LENGTH), pctArea, - lidUnit->fromImperv*100.0, lidUnit->fromPerv*100.0); //(5.1.013) + lidUnit->fromImperv*100.0, lidUnit->fromPerv*100.0); lidList = lidList->nextLidUnit; } } @@ -1019,14 +1002,14 @@ void validateLidProc(int j) LidProcs[j].drain.offset = 0.0; } - //... check for invalid drain open/closed heads //(5.1.013) - if (LidProcs[j].drain.hOpen > 0.0 && // - LidProcs[j].drain.hOpen <= LidProcs[j].drain.hClose) // - { // - strcpy(Msg, LidProcs[j].ID); // - strcat(Msg, ERR_DRAIN_HEADS); // - report_writeErrorMsg(ERR_LID_PARAMS, Msg); // - } // + //... check for invalid drain open/closed heads + if (LidProcs[j].drain.hOpen > 0.0 && + LidProcs[j].drain.hOpen <= LidProcs[j].drain.hClose) + { + strcpy(Msg, LidProcs[j].ID); + strcat(Msg, ERR_DRAIN_HEADS); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } //... compute the surface layer's overland flow constant (alpha) if ( LidProcs[j].lidType == VEG_SWALE ) @@ -1122,7 +1105,7 @@ void validateLidGroup(int j) double totalArea = Subcatch[j].area; double totalLidArea = 0.0; double fromImperv = 0.0; - double fromPerv = 0.0; //(5.1.013) + double fromPerv = 0.0; TLidUnit* lidUnit; TLidList* lidList; TLidGroup lidGroup; @@ -1138,7 +1121,7 @@ void validateLidGroup(int j) //... update contributing fractions totalLidArea += (lidUnit->area * lidUnit->number); fromImperv += lidUnit->fromImperv; - fromPerv += lidUnit->fromPerv; //(5.1.013) + fromPerv += lidUnit->fromPerv; //... assign biocell soil layer infiltration parameters lidUnit->soilInfil.Ks = 0.0; @@ -1159,10 +1142,10 @@ void validateLidGroup(int j) //... assign vegetative swale infiltration parameters if ( LidProcs[k].lidType == VEG_SWALE ) { - if ( Subcatch[j].infilModel == GREEN_AMPT || //(5.1.015) - Subcatch[j].infilModel == MOD_GREEN_AMPT ) //(5.1.015) + if ( Subcatch[j].infilModel == GREEN_AMPT || + Subcatch[j].infilModel == MOD_GREEN_AMPT ) { - grnampt_getParams(j, p); //(5.1.015) + grnampt_getParams(j, p); if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) { strcpy(Msg, LidProcs[k].ID); @@ -1196,7 +1179,7 @@ void validateLidGroup(int j) { report_writeErrorMsg(ERR_LID_AREAS, Subcatch[j].ID); } - if ( fromImperv > 1.001 || fromPerv > 1.001 ) //(5.1.013) + if ( fromImperv > 1.001 || fromPerv > 1.001 ) { report_writeErrorMsg(ERR_LID_CAPTURE_AREA, Subcatch[j].ID); } @@ -1247,8 +1230,8 @@ void lid_initState() lidUnit->soilMoisture = 0.0; lidUnit->paveDepth = 0.0; lidUnit->dryTime = initDryTime; - lidUnit->volTreated = 0.0; //(5.1.013) - lidUnit->nextRegenDay = LidProcs[k].pavement.regenDays; // + lidUnit->volTreated = 0.0; + lidUnit->nextRegenDay = LidProcs[k].pavement.regenDays; initVol = 0.0; if ( LidProcs[k].soil.thickness > 0.0 ) { @@ -1445,8 +1428,6 @@ double lid_getDrainFlow(int j, int timePeriod) //============================================================================= -//// This function was modified for relelase 5.1.013. //// //(5.1.013) - void lid_addDrainLoads(int j, double c[], double tStep) // // Purpose: adds pollutant loads routed from drains to system @@ -1510,11 +1491,11 @@ void lid_addDrainRunon(int j) // Output: none. // { - int i; // index of an LID unit's LID process //(5.1.013) + int i; // index of an LID unit's LID process int k; // index of subcatchment receiving LID drain flow int p; // pollutant index double q; // drain flow rate (cfs) - double w; // mass of polllutant from drain flow //(5.1.013) + double w; // mass of polllutant from drain flow TLidUnit* lidUnit; TLidList* lidList; TLidGroup lidGroup; @@ -1529,7 +1510,7 @@ void lid_addDrainRunon(int j) { //... see if LID's drain discharges to another subcatchment lidUnit = lidList->lidUnit; - i = lidUnit->lidIndex; //(5.1.013) + i = lidUnit->lidIndex; k = lidUnit->drainSubcatch; if ( k >= 0 && k != j ) { @@ -1542,9 +1523,9 @@ void lid_addDrainRunon(int j) // point which is converted later on to a concentration) for (p = 0; p < Nobjects[POLLUT]; p++) { - w = q * Subcatch[j].oldQual[p] * LperFT3; //(5.1.013) - w = w * (1.0 - LidProcs[i].drainRmvl[p]); // - Subcatch[k].newQual[p] += w; // + w = q * Subcatch[j].oldQual[p] * LperFT3; + w = w * (1.0 - LidProcs[i].drainRmvl[p]); + Subcatch[k].newQual[p] += w; } } lidList = lidList->nextLidUnit; @@ -1565,7 +1546,7 @@ void lid_addDrainInflow(int j, double f) // and pollutant mass (Node[].newQual[]) inflow seen by nodes that // receive drain flow from the LID units in subcatchment j. { - int i, // LID process index //(5.1.013) + int i, // LID process index k, // node index p; // pollutant index double q, // drain flow (cfs) @@ -1584,7 +1565,7 @@ void lid_addDrainInflow(int j, double f) { //... see if LID's drain discharges to conveyance system node lidUnit = lidList->lidUnit; - i = lidUnit->lidIndex; //(5.1.013) + i = lidUnit->lidIndex; k = lidUnit->drainNode; if ( k >= 0 ) { @@ -1602,7 +1583,7 @@ void lid_addDrainInflow(int j, double f) //... add interpolated load to node's wet weather loading w = (1.0 - f) * w1 + f * w2; - w = w * (1.0 - LidProcs[i].drainRmvl[p]); //(5.1.013) + w = w * (1.0 - LidProcs[i].drainRmvl[p]); Node[k].newQual[p] += w; massbal_addInflowQual(WET_WEATHER_INFLOW, p, w); } @@ -1628,7 +1609,7 @@ void lid_getRunoff(int j, double tStep) TLidUnit* lidUnit; // a member of the list of LID units double lidArea; // area of an LID unit double qImperv = 0.0; // runoff from impervious areas (cfs) - double qPerv = 0.0; // runoff from pervious areas (cfs) //(5.1.013) + double qPerv = 0.0; // runoff from pervious areas (cfs) double lidInflow = 0.0; // inflow to an LID unit (ft/s) double qRunoff = 0.0; // surface runoff from all LID units (cfs) double qDrain = 0.0; // drain flow from all LID units (cfs) @@ -1652,7 +1633,7 @@ void lid_getRunoff(int j, double tStep) if ( Subcatch[j].area > Subcatch[j].lidArea ) { qImperv = getImpervAreaRunoff(j); - qPerv = getPervAreaRunoff(j); //(5.1.013) + qPerv = getPervAreaRunoff(j); } //... evaluate performance of each LID unit placed in the subcatchment @@ -1666,8 +1647,8 @@ void lid_getRunoff(int j, double tStep) if ( lidArea > 0.0 ) { //... find runoff from non-LID area treated by LID area (ft/sec) - lidInflow = (qImperv * lidUnit->fromImperv + //(5.1.013) - qPerv * lidUnit->fromPerv) / lidArea; // + lidInflow = (qImperv * lidUnit->fromImperv + + qPerv * lidUnit->fromPerv) / lidArea; //... update total runoff volume treated VlidIn += lidInflow * lidArea * tStep; @@ -1726,7 +1707,7 @@ void findNativeInfil(int j, double tStep) NativeInfil = infil_getInfil(j, tStep, Subcatch[j].rainfall, Subcatch[j].runon, - getSurfaceDepth(j)); //(5.1.015) + getSurfaceDepth(j)); } //... see if there is any groundwater-imposed limit on infil. @@ -1769,8 +1750,6 @@ double getImpervAreaRunoff(int j) //============================================================================= -//// This function was added for release 5.1.013. //// //(5.1.013) - double getPervAreaRunoff(int j) // // Purpose: computes runoff from pervious area of a subcatchment that diff --git a/src/solver/lid.h b/src/solver/lid.h index 146b58e2a..1bfecc798 100644 --- a/src/solver/lid.h +++ b/src/solver/lid.h @@ -2,28 +2,23 @@ // lid.h // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 03/19/15 (Build 5.1.008) -// 08/01/16 (Build 5.1.011) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// Author: L. Rossman (US EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // // Public interface for LID functions. // +// Update History +// ============== // Build 5.1.008: // - Support added for Roof Disconnection LID. // - Support added for separate routing of LID drain flows. // - Detailed LID reporting modified. -// // Build 5.1.011: -// - Water depth replaces moisture content for LID's pavement layer. +// - Water depth replaces moisture content for LID's pavement layer. // - Arguments for lidproc_saveResults() modified. -// // Build 5.1.012: // - Redefined meaning of wasDry in TLidRptFile structure. -// // Build 5.1.013: // - New member fromPerv added to TLidUnit structure to allow LID // units to also treat pervious area runoff. @@ -35,13 +30,11 @@ // pollutant removal values. // - New members added to TPavementLayer and TLidUnit to support // unclogging permeable pavement at fixed intervals. -// //----------------------------------------------------------------------------- #ifndef LID_H #define LID_H - #include #include #include @@ -52,8 +45,8 @@ //----------------------------------------------------------------------------- enum LidTypes { BIO_CELL, // bio-retention cell - RAIN_GARDEN, // rain garden - GREEN_ROOF, // green roof + RAIN_GARDEN, // rain garden + GREEN_ROOF, // green roof INFIL_TRENCH, // infiltration trench POROUS_PAVEMENT, // porous pavement RAIN_BARREL, // rain barrel @@ -74,7 +67,7 @@ typedef struct { double thickness; // depression storage or berm ht. (ft) double voidFrac; // available fraction of storage volume - double roughness; // surface Mannings n + double roughness; // surface Mannings n double surfSlope; // land surface slope (fraction) double sideSlope; // swale side slope (run/rise) double alpha; // slope/roughness term in Manning eqn. @@ -89,8 +82,8 @@ typedef struct double impervFrac; // impervious area fraction double kSat; // permeability (ft/sec) double clogFactor; // clogging factor - double regenDays; // clogging regeneration interval (days) //(5.1.013) - double regenDegree; // degree of clogging regeneration // + double regenDays; // clogging regeneration interval (days) + double regenDegree; // degree of clogging regeneration } TPavementLayer; // LID Soil Layer @@ -121,9 +114,9 @@ typedef struct double expon; // underdrain head exponent (for in or mm) double offset; // offset height of underdrain (ft) double delay; // rain barrel drain delay time (sec) - double hOpen; // head when drain opens (ft) //(5.1.013) - double hClose; // head when drain closes (ft) // - int qCurve; // curve controlling flow rate (optional) // + double hOpen; // head when drain opens (ft) + double hClose; // head when drain closes (ft) + int qCurve; // curve controlling flow rate (optional) } TDrainLayer; // Drainage Mat Layer (for green roofs) @@ -146,7 +139,7 @@ typedef struct TStorageLayer storage; // storage layer parameters TDrainLayer drain; // underdrain system parameters TDrainMatLayer drainMat; // drainage mat layer - double* drainRmvl; // underdrain pollutant removals //(5.1.013) + double* drainRmvl; // underdrain pollutant removals } TLidProc; // Water Balance Statistics @@ -179,13 +172,13 @@ typedef struct double botWidth; // bottom width of single unit (ft) double initSat; // initial saturation of soil & storage layers double fromImperv; // fraction of impervious area runoff treated - double fromPerv; // fraction of pervious area runoff treated //(5.1.013) + double fromPerv; // fraction of pervious area runoff treated int toPerv; // 1 if outflow sent to pervious area; 0 if not int drainSubcatch; // subcatchment receiving drain flow int drainNode; // node receiving drain flow TLidRptFile* rptFile; // pointer to detailed report file - TGrnAmpt soilInfil; // infil. object for biocell soil layer + TGrnAmpt soilInfil; // infil. object for biocell soil layer double surfaceDepth; // depth of ponded water on surface layer (ft) double paveDepth; // depth of water in porous pavement layer double soilMoisture; // moisture content of biocell soil layer @@ -193,12 +186,12 @@ typedef struct // net inflow - outflow from previous time step for each LID layer (ft/s) double oldFluxRates[MAX_LAYERS]; - + double dryTime; // time since last rainfall (sec) double oldDrainFlow; // previous drain flow (cfs) double newDrainFlow; // current drain flow (cfs) - double volTreated; // total volume treated (ft) //(5.1.013) - double nextRegenDay; // next day when unit regenerated // + double volTreated; // total volume treated (ft) + double nextRegenDay; // next day when unit regenerated TWaterBalance waterBalance; // water balance quantites } TLidUnit; @@ -237,5 +230,4 @@ double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, double ucfRainDepth); - -#endif //LID_H +#endif diff --git a/src/solver/lidproc.c b/src/solver/lidproc.c index 54b3f9ca9..1d7bc7a3e 100644 --- a/src/solver/lidproc.c +++ b/src/solver/lidproc.c @@ -2,28 +2,20 @@ // lidproc.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/12 (Build 5.1.001) -// 05/19/14 (Build 5.1.006) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 04/30/15 (Build 5.1.009) -// 08/05/15 (Build 5.1.010) -// 08/01/16 (Build 5.1.011) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// 03/01/20 (Build 5.1.014) -// Author: L. Rossman (US EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // // This module computes the hydrologic performance of an LID (Low Impact // Development) unit at a given point in time. // +// Update History +// ============== // Build 5.1.007: // - Euler integration now applied to all LID types except Vegetative // Swale which continues to use successive approximation. // - LID layer flux routines were re-written to more accurately model // flooded conditions. -// // Build 5.1.008: // - MAX_STATE_VARS replaced with MAX_LAYERS. // - Optional soil layer added to Porous Pavement LID. @@ -33,13 +25,10 @@ // - Detailed reporting procedure fixed. // - Possibile negative head on Bioretention Cell drain avoided. // - Bug in computing flow through Green Roof drainage mat fixed. -// // Build 5.1.009: // - Fixed typo in net flux rate for vegetative swale LID. -// // Build 5.1.010: // - New modified version of Green-Ampt used for surface layer infiltration. -// // Build 5.1.011: // - Re-named STOR_INFIL to STOR_EXFIL and StorageInfil to StorageExfil to // better reflect their meaning. @@ -48,22 +37,18 @@ // - Flux rate routines for LIDs with underdrains modified to produce more // physically meaningful results. // - Reporting of detailed results re-written. -// // Build 5.1.012: // - Modified upper limit for soil layer percolation. // - Modified upper limit on surface infiltration into rain gardens. // - Modified upper limit on drain flow for LIDs with storage layers. // - Used re-defined wasDry variable for LID reports to fix duplicate lines. -// // Build 5.1.013: // - Support added for open/closed head levels and multiplier v. head curve // to control underdrain flow. // - Support added for regenerating pavement permeability at fixed intervals. -// // Build 5.1.014: // - Fixed failure to initialize all LID layer moisture volumes to 0 before // computing LID unit performance in lidproc_getOutflow. -// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -881,8 +866,8 @@ void pavementFluxRates(double x[], double f[]) //... find perc rate out of pavement layer PavePerc = getPavementPermRate(); - //... surface infiltration can't exceed pavement permeability //(5.1.013) - SurfaceInfil = MIN(SurfaceInfil, PavePerc); // + //... surface infiltration can't exceed pavement permeability + SurfaceInfil = MIN(SurfaceInfil, PavePerc); //... limit pavement perc by available water maxRate = PaveVolume/Tstep + SurfaceInfil - PaveEvap; @@ -1357,7 +1342,7 @@ double getStorageDrainRate(double storageDepth, double soilTheta, // layers above it (soil, pavement, and surface in that order) // minus the drain outlet offset. { - int curve = theLidProc->drain.qCurve; //(5.1.013) + int curve = theLidProc->drain.qCurve; double head = storageDepth; double outflow = 0.0; double paveThickness = theLidProc->pavement.thickness; @@ -1397,13 +1382,13 @@ double getStorageDrainRate(double storageDepth, double soilTheta, } } - // --- no outflow if: //(5.1.013) - // a) no prior outflow and head below open threshold // - // b) prior outflow and head below closed threshold // - if ( theLidUnit->oldDrainFlow == 0.0 && // - head <= theLidProc->drain.hOpen ) return 0.0; // - if ( theLidUnit->oldDrainFlow > 0.0 && // - head <= theLidProc->drain.hClose ) return 0.0; // + // --- no outflow if: + // a) no prior outflow and head below open threshold + // b) prior outflow and head below closed threshold + if ( theLidUnit->oldDrainFlow == 0.0 && + head <= theLidProc->drain.hOpen ) return 0.0; + if ( theLidUnit->oldDrainFlow > 0.0 && + head <= theLidProc->drain.hClose ) return 0.0; // --- make head relative to drain offset head -= theLidProc->drain.offset; @@ -1420,7 +1405,7 @@ double getStorageDrainRate(double storageDepth, double soilTheta, pow(head, theLidProc->drain.expon); // --- apply user-supplied control curve to outflow - if (curve >= 0) outflow *= table_lookup(&Curve[curve], head); //(5.1.013) + if (curve >= 0) outflow *= table_lookup(&Curve[curve], head); // --- convert outflow to ft/s outflow /= UCF(RAINFALL); @@ -1527,7 +1512,7 @@ void updateWaterBalance(TLidUnit *lidUnit, double inflow, double evap, // Output: none // { - lidUnit->volTreated += inflow * Tstep; //(5.1.013) + lidUnit->volTreated += inflow * Tstep; lidUnit->waterBalance.inflow += inflow * Tstep; lidUnit->waterBalance.evap += evap * Tstep; lidUnit->waterBalance.infil += infil * Tstep; diff --git a/src/solver/link.c b/src/solver/link.c index 3abc2f5f8..ca8627de4 100644 --- a/src/solver/link.c +++ b/src/solver/link.c @@ -2,54 +2,46 @@ // link.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 08/05/15 (Build 5.1.010) -// 08/01/16 (Build 5.1.011) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// 03/01/20 (Build 5.1.014) -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // M. Tryby (EPA) // // Conveyance system link functions // +// Update History +// ============== // Build 5.1.007: // - Optional surcharging of weirs introduced. -// // Build 5.1.008: // - Bug in finding flow through surcharged weir fixed. // - Bug in finding if conduit is upstrm/dnstrm full fixed. // - Monthly conductivity adjustment applied to conduit seepage. // - Conduit seepage limited by conduit's flow rate. -// // Build 5.1.010: // - Support added for new ROADWAY_WEIR object. // - Time of last setting change initialized for links. -// // Build 5.1.011: // - Crest elevation of regulator links raised to downstream invert. // - Fixed converting roadWidth weir parameter to internal units. // - Weir shape parameter deprecated. // - Extra geometric parameters ignored for non-conduit open rectangular // cross sections. -// // Build 5.1.012: // - Conduit seepage rate now based on flow width, not wetted perimeter. // - Formula for side flow weir corrected. // - Crest length contraction adjustments corrected. -// // Build 5.1.013: // - Maximum depth adjustments made for storage units that can surcharge. // - Support added for head-dependent weir coefficient curves. // - Adjustment of regulator link crest offset to match downstream node invert // now only done for Dynamic Wave flow routing. -// // Build 5.1.014: // - Conduit evap. and seepage losses initialized to 0 in conduit_initState() // and not allowed to exceed current flow rate in conduit_getLossRate(). +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for variable speed pumps. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -57,6 +49,8 @@ #include #include #include "headers.h" +#include "inlet.h" +#include "street.h" //----------------------------------------------------------------------------- // Constants @@ -102,7 +96,7 @@ static double conduit_getLength(int j); static double conduit_getLengthFactor(int j, int k, double roughness); static double conduit_getSlope(int j); static double conduit_getInflow(int j); -static double conduit_getLossRate(int j, double q); //(5.1.014) +static double conduit_getLossRate(int j, double q); static int pump_readParams(int j, int k, char* tok[], int ntoks); static void pump_validate(int j, int k); @@ -194,6 +188,15 @@ int link_readXsectParams(char* tok[], int ntoks) Link[j].xsect.type = k; Link[j].xsect.transect = i; } + + // --- for street cross section, find index of Street object + else if (k == STREET_XSECT) + { + i = project_findObject(STREET, tok[2]); + if (i < 0) return error_setInpError(ERR_NAME, tok[2]); + Link[j].xsect.type = k; + Link[j].xsect.transect = i; + } else { // --- parse max. depth & shape curve for a custom shape @@ -360,7 +363,7 @@ void link_setParams(int j, int type, int n1, int n2, int k, double x[]) Weir[k].canSurcharge = (int)x[6]; Weir[k].roadWidth = x[7] / UCF(LENGTH); Weir[k].roadSurface = (int)x[8]; - Weir[k].cdCurve = (int)x[9]; //(5.1.013) + Weir[k].cdCurve = (int)x[9]; break; case OUTLET: @@ -407,13 +410,13 @@ void link_validate(int j) if ( Node[Link[j].node1].invertElev + Link[j].offset1 < Node[Link[j].node2].invertElev ) { - if (RouteModel == DW) //(5.1.013) + if (RouteModel == DW) { Link[j].offset1 = Node[Link[j].node2].invertElev - Node[Link[j].node1].invertElev; - report_writeWarningMsg(WARN10b, Link[j].ID); //(5.1.013) + report_writeWarningMsg(WARN10b, Link[j].ID); } - else report_writeWarningMsg(WARN10a, Link[j].ID); //(5.1.013) + else report_writeWarningMsg(WARN10a, Link[j].ID); } } @@ -427,7 +430,7 @@ void link_validate(int j) // --- extend upstream node's full depth to link's crown elevation n = Link[j].node1; - if ( Node[n].type != STORAGE || Node[n].surDepth > 0.0 ) //(5.1.013) + if ( Node[n].type != STORAGE || Node[n].surDepth > 0.0 ) { Node[n].fullDepth = MAX(Node[n].fullDepth, Link[j].offset1 + Link[j].xsect.yFull); @@ -435,7 +438,7 @@ void link_validate(int j) // --- do same for downstream node only for conduit links n = Link[j].node2; - if ( (Node[n].type != STORAGE || Node[n].surDepth > 0.0) && //(5.1.013) + if ( (Node[n].type != STORAGE || Node[n].surDepth > 0.0) && Link[j].type == CONDUIT ) { Node[n].fullDepth = MAX(Node[n].fullDepth, @@ -514,7 +517,7 @@ void link_initState(int j) { Link[j].oldQual[p] = 0.0; Link[j].newQual[p] = 0.0; - Link[j].totalLoad[p] = 0.0; + Link[j].totalLoad[p] = 0.0; } } @@ -870,7 +873,7 @@ double link_getPower(int j) //============================================================================= -double link_getLossRate(int j, double q) //(5.1.014) +double link_getLossRate(int j, double q) // // Input: j = link index // q = flow rate (ft3/sec) @@ -880,7 +883,7 @@ double link_getLossRate(int j, double q) / // evaporation and seepage. // { - if ( Link[j].type == CONDUIT ) return conduit_getLossRate(j, q); //(5.1.014) + if ( Link[j].type == CONDUIT ) return conduit_getLossRate(j, q); else return 0.0; } @@ -999,6 +1002,13 @@ void conduit_validate(int j, int k) Conduit[k].roughness = Transect[Link[j].xsect.transect].roughness; } + // --- if street xsection, then set its parameters + if (Link[j].xsect.type == STREET_XSECT) + { + xsect_setStreetXsectParams(&Link[j].xsect); + Conduit[k].roughness = Street[Link[j].xsect.transect].roughness; + } + // --- if force main xsection, adjust units on D-W roughness height if ( Link[j].xsect.type == FORCE_MAIN ) { @@ -1031,7 +1041,7 @@ void conduit_validate(int j, int k) report_writeWarningMsg(WARN03, Link[j].ID); Link[j].offset1 = 0.0; } - if ( Link[j].offset2 < 0.0 ) + if ( Link[j].offset2 < 0.0 ) { report_writeWarningMsg(WARN03, Link[j].ID); Link[j].offset2 = 0.0; @@ -1282,8 +1292,8 @@ void conduit_initState(int j, int k) { Link[j].newDepth = link_getYnorm(j, Link[j].q0 / Conduit[k].barrels); Link[j].oldDepth = Link[j].newDepth; - Conduit[k].evapLossRate = 0.0; //(5.1.014) - Conduit[k].seepLossRate = 0.0; //(5.1.014) + Conduit[k].evapLossRate = 0.0; + Conduit[k].seepLossRate = 0.0; } //============================================================================= @@ -1302,8 +1312,6 @@ double conduit_getInflow(int j) //============================================================================= -//// This function was modified for relese 5.1.014. //// //(5.1.014) - double conduit_getLossRate(int j, double q) // // Input: j = link index @@ -1457,7 +1465,7 @@ void pump_validate(int j, int k) else { if ( Curve[m].curveType < PUMP1_CURVE || - Curve[m].curveType > PUMP4_CURVE ) + Curve[m].curveType > PUMP5_CURVE ) report_writeErrorMsg(ERR_NO_CURVE, Link[j].ID); // --- store pump curve type with pump's parameters @@ -1521,6 +1529,7 @@ double pump_getInflow(int j) int n1, n2; double vol, depth, head; double qIn, qIn1, dh = 0.001; + double s = 1.0; // speed setting k = Link[j].subIndex; m = Pump[k].pumpCurve; @@ -1558,23 +1567,22 @@ double pump_getInflow(int j) break; case PUMP3_CURVE: - head = ( (Node[n2].newDepth + Node[n2].invertElev) - - (Node[n1].newDepth + Node[n1].invertElev) ); - - head = MAX(head, 0.0); - - qIn = table_lookup(&Curve[m], head*UCF(LENGTH)) / UCF(FLOW); - - // --- compute dQ/dh (slope of pump curve) and - // reverse sign since flow decreases with increasing head - Link[j].dqdh = -table_getSlope(&Curve[m], head*UCF(LENGTH)) * - UCF(LENGTH) / UCF(FLOW); - - // --- check if off of pump curve - head *= UCF(LENGTH); - if ( head < Pump[k].xMin || head > Pump[k].xMax ) - Link[j].flowClass = YES; - break; + case PUMP5_CURVE: + if (Curve[m].curveType == PUMP5_CURVE) s = Link[j].setting; + head = ((Node[n2].newDepth + Node[n2].invertElev) - + (Node[n1].newDepth + Node[n1].invertElev)) / s / s; + head = MAX(head, 0.0) * UCF(LENGTH); + qIn = table_lookup(&Curve[m], head) / UCF(FLOW); + + // --- compute dQ/dh (slope of pump curve) and + // reverse sign since flow decreases with increasing head + Link[j].dqdh = -table_getSlope(&Curve[m], head) * + UCF(LENGTH) / UCF(FLOW) / s; + + // --- check if off of pump curve + if (head < Pump[k].xMin || head > Pump[k].xMax) + Link[j].flowClass = YES; + break; case PUMP4_CURVE: depth = Node[n1].newDepth; @@ -1984,7 +1992,7 @@ int weir_readParams(int j, int k, char* tok[], int ntoks) { int m; int n1, n2; - double x[10]; //(5.1.013) + double x[10]; char* id; // --- check for valid ID and end node IDs @@ -2011,7 +2019,7 @@ int weir_readParams(int j, int k, char* tok[], int ntoks) x[6] = 1.0; x[7] = 0.0; x[8] = 0.0; - x[9] = -1.0; //(5.1.013) + x[9] = -1.0; if ( ntoks >= 7 && *tok[6] != '*' ) { m = findmatch(tok[6], NoYesWords); @@ -2050,12 +2058,12 @@ int weir_readParams(int j, int k, char* tok[], int ntoks) } } - if (ntoks >= 13 && *tok[12] != '*') //(5.1.013) - { // - m = project_findObject(CURVE, tok[12]); // coeff. curve // - if (m < 0) return error_setInpError(ERR_NAME, tok[12]); // - x[9] = m; // - } // + if (ntoks >= 13 && *tok[12] != '*') + { + m = project_findObject(CURVE, tok[12]); // coeff. curve + if (m < 0) return error_setInpError(ERR_NAME, tok[12]); + x[9] = m; + } // --- add parameters to weir object Link[j].ID = id; @@ -2303,8 +2311,8 @@ void weir_getFlow(int j, int k, double head, double dir, int hasFlapGate, double area; double veloc; int wType; - int cdCurve = Weir[k].cdCurve; //(5.1.013) - double cDisch1 = Weir[k].cDisch1; // + int cdCurve = Weir[k].cdCurve; + double cDisch1 = Weir[k].cDisch1; // --- q1 = flow through central portion of weir, // q2 = flow through end sections of trapezoidal weir @@ -2317,8 +2325,8 @@ void weir_getFlow(int j, int k, double head, double dir, int hasFlapGate, length = Link[j].xsect.wMax * UCF(LENGTH); h = head * UCF(LENGTH); - // --- lookup tabulated discharge coeff. //(5.1.013) - if ( cdCurve >= 0 ) cDisch1 = table_lookup(&Curve[cdCurve], h); // + // --- lookup tabulated discharge coeff. + if ( cdCurve >= 0 ) cDisch1 = table_lookup(&Curve[cdCurve], h); // --- use appropriate formula for weir flow wType = Weir[k].type; @@ -2331,7 +2339,7 @@ void weir_getFlow(int j, int k, double head, double dir, int hasFlapGate, // --- reduce length when end contractions present length -= 0.1 * Weir[k].endCon * h; length = MAX(length, 0.0); - *q1 = cDisch1 * length * pow(h, 1.5); //(5.1.013) + *q1 = cDisch1 * length * pow(h, 1.5); break; case SIDEFLOW_WEIR: @@ -2342,23 +2350,23 @@ void weir_getFlow(int j, int k, double head, double dir, int hasFlapGate, // --- weir behaves as a transverse weir under reverse flow if ( dir < 0.0 ) - *q1 = cDisch1 * length * pow(h, 1.5); //(5.1.013) + *q1 = cDisch1 * length * pow(h, 1.5); else // Corrected formula (see Metcalf & Eddy, Inc., // Wastewater Engineering, McGraw-Hill, 1972 p. 164). - *q1 = cDisch1 * pow(length, 0.83) * pow(h, 1.67); //(5.1.013) + *q1 = cDisch1 * pow(length, 0.83) * pow(h, 1.67); break; case VNOTCH_WEIR: - *q1 = cDisch1 * Weir[k].slope * pow(h, 2.5); //(5.1.013) + *q1 = cDisch1 * Weir[k].slope * pow(h, 2.5); break; case TRAPEZOIDAL_WEIR: y = (1.0 - Link[j].setting) * Link[j].xsect.yFull; length = xsect_getWofY(&Link[j].xsect, y) * UCF(LENGTH); - *q1 = cDisch1 * length * pow(h, 1.5); //(5.1.013) + *q1 = cDisch1 * length * pow(h, 1.5); *q2 = Weir[k].cDisch2 * Weir[k].slope * pow(h, 2.5); } diff --git a/src/solver/macros.h b/src/solver/macros.h index 6f10ae464..b70ecd6f9 100644 --- a/src/solver/macros.h +++ b/src/solver/macros.h @@ -2,8 +2,8 @@ // macros.h // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/07 (Build 5.1.001) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman //----------------------------------------------------------------------------- diff --git a/src/solver/massbal.c b/src/solver/massbal.c index c5a02903d..1ef8e21b7 100644 --- a/src/solver/massbal.c +++ b/src/solver/massbal.c @@ -2,41 +2,34 @@ // massbal.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/19/14 (Build 5.1.001) -// 09/15/14 (Build 5.1.007) -// 04/02/15 (Build 5.1.008) -// 08/05/15 (Build 5.1.010) -// 08/01/16 (Build 5.1.011) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/26/21 (Build 5.2.0) +// Author: L. Rossman // M. Tryby (EPA) // // Mass balance functions // +// Update History +// ============== // Build 5.1.007: // - Mass balances modified to to correctly handle negative external inflows. // - Volume from minimum surface area at nodes included in mass balances. -// // Build 5.1.008: // - massbal_updateRunoffTotals() modified. // - LID drain flows and returned outfall flows added to components of // runoff mass balance. // - Seepage pollutant loss added into mass balances. -// // Build 5.1.010: // - Remaining pollutant mass in "dry" elements now added to final storage. -// // Build 5.1.011: // - Final stored pollutant mass in links ignored for Steady Flow routing. -// // Build 5.1.012: // - Terminal storage nodes no longer treated as non-storage terminal // nodes are when updating total outflow volume. -// // Build 5.1.013: // - Volume from MinSurfArea no longer included in initial & final storage. +// Build 5.2.0: +// - Support added for Inlets. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -625,20 +618,38 @@ void massbal_updateRoutingTotals(double tStep) QualTotals[j].seepLoss += StepQualTotals[j].seepLoss * tStep; QualTotals[j].finalStorage += StepQualTotals[j].finalStorage; } +} + +//============================================================================= - for ( j = 0; j < Nobjects[NODE]; j++) +void massbal_updateNodeTotals(double tStep) +// +// Input: tStep = time step (sec) +// Output: none +// Purpose: updates cumulative inflow and outflow volumes for nodes. +// +{ + int j; + for (j = 0; j < Nobjects[NODE]; j++) { + // --- update node's cumulative inflow volume NodeInflow[j] += Node[j].inflow * tStep; - if ( Node[j].type == OUTFALL || - (Node[j].degree == 0 && Node[j].type != STORAGE) ) + + // --- for outfall nodes and non-storage nodes with no outflow + // links, add inflow volume to cumulative outflow volume + if (Node[j].type == OUTFALL || + (Node[j].degree == 0 && Node[j].type != STORAGE)) { NodeOutflow[j] += Node[j].inflow * tStep; } + + // --- otherwise update node's cumulative outflow volume with + // its current outflow volume plus any overflow volume else { - NodeOutflow[j] += Node[j].outflow * tStep; - if ( Node[j].newVolume <= Node[j].fullVolume ) - NodeOutflow[j] += Node[j].overflow * tStep; + NodeOutflow[j] += Node[j].outflow * tStep; + if (Node[j].newVolume <= Node[j].fullVolume) + NodeOutflow[j] += Node[j].overflow * tStep; } } } diff --git a/src/solver/mathexpr.c b/src/solver/mathexpr.c index d5e944b85..854b571f9 100644 --- a/src/solver/mathexpr.c +++ b/src/solver/mathexpr.c @@ -1,6 +1,6 @@ /****************************************************************************** ** MODULE: MATHEXPR.C -** PROJECT: EPA SWMM 5.1 +** PROJECT: EPA SWMM5 ** DESCRIPTION: Evaluates symbolic mathematical expression consisting ** of numbers, variable names, math functions & arithmetic ** operators. diff --git a/src/solver/mathexpr.h b/src/solver/mathexpr.h index ab56b7fba..b36d3db1d 100644 --- a/src/solver/mathexpr.h +++ b/src/solver/mathexpr.h @@ -1,6 +1,6 @@ /****************************************************************************** ** MODULE: MATHEXPR.H -** PROJECT: SWMM 5.1 +** PROJECT: SWMM5 ** DESCRIPTION: header file for the math expression parser in mathexpr.c. ** AUTHORS: L. Rossman, US EPA - NRMRL ** F. Shang, University of Cincinnati diff --git a/src/solver/node.c b/src/solver/node.c index ac1061621..21a675aaa 100644 --- a/src/solver/node.c +++ b/src/solver/node.c @@ -2,40 +2,34 @@ // node.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 09/15/14 (Build 5.1.007) -// 04/02/15 (Build 5.1.008) -// 08/05/15 (Build 5.1.010) -// 05/10/18 (Build 5.1.013) -// 03/01/20 (Build 5.1.014) -// 04/14/20 (Build 5.1.015) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Conveyance system node functions. // +// Update History +// ============== // Build 5.1.007: // - Ponded area property for storage nodes deprecated. // - Support for Green-Ampt seepage from bottom and sides of storage node added. // - Storage node evap. & seepage losses now computed at start of each routing // time step. -// // Build 5.1.008: // - Support added for sending outfall discharge to a subctchment. -// // Build 5.1.010: // - Storage losses now based on node's new volume instead of old volume. -// // Build 5.1.013: // - A surcharge depth can now be applied to storage nodes. // - A negative inflow is now assigned to an Outfall node with backflow. -// // Build 5.1.014: // - Fixed bug in storage_losses() that affected storage exfiltration. -// // Build 5.1.015: // - Fatal error issued if a storage node's area curve produces a negative // volume when extrapolated to the node's full depth. +// Build 5.2.0: +// - Support added Streets and Inlets. +// - Support added for conic & pyramidal storage functions. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -169,15 +163,13 @@ void node_setParams(int j, int type, int k, double x[]) case STORAGE: Node[j].fullDepth = x[1] / UCF(LENGTH); Node[j].initDepth = x[2] / UCF(LENGTH); - Storage[k].aCoeff = x[3]; - Storage[k].aExpon = x[4]; - Storage[k].aConst = x[5]; - Storage[k].aCurve = (int)x[6]; - - // Surcharge depth replaces ponded area //(5.1.013) - Node[j].surDepth = x[7] / UCF(LENGTH); // - - Storage[k].fEvap = x[8]; + Storage[k].shape = (int)x[3]; + Storage[k].a1 = x[4]; + Storage[k].a2 = x[5]; + Storage[k].a0 = x[6]; + Storage[k].aCurve = (int)x[7]; + Node[j].surDepth = x[8] / UCF(LENGTH); + Storage[k].fEvap = x[9]; break; case DIVIDER: @@ -216,10 +208,10 @@ void node_validate(int j) if ( Node[j].initDepth > Node[j].fullDepth + Node[j].surDepth ) report_writeErrorMsg(ERR_NODE_DEPTH, Node[j].ID); - // --- check for negative volume for storage node at full depth //(5.1.015) - if (Node[j].type == STORAGE) // - if (node_getVolume(j, Node[j].fullDepth) < 0.0) // - report_writeErrorMsg(ERR_STORAGE_VOLUME, Node[j].ID); // + // --- check for negative volume for storage node at full depth + if (Node[j].type == STORAGE) + if (node_getVolume(j, Node[j].fullDepth) < 0.0) + report_writeErrorMsg(ERR_STORAGE_VOLUME, Node[j].ID); if ( Node[j].type == DIVIDER ) divider_validate(j); @@ -298,6 +290,8 @@ void node_setOldHydState(int j) { Node[j].oldDepth = Node[j].newDepth; Node[j].oldVolume = Node[j].newVolume; + Node[j].oldFlowInflow = Node[j].inflow; + Node[j].oldNetInflow = Node[j].inflow - Node[j].outflow; } //============================================================================= @@ -328,8 +322,6 @@ void node_initInflow(int j, double tStep) // { // --- initialize inflow & outflow - Node[j].oldFlowInflow = Node[j].inflow; - Node[j].oldNetInflow = Node[j].inflow - Node[j].outflow; Node[j].inflow = Node[j].newLatFlow; Node[j].outflow = Node[j].losses; @@ -646,7 +638,6 @@ int junc_readParams(int j, int k, char* tok[], int ntoks) return 0; } - //============================================================================= // S T O R A G E M E T H O D S //============================================================================= @@ -661,12 +652,17 @@ int storage_readParams(int j, int k, char* tok[], int ntoks) // Purpose: reads a storage unit's properties from a tokenized line of input. // // Format of input line is: -// nodeID elev maxDepth initDepth FUNCTIONAL a1 a2 a0 surDepth fEvap (infil) //(5.1.013) -// nodeID elev maxDepth initDepth TABULAR curveID surDepth fEvap (infil) // -// +// nodeID elev maxDepth initDepth FUNCTIONAL a1 a2 a0 surDepth fEvap (infil) +// nodeID elev maxDepth initDepth CONICAL L W Z surDepth fEvap (infil) +// nodeID elev maxDepth initDepth PYRAMIDAL L W Z surDepth fEvap (infil) +// nodeID elev maxDepth initDepth TABULAR curveID surDepth fEvap (infil) +// x[0] x[1] x[2] x[3] x[4..7] x[8] x[9] { int i, m, n; - double x[9]; + double x[10], y[3]; + double A, B; //base semi-axis length & width for conical shape + double L, W; //base length & width for pyramidal shape + double Z; //run over rise for conical & pyramidal sides char* id; // --- get ID name @@ -684,40 +680,84 @@ int storage_readParams(int j, int k, char* tok[], int ntoks) // --- get surf. area relation type m = findmatch(tok[4], RelationWords); if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); - x[3] = 0.0; // a1 - x[4] = 0.0; // a2 - x[5] = 0.0; // a0 - x[6] = -1.0; // curveID - x[7] = 0.0; // aPond - x[8] = 0.0; // fEvap + x[3] = m; + x[4] = 0.0; // a1 + x[5] = 0.0; // a2 + x[6] = 0.0; // a0 + x[7] = -1.0; // curveID + x[8] = 0.0; // surDepth + x[9] = 0.0; // fEvap + + // --- get surf. area curve name + if (m == TABULAR) + { + i = project_findObject(CURVE, tok[5]); + if (i < 0) return error_setInpError(ERR_NAME, tok[5]); + x[7] = i; + n = 6; + } // --- get surf. area function coeffs. - if ( m == FUNCTIONAL ) + else { + if (ntoks < 8) return error_setInpError(ERR_ITEMS, ""); for (i=5; i<=7; i++) { - if ( i < ntoks ) - { - if ( ! getDouble(tok[i], &x[i-2]) ) - return error_setInpError(ERR_NUMBER, tok[i]); - } + if ( ! getDouble(tok[i], &y[i-5]) ) + return error_setInpError(ERR_NUMBER, tok[i]); } n = 8; } - // --- get surf. area curve name - else + // --- check for valid data + switch (m) { - m = project_findObject(CURVE, tok[5]); - if ( m < 0 ) return error_setInpError(ERR_NAME, tok[5]); - x[6] = m; - n = 6; + case FUNCTIONAL: + // area at 0 depth can't be negative + if (y[2] < 0.0) return error_setInpError(ERR_NUMBER, tok[7]); + break; + + case CONICAL: + case PYRAMIDAL: + // length or width can't be <= 0, slope can't be < 0 + if (y[0] <= 0.0 ) return error_setInpError(ERR_NUMBER, tok[5]); + if (y[1] <= 0.0) return error_setInpError(ERR_NUMBER, tok[6]); + if (y[2] < 0.0) return error_setInpError(ERR_NUMBER, tok[7]); + break; } - // --- ponded area replaced by surcharge depth //(5.1.013) + // --- convert supplied parameters to coeffs. in surface area equation + switch (m) + { + case FUNCTIONAL: + x[4] = y[0]; + x[5] = y[1]; + x[6] = y[2]; + break; + + case CONICAL: + A = y[0] / 2.; // base semi-axis length + B = y[1] / 2.; // base semi axis width + Z = y[2]; // side slope + x[4] = 2.0 * PI * B * Z; // linear coeff. + x[5] = PI * B / A * Z * Z; // quadratic coeff. + x[6] = PI * A * B; // constant + break; + + case PYRAMIDAL: + L = y[0]; + W = y[1]; + Z = y[2]; + x[4] = 2.0 * (L + W) * Z; // linear coeff. + x[5] = 4.0 * Z * Z; // quadratic coeff. + x[6] = L * W; // constant + break; + } + + // --- get surcharge depth if present if ( ntoks > n) { - if ( ! getDouble(tok[n], &x[7]) ) + if ( ! getDouble(tok[n], &x[8]) ) return error_setInpError(ERR_NUMBER, tok[n]); n++; } @@ -725,7 +765,7 @@ int storage_readParams(int j, int k, char* tok[], int ntoks) // --- get evaporation fraction if present if ( ntoks > n ) { - if ( ! getDouble(tok[n], &x[8]) ) + if ( ! getDouble(tok[n], &x[9]) ) return error_setInpError(ERR_NUMBER, tok[n]); n++; } @@ -751,8 +791,8 @@ double storage_getDepth(int j, double v) { int k = Node[j].subIndex; int i = Storage[k].aCurve; - double d, e; - TStorageVol storageVol; + double d; + TStorageVol storageVol; // --- return max depth if a max. volume has been computed // and volume is > max. volume @@ -760,35 +800,26 @@ double storage_getDepth(int j, double v) && v >= Node[j].fullVolume ) return Node[j].fullDepth; if ( v == 0.0 ) return 0.0; - // --- use tabular area v. depth curve - if ( i >= 0 ) - return table_getInverseArea(&Curve[i], v*UCF(VOLUME)) / UCF(LENGTH); - - // --- use functional area v. depth relation - else + if (Storage[k].shape == TABULAR) { - v *= UCF(VOLUME); - if ( Storage[k].aExpon == 0.0 ) - { - d = v / (Storage[k].aConst + Storage[k].aCoeff); - } - else if ( Storage[k].aConst == 0.0 ) - { - e = 1.0 / (Storage[k].aExpon + 1.0); - d = pow(v / (Storage[k].aCoeff * e), e); - } - else - { - storageVol.k = k; - storageVol.v = v; - d = v / (Storage[k].aConst + Storage[k].aCoeff); - findroot_Newton(0.0, Node[j].fullDepth*UCF(LENGTH), &d, - 0.001, storage_getVolDiff, &storageVol); - } - d /= UCF(LENGTH); - if ( d > Node[j].fullDepth ) d = Node[j].fullDepth; - return d; + i = Storage[k].aCurve; + if (i >= 0) + return table_getStorageDepth(&Curve[i], v*UCF(VOLUME)) / UCF(LENGTH); + else return 0.0; } + + v *= UCF(VOLUME); + storageVol.k = k; + storageVol.v = v; + if (Storage[k].shape == FUNCTIONAL) + d = v / (Storage[k].a0 + Storage[k].a1); + else + d = v / Storage[k].a0; + findroot_Newton(0.0, Node[j].fullDepth*UCF(LENGTH), &d, + 0.001, storage_getVolDiff, &storageVol); + d /= UCF(LENGTH); + if ( d > Node[j].fullDepth ) d = Node[j].fullDepth; + return d; } //============================================================================= @@ -796,28 +827,23 @@ double storage_getDepth(int j, double v) void storage_getVolDiff(double y, double* f, double* df, void* p) // // Input: y = depth of water (ft) +// p = pointer to a TStorageVol object // Output: f = volume of water (ft3) -// df = dVolume/dDepth (ft2) -// Purpose: computes volume and its derivative with respect to depth -// at storage node Kstar using the node's area versus depth function. +// df = dVolume/dDepth ( = surface area)(ft2) +// Purpose: computes volume difference and its derivative at a storage node +// using the node's area versus depth function. // { int k; - double e, v; TStorageVol* storageVol; - // ... cast void pointer p to a TStorageVol object + // --- cast void pointer p to a TStorageVol object storageVol = (TStorageVol *)p; k = storageVol->k; - // ... find storage volume at depth y - e = Storage[k].aExpon + 1.0; - v = Storage[k].aConst * y + Storage[k].aCoeff / e * pow(y, e); - - // ... compute difference between this volume and target volume - // as well as its derivative w.r.t. y - *f = v - storageVol->v; - *df = Storage[k].aConst + Storage[k].aCoeff * pow(y, e-1.0); + // --- compute volume & surface area at depth y + *f = storage_getVolume(k, y) - storageVol->v; + *df = storage_getSurfArea(k, y); } //============================================================================= @@ -831,27 +857,39 @@ double storage_getVolume(int j, double d) // { int k = Node[j].subIndex; - int i = Storage[k].aCurve; - double v; + int i; + double n, v; // --- return full volume if depth >= max. depth if ( d == 0.0 ) return 0.0; if ( d >= Node[j].fullDepth && Node[j].fullVolume > 0.0 ) return Node[j].fullVolume; - // --- use table integration if area v. depth table exists - if ( i >= 0 ) - return table_getArea(&Curve[i], d*UCF(LENGTH)) / UCF(VOLUME); - - // --- otherwise use functional area v. depth relation - else + switch (Storage[k].shape) { - d *= UCF(LENGTH); - v = Storage[k].aConst * d; - v += Storage[k].aCoeff / (Storage[k].aExpon+1.0) * - pow(d, Storage[k].aExpon+1.0); - return v / UCF(VOLUME); + // --- for tabular shape function, use end area method + case TABULAR: + i = Storage[k].aCurve; + if (i >= 0) + return table_getStorageVolume(&Curve[i], d*UCF(LENGTH)) / + UCF(VOLUME); + else return 0.0; + // --- for FUNCTIONAL relation, integrate a0 + a1*d^a2 + case FUNCTIONAL: + d *= UCF(LENGTH); + n = Storage[k].a2 + 1.0; + v = (Storage[k].a0 * d) + Storage[k].a1 / n * pow(d, n); + return v / UCF(VOLUME); + + // --- for conical & pyramidal shapes, integrate quadratic function + case CONICAL: + case PYRAMIDAL: + d *= UCF(LENGTH); + v = d * (Storage[k].a0 + d * (Storage[k].a1 / 2.0 + d * Storage[k].a2 / 3.0)); + return v / UCF(VOLUME); + + default: return 0.0; } } @@ -865,18 +903,33 @@ double storage_getSurfArea(int j, double d) // Purpose: computes a storage node's surface area from its water depth. // { - double area; + double area = 0.0; int k = Node[j].subIndex; - int i = Storage[k].aCurve; - if ( i >= 0 ) - area = table_lookupEx(&Curve[i], d*UCF(LENGTH)); - else + int i; + + switch (Storage[k].shape) { - if ( Storage[k].aCoeff <= 0.0 ) area = Storage[k].aConst; - else if ( Storage[k].aExpon == 0.0 ) - area = Storage[k].aConst + Storage[k].aCoeff; - else area = Storage[k].aConst + Storage[k].aCoeff * - pow(d*UCF(LENGTH), Storage[k].aExpon); + // --- for tabular shape function, use table look-up + case TABULAR: + i = Storage[k].aCurve; + if (i >= 0) + area = table_lookupEx(&Curve[i], d*UCF(LENGTH)); + break; + + // --- for FUNCTIONAL relation, evaluate power function + case FUNCTIONAL: + area = Storage[k].a0 + Storage[k].a1 * + pow(d*UCF(LENGTH), Storage[k].a2); + break; + + // --- for conical & pyramidal shapes, evaluate quadratic + case CONICAL: + case PYRAMIDAL: + d *= UCF(LENGTH); + area = Storage[k].a0 + d * (Storage[k].a1 + d * Storage[k].a2); + break; + + default: return 0.0; } return area / UCF(LENGTH) / UCF(LENGTH); } @@ -1228,7 +1281,7 @@ int outfall_readParams(int j, int k, char* tok[], int ntoks) x[3] = -1.; // tidal curve x[4] = -1.; // tide series x[5] = 0.; // flap gate - x[6] = -1.; // route to subcatch//(5.1.008) + x[6] = -1.; // route to subcatch n = 4; if ( i >= FIXED_OUTFALL ) diff --git a/src/solver/objects.h b/src/solver/objects.h index 87716b86a..a74545a4b 100644 --- a/src/solver/objects.h +++ b/src/solver/objects.h @@ -2,16 +2,9 @@ // objects.h // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/19/14 (Build 5.1.000) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 08/05/15 (Build 5.1.010) -// 08/01/16 (Build 5.1.011) -// 05/10/18 (Build 5.1.013) -// 04/01/20 (Build 5.1.015) -// -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // M. Tryby (EPA) // R. Dickinson (CDM) // @@ -26,39 +19,41 @@ // In many structure definitions, a blank line separates the set of // input properties from the set of computed output properties. // +// Update History +// ============== // Build 5.1.007: // - Data structure for monthly adjustments of temperature, evaporation, // and rainfall added. // - User-supplied equation for deep GW flow added to subcatchment object. // - Exfiltration added to storage node object. // - Surcharge option added to weir object. -// // Build 5.1.008: // - Route to subcatchment option added to Outfall data structure. // - Hydraulic conductivity added to monthly adjustments data structure. // - Total LID drain flow and outfall runon added to Runoff Totals. // - Groundwater statistics object added. // - Maximum depth for reporting times added to node statistics object. -// // Build 5.1.010: // - Additional fields added to Weir object to support ROADWAY_WEIR type. // - New field added to Link object to record when its setting was changed. // - q1Old and q2Old of Link object restored. -// // Build 5.1.011: // - Description of oldFlow & newFlow for TGroundwater object modified. // - Weir shape parameter deprecated. // - Added definition of a hydraulic event time period (TEvent). -// // Build 5.1.013: // - New member 'averages' added to the TRptFlags structure. // - Adjustment patterns added to TSubcatch structure. // - Members impervRunoff and pervRunoff added to TSubcatchStats structure. // - Member cdCurve (weir coeff. curve) added to TWeir structure. -// // Build 5.1.015: // - Support added for multiple infiltration methods within a project. // - Support added for grouped freqency table of routing time steps. +// Build 5.2.0: +// - Support added for Street and Inlet objects. +// - Support added for conic & pyramidal storage functions. +// - Support added for reporting most frequent non-converging links. +// - Support added for tracking a gage's prior n-hour rainfall total. //----------------------------------------------------------------------------- #ifndef OBJECTS_H @@ -66,6 +61,7 @@ #include "mathexpr.h" +#include "inlet.h" #include "infil.h" #include "exfil.h" @@ -115,7 +111,7 @@ typedef struct typedef struct { char* ID; // raingage name - int dataSource; // data from time series or file + int dataSource; // data from time series or file int tSeries; // rainfall data time series index char fname[MAXFNAME+1]; // name of rainfall data file char staID[MAXMSG+1]; // station number @@ -137,9 +133,11 @@ typedef struct double rainfall; // current rainfall (in/hr or mm/hr) double nextRainfall; // next rainfall (in/hr or mm/hr) double reportRainfall; // rainfall value used for reported results + double pastRain[25]; // previous hourly rain volume (in or mm) + int pastInterval; // seconds since pastRain last updated int coGage; // index of gage with same rain timeseries int isUsed; // TRUE if gage used by any subcatchment - int isCurrent; // TRUE if gage's rainfall is current + int isCurrent; // TRUE if gage's rainfall is current } TGage; //------------------- @@ -147,7 +145,7 @@ typedef struct //------------------- typedef struct { - int dataSource; // data from time series or file + int dataSource; // data from time series or file int tSeries; // temperature data time series index DateTime fileStartDate; // starting date of data read from file double elev; // elev. of study area (ft) @@ -195,11 +193,11 @@ typedef struct int tSeries; // time series index double monthlyEvap[12]; // monthly evaporation values double panCoeff[12]; // monthly pan coeff. values - int recoveryPattern; // soil recovery factor pattern + int recoveryPattern; // soil recovery factor pattern int dryOnly; // true if evaporation only in dry periods //---------------------------- double rate; // current evaporation rate (ft/sec) - double recoveryFactor; // current soil recovery factor + double recoveryFactor; // current soil recovery factor } TEvap; //------------------- @@ -267,7 +265,7 @@ typedef struct //------------------------ typedef struct { - int aquifer; // index of associated gw aquifer + int aquifer; // index of associated gw aquifer int node; // index of node receiving gw flow double surfElev; // elevation of ground surface (ft) double a1, b1; // ground water outflow coeff. & exponent @@ -375,7 +373,7 @@ typedef struct int gage; // raingage index int outNode; // outlet node index int outSubcatch; // outlet subcatchment index - int infilModel; // infiltration method index //(5.1.015) + int infilModel; // infiltration method index int infil; // infiltration object index TSubarea subArea[3]; // sub-area data double width; // overland flow width (ft) @@ -389,14 +387,14 @@ typedef struct MathExpr* gwLatFlowExpr; // user-supplied lateral outflow expression MathExpr* gwDeepFlowExpr; // user-supplied deep percolation expression TSnowpack* snowpack; // associated snow pack data - int nPervPattern; // pervious N pattern index //(5.1.013) - int dStorePattern; // depression storage pattern index // - int infilPattern; // infiltration rate pattern index // + int nPervPattern; // pervious N pattern index + int dStorePattern; // depression storage pattern index + int infilPattern; // infiltration rate pattern index //----------------------------- double lidArea; // area devoted to LIDs (ft2) double rainfall; // current rainfall (ft/sec) double evapLoss; // current evap losses (ft/sec) - double infilLoss; // current infil losses (ft/sec) + double infilLoss; // current infil losses (ft/sec) double runon; // runon from other subcatchments (cfs) double oldRunoff; // previous runoff (cfs) double newRunoff; // current runoff (cfs) @@ -541,14 +539,15 @@ typedef struct typedef struct { double fEvap; // fraction of evaporation realized - double aConst; // surface area at zero height (ft2) - double aCoeff; // coeff. of area v. height curve - double aExpon; // exponent of area v. height curve + double a0; // surface area at zero height (ft2) + double a1; // coeff. of area v. height curve + double a2; // coeff. of area v. height curve int aCurve; // index of tabulated area v. height curve + int shape; // type of shape from StorageType enum TExfil* exfil; // ptr. to exfiltration object //----------------------------- double hrt; // hydraulic residence time (sec) - double evapLoss; // evaporation loss (ft3) + double evapLoss; // evaporation loss (ft3) double exfilLoss; // exfiltration loss (ft3) } TStorage; @@ -600,10 +599,10 @@ typedef struct double aFull; // area when full (ft2) double rFull; // hyd. radius when full (ft) double wMax; // width at widest point (ft) - double ywMax; // depth at max width (ft) + double ywMax; // depth at max width (ft) double sMax; // section factor at max. flow (ft^4/3) double aMax; // area at max. flow (ft2) - double lengthFactor; // floodplain / channel length + double lengthFactor; // floodplain / channel length //-------------------------------------- double roughness; // Manning's n double areaTbl[N_TRANSECT_TBL]; // table of area v. depth @@ -620,7 +619,7 @@ typedef struct { int curve; // index of shape curve int nTbl; // size of geometry tables - double aFull; // area when full + double aFull; // area when full double rFull; // hyd. radius when full double wMax; // max. width double sMax; // max. section factor @@ -651,6 +650,7 @@ typedef struct double cLossAvg; // avg. loss coeff. double seepRate; // seepage rate (ft/sec) int hasFlapGate; // true if flap gate present + TInlet *inlet; // pointer to a street inlet object //----------------------------- double oldFlow; // previous flow rate (cfs) double newFlow; // current flow rate (cfs) @@ -708,10 +708,10 @@ typedef struct { int type; // pump type int pumpCurve; // pump curve table index - double initSetting; // initial speed setting + double initSetting; // initial speed setting double yOn; // startup depth (ft) double yOff; // shutoff depth (ft) - double xMin; // minimum pt. on pump curve + double xMin; // minimum pt. on pump curve double xMax; // maximum pt. on pump curve } TPump; @@ -745,7 +745,7 @@ typedef struct int canSurcharge; // true if weir can surcharge double roadWidth; // width for ROADWAY weir int roadSurface; // road surface material - int cdCurve; // discharge coeff. curve index //(5.1.013) + int cdCurve; // discharge coeff. curve index //----------------------------- double cSurcharge; // orifice coeff. for surcharge double length; // equivalent length (ft) @@ -833,7 +833,7 @@ typedef struct char flowStats; // TRUE if routing link flow stats. reported char nodeStats; // TRUE if routing node depth stats. reported char controls; // TRUE if control actions reported - char averages; // TRUE if average results reported //(5.1.013) + char averages; // TRUE if average results reported int linesPerPage; // number of lines printed per page } TRptFlags; @@ -842,7 +842,7 @@ typedef struct //------------------------------- typedef struct { // All volume totals are in ft3. - double rainfall; // rainfall volume + double rainfall; // rainfall volume double evap; // evaporation loss double infil; // infiltration loss double runoff; // runoff volume @@ -910,7 +910,7 @@ typedef struct //----------------------- // SYSTEM-WIDE STATISTICS //----------------------- -#define TIMELEVELS 6 //(5.1.015) +#define TIMELEVELS 6 typedef struct { double minTimeStep; @@ -918,8 +918,8 @@ typedef struct double avgTimeStep; double avgStepCount; double steadyStateCount; - double timeStepIntervals[TIMELEVELS]; //(5.1.015) - int timeStepCounts[TIMELEVELS]; //(5.1.015) + double timeStepIntervals[TIMELEVELS]; + int timeStepCounts[TIMELEVELS]; } TSysStats; //-------------------- @@ -944,9 +944,9 @@ typedef struct double evap; double infil; double runoff; - double maxFlow; - double impervRunoff; //(5.1.013) - double pervRunoff; // + double maxFlow; + double impervRunoff; + double pervRunoff; } TSubcatchStats; //---------------- @@ -967,6 +967,7 @@ typedef struct double maxInflow; double maxOverflow; double maxPondedVol; + int nonConvergedCount; DateTime maxInflowDate; DateTime maxOverflowDate; } TNodeStats; @@ -992,11 +993,11 @@ typedef struct { double avgFlow; double maxFlow; - double* totalLoad; + double* totalLoad; int totalPeriods; } TOutfallStats; -//---------------- +//---------------- // PUMP STATISTICS //---------------- typedef struct @@ -1043,18 +1044,17 @@ typedef struct int objType; // either NODE or LINK int index; // node or link index double value; // value of node or link statistic -} TMaxStats; +} TMaxStats; //------------------ // REPORT FIELD INFO //------------------ -typedef struct +typedef struct { - char Name[80]; // name of reported variable + char Name[80]; // name of reported variable char Units[80]; // units of reported variable char Enabled; // TRUE if appears in report table int Precision; // number of decimal places when reported } TRptField; - #endif //OBJECTS_H diff --git a/src/solver/output.c b/src/solver/output.c index 47cd9df93..3408a7307 100644 --- a/src/solver/output.c +++ b/src/solver/output.c @@ -2,32 +2,28 @@ // output.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 03/19/15 (Build 5.1.008) -// 08/05/15 (Build 5.1.010) -// 05/10/18 (Build 5.1.013) -// 03/01/20 (Build 5.1.014) -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // // Binary output file access functions. // +// Update History +// ============== // Build 5.1.008: // - Possible divide by zero for reported system wide variables avoided. // - Updating of maximum node depth at reporting times added. -// // Build 5.1.010: // - Potentional ET added to list of system-wide variables saved to file. -// // Build 5.1.013: // - Names NsubcatchVars, NnodeVars & NlinkVars replaced with // NumSubcatchVars, NumNodeVars & NumLinkVars // - Support added for saving average node & link routing results to // binary file in each reporting period. -// // Build 5.1.014: // - Incorrect loop limit fixed in function output_saveAvgResults. -// +// Build 5.2.0: +// - Changed how time step averaged flow is computed. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -45,10 +41,10 @@ enum InputDataType {INPUT_TYPE_CODE, INPUT_AREA, INPUT_INVERT, INPUT_MAX_DEPTH, INPUT_OFFSET, INPUT_LENGTH}; -typedef struct //(5.1.013) -{ // - REAL4* xAvg; // -} TAvgResults; // +typedef struct +{ + REAL4* xAvg; +} TAvgResults; //----------------------------------------------------------------------------- // Shared variables @@ -66,9 +62,9 @@ static INT4 NumLinks; // number of links reported on static INT4 NumPolluts; // number of pollutants reported on static REAL4 SysResults[MAX_SYS_RESULTS]; // values of system output vars. -static TAvgResults* AvgLinkResults; //(5.1.013) -static TAvgResults* AvgNodeResults; // -static int Nsteps; // +static TAvgResults* AvgLinkResults; +static TAvgResults* AvgNodeResults; +static int Nsteps; //----------------------------------------------------------------------------- // Exportable variables (shared with report.c) @@ -87,11 +83,10 @@ static void output_saveSubcatchResults(double reportTime, FILE* file); static void output_saveNodeResults(double reportTime, FILE* file); static void output_saveLinkResults(double reportTime, FILE* file); -static int output_openAvgResults(void); //(5.1.013) -static void output_closeAvgResults(void); // -static void output_initAvgResults(void); // -static void output_saveAvgResults(FILE* file); // - +static int output_openAvgResults(void); +static void output_closeAvgResults(void); +static void output_initAvgResults(void); +static void output_saveAvgResults(FILE* file); //----------------------------------------------------------------------------- // External functions (declared in funcs.h) @@ -99,7 +94,7 @@ static void output_saveAvgResults(FILE* file); / // output_open (called by swmm_start in swmm5.c) // output_end (called by swmm_end in swmm5.c) // output_close (called by swmm_close in swmm5.c) -// output_updateAvgResults (called by swmm_step in swmm5.c) //(5.1.013) +// output_updateAvgResults (called by swmm_step in swmm5.c) // output_saveResults (called by swmm_step in swmm5.c) // output_checkFileSize (called by swmm_report) // output_readDateTime (called by routines in report.c) @@ -139,7 +134,7 @@ int output_open() // Total Inflow, Overflow and Quality NumNodeVars = MAX_NODE_RESULTS - 1 + NumPolluts; - // --- link results consist of Depth, Flow, Velocity, Volume, //(5.1.013) + // --- link results consist of Depth, Flow, Velocity, Volume, // Capacity and Quality NumLinkVars = MAX_LINK_RESULTS - 1 + NumPolluts; @@ -170,14 +165,14 @@ int output_open() return ErrorCode; } - // --- allocate memory to store average node & link results per period //(5.1.013) - AvgNodeResults = NULL; // - AvgLinkResults = NULL; // - if ( RptFlags.averages && !output_openAvgResults() ) // - { // - report_writeErrorMsg(ERR_MEMORY, ""); // - return ErrorCode; // - } // + // --- allocate memory to store average node & link results per period + AvgNodeResults = NULL; + AvgLinkResults = NULL; + if ( RptFlags.averages && !output_openAvgResults() ) + { + report_writeErrorMsg(ERR_MEMORY, ""); + return ErrorCode; + } fseek(Fout.file, 0, SEEK_SET); k = MAGICNUMBER; @@ -453,7 +448,7 @@ void output_saveResults(double reportTime) // { int i; - extern TRoutingTotals StepFlowTotals; // defined in massbal.c //(5.1.013) + extern TRoutingTotals StepFlowTotals; // defined in massbal.c DateTime reportDate = getDateTime(reportTime); REAL8 date; @@ -469,11 +464,11 @@ void output_saveResults(double reportTime) if (Nobjects[SUBCATCH] > 0) output_saveSubcatchResults(reportTime, Fout.file); - // --- save average routing results over reporting period if called for //(5.1.013) - if ( RptFlags.averages ) output_saveAvgResults(Fout.file); // + // --- save average routing results over reporting period if called for + if ( RptFlags.averages ) output_saveAvgResults(Fout.file); - // --- otherwise save interpolated point routing results //(5.1.013) - else // + // --- otherwise save interpolated point routing results + else { if (Nobjects[NODE] > 0) output_saveNodeResults(reportTime, Fout.file); @@ -537,7 +532,7 @@ void output_close() FREE(SubcatchResults); FREE(NodeResults); FREE(LinkResults); - output_closeAvgResults(); //(5.1.013) + output_closeAvgResults(); } //============================================================================= @@ -624,8 +619,6 @@ void output_saveSubcatchResults(double reportTime, FILE* file) //============================================================================= -//// This function was re-written for release 5.1.013. //// //(5.1.013) - void output_saveNodeResults(double reportTime, FILE* file) // // Input: reportTime = elapsed simulation time (millisec) @@ -756,8 +749,6 @@ void output_readLinkResults(int period, int index) fread(SysResults, sizeof(REAL4), MAX_SYS_RESULTS, Fout.file); } -//// The following functions were added for release 5.1.013. //// //(5.1.013) - //============================================================================= // Functions for saving average results within a reporting period to file. //============================================================================= @@ -869,16 +860,7 @@ void output_updateAvgResults() // --- add current results to average accumulation for (j = 0; j < NumLinkVars; j++) { - // --- accumulate flow so its sign (+/-) will equal that of most - // recent flow result - if ( j == LINK_FLOW ) - { - AvgLinkResults[k].xAvg[j] = - sign * (ABS(AvgLinkResults[k].xAvg[j]) + ABS(LinkResults[j])); - } - - // --- link capacity is another special case - else if (j == LINK_CAPACITY) + if (j == LINK_CAPACITY) { // --- accumulate capacity (fraction full) for conduits if ( Link[i].type == CONDUIT ) @@ -940,7 +922,7 @@ void output_saveAvgResults(FILE* file) } // --- add each link's volume to total system storage - for (i = 0; i < Nobjects[LINK]; i++) //(5.1.014) + for (i = 0; i < Nobjects[LINK]; i++) { SysResults[SYS_STORAGE] += (REAL4)(Link[i].newVolume * UCF(VOLUME)); } diff --git a/src/solver/project.c b/src/solver/project.c index 1c8ed78a0..d95bdd9f2 100644 --- a/src/solver/project.c +++ b/src/solver/project.c @@ -2,16 +2,8 @@ // project.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/19/14 (Build 5.1.000) -// 04/14/14 (Build 5.1.004) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 04/30/15 (Build 5.1.009) -// 08/01/16 (Build 5.1.011) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// 04/01/20 (Build 5.1.015) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Project management functions. @@ -23,41 +15,38 @@ // o initializing the internal state of all objects // o managing hash tables for identifying objects by ID name // +// Update History +// ============== // Build 5.1.004: // - Ignore RDII option added. -// // Build 5.1.007: // - Default monthly adjustments for climate variables included. // - User-supplied GW flow equations initialized to NULL. // - Storage node exfiltration object initialized to NULL. // - Freeing of memory used for storage node exfiltration included. -// // Build 5.1.008: // - Constants used for dynamic wave routing moved to dynwave.c. // - Input processing of minimum time step & number of // parallel threads for dynamic wave routing added. // - Default values of hyd. conductivity adjustments added. // - Freeing of memory used for outfall pollutant load added. -// // Build 5.1.009: // - Fixed bug in computing total duration introduced in 5.1.008. -// // Build 5.1.011: // - Memory management of hydraulic event dates array added. -// // Build 5.1.012: // - Minimum conduit slope option initialized to 0 (none). // - NO/YES no longer accepted as options for NORMAL_FLOW_LIMITED. -// // Build 5.1.013: // - omp_get_num_threads function protected against lack of compiler // support for OpenMP. // - Rain gage validation now performed after subcatchment validation. // - More robust parsing of MinSurfarea option provided. // - Support added for new RuleStep analysis option. -// // Build 5.1.015: // - Support added for multiple infiltration methods within a project. +// Build 5.2.0: +// - Support added for Streets and Inlets. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -65,13 +54,15 @@ #include #include -#if defined(_OPENMP) //(5.1.013) - #include // -#else // - int omp_get_num_threads(void) { return 1;} // -#endif // +// Protect against lack of compiler support for OpenMP +#if defined(_OPENMP) + #include +#else + int omp_get_num_threads(void) { return 1;} +#endif #include "headers.h" +#include "street.h" #include "lid.h" #include "hash.h" #include "mempool.h" @@ -215,7 +206,7 @@ void project_validate() if ( Nobjects[AQUIFER] == 0 ) IgnoreGwater = TRUE; for ( i=0; i 0 //(5.1.013) - if (k == RULE_STEP) // - { // - if (s < 0) return error_setInpError(ERR_NUMBER, s2); // - } // - else if ( s <= 0 ) return error_setInpError(ERR_NUMBER, s2); // + // --- RuleStep allowed to be 0 while other time steps must be > 0 + if (k == RULE_STEP) + { + if (s < 0) return error_setInpError(ERR_NUMBER, s2); + } + else if ( s <= 0 ) return error_setInpError(ERR_NUMBER, s2); switch ( k ) { case WET_STEP: WetStep = s; break; case DRY_STEP: DryStep = s; break; case REPORT_STEP: ReportStep = s; break; - case RULE_STEP: RuleStep = s; break; //(5.1.013) + case RULE_STEP: RuleStep = s; break; } break; @@ -672,10 +665,10 @@ int project_readOption(char* s1, char* s2) // --- minimum surface area (ft2 or sq. meters) associated with nodes // under dynamic wave flow routing case MIN_SURFAREA: - if (!getDouble(s2, &MinSurfArea)) //(5.1.013) - return error_setInpError(ERR_NUMBER, s2); //(5.1.013) - if (MinSurfArea < 0.0) //(5.1.013) - return error_setInpError(ERR_NUMBER, s2); //(5.1.013) + if (!getDouble(s2, &MinSurfArea)) + return error_setInpError(ERR_NUMBER, s2); + if (MinSurfArea < 0.0) + return error_setInpError(ERR_NUMBER, s2); break; // --- minimum conduit slope (%) @@ -720,7 +713,7 @@ int project_readOption(char* s1, char* s2) LatFlowTol /= 100.0; break; - // --- method used for surcharging in dynamic wave flow routing //(5.1.013) + // --- method used for surcharging in dynamic wave flow routing case SURCHARGE_METHOD: m = findmatch(s2, SurchargeWords); if (m < 0) return error_setInpError(ERR_KEYWORD, s2); @@ -763,10 +756,10 @@ void initPointers() Tseries = NULL; Transect = NULL; Shape = NULL; - Aquifer = NULL; - UnitHyd = NULL; - Snowmelt = NULL; - Event = NULL; + Aquifer = NULL; + UnitHyd = NULL; + Snowmelt = NULL; + Event = NULL; MemPoolAllocated = FALSE; } @@ -810,8 +803,8 @@ void setDefaults() FlowUnits = CFS; // CFS flow units InfilModel = HORTON; // Horton infiltration method RouteModel = KW; // Kin. wave flow routing method - SurchargeMethod = EXTRAN; // Use EXTRAN method for surcharging //(5.1.013) - CrownCutoff = 0.96; //(5.1.013) + SurchargeMethod = EXTRAN; // Use EXTRAN method for surcharging + CrownCutoff = 0.96; // Fractional pipe crown cutoff AllowPonding = FALSE; // No ponding at nodes InertDamping = SOME; // Partial inertial damping NormalFlowLtd = BOTH; // Default normal flow limitation @@ -1015,8 +1008,14 @@ void createObjects() ErrorCode = transect_create(Nobjects[TRANSECT]); if ( ErrorCode ) return; + // --- create street cross sections & inlet designs + ErrorCode = street_create(Nobjects[STREET]); + if ( ErrorCode ) return; + ErrorCode = inlet_create(Nobjects[INLET]); + if ( ErrorCode ) return; + // --- allocate memory for infiltration data - infil_create(Nobjects[SUBCATCH]); //(5.1.015) + infil_create(Nobjects[SUBCATCH]); // --- allocate memory for water quality state variables for (j = 0; j < Nobjects[SUBCATCH]; j++) @@ -1039,6 +1038,7 @@ void createObjects() } for (j = 0; j < Nobjects[LINK]; j++) { + Link[j].inlet = NULL; 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)); @@ -1185,6 +1185,7 @@ void deleteObjects() FREE(Link[j].oldQual); FREE(Link[j].newQual); FREE(Link[j].totalLoad); + // Any inlet assigned to Link[j].inlet is freed in inlet_delete(). } // --- free memory used for rainfall infiltration @@ -1223,6 +1224,10 @@ void deleteObjects() // --- delete cross section transects transect_delete(); + // --- delete street and inlet design objects + street_delete(); + inlet_delete(); + // --- delete control rules controls_delete(); diff --git a/src/solver/qualrout.c b/src/solver/qualrout.c index 3a23e4e72..b92833018 100644 --- a/src/solver/qualrout.c +++ b/src/solver/qualrout.c @@ -2,29 +2,26 @@ // qualrout.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 04/02/15 (Build 5.1.008) -// 04/30/15 (Build 5.1.009) -// 08/05/15 (Build 5.1.010) -// 04/01/20 (Build 5.1.015) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Water quality routing functions. // +// Update History +// ============== // Build 5.1.008: // - Pollutant mass lost to seepage flow added to mass balance totals. // - Pollutant concen. increased when evaporation occurs. -// // Build 5.1.009: // - Criterion for dry link/storage node changed to avoid concen. blowup. -// // Build 5.1.010: // - Entire module re-written to be more compact and easier to follow. // - Neglible depth limit replaced with a negligible volume limit. -// // Build 5.1.015: // - Fixed mass balance issue for empty storage nodes that flood. +// Build 5.2.0: +// - Support added for flow capture by inlet structures. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -194,7 +191,11 @@ void findLinkMassFlow(int i, double tStep) // --- identify index of downstream node j = Link[i].node2; if ( qLink < 0.0 ) j = Link[i].node1; + + // --- flow rate into downstream node (adjusted for inlet capture) qLink = fabs(qLink); + if (RouteModel != DW) + qLink -= inlet_capturedFlow(i); // --- examine each pollutant for (p = 0; p < Nobjects[POLLUT]; p++) @@ -446,8 +447,8 @@ void findStorageQual(int j, double tStep) wIn = Node[j].newQual[p]; c2 = getMixedQual(c1, v1, wIn, qIn, tStep); -// --- set concen. to zero if remaining volume & inflow is negligible //(5.1.015) - if (Node[j].newVolume <= ZeroVolume && qIn <= FLOW_TOL) //(5.1.015) + // --- set concen. to zero if remaining volume & inflow is negligible + if (Node[j].newVolume <= ZeroVolume && qIn <= FLOW_TOL) { massbal_addToFinalStorage(p, c2 * Node[j].newVolume); c2 = 0.0; diff --git a/src/solver/rain.c b/src/solver/rain.c index 873031e74..c7c0fa581 100644 --- a/src/solver/rain.c +++ b/src/solver/rain.c @@ -2,12 +2,8 @@ // rain.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 08/05/15 (Build 5.1.010) -// 08/22/16 (Build 5.1.011) -// 05/10/18 (Build 5.1.013) -// 03/01/20 (Build 5.1.014) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Places rainfall data from external files into a SWMM rainfall @@ -41,15 +37,14 @@ // Date/time for start of period (8-byte double) // Rain depth (inches) (4-byte float) // +// Update History +// ============== // Release 5.1.010: // - Modified error message for records out of sequence in std. format file. -// // Release 5.1.011: // - Can now read decimal rainfall values in newer NWS online format. -// // Release 5.1.013: // - Variable x properly initialized with float value in readNwsOnlineValue(). -// // Release 5.1.014: // - Fixed indexing bug in rainFileConflict() function. //----------------------------------------------------------------------------- @@ -874,7 +869,7 @@ int readNwsOnlineValue(char* s, long* v, char* flag) // { int n; - float x = 99.99f; //(5.1.013) + float x = 99.99f; // --- check for newer format of decimal inches if ( strchr(s, '.') ) diff --git a/src/solver/rdii.c b/src/solver/rdii.c index d94962489..701a27bd4 100644 --- a/src/solver/rdii.c +++ b/src/solver/rdii.c @@ -2,13 +2,9 @@ // rdii.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 04/04/14 (Build 5.1.003) -// 04/14/14 (Build 5.1.004) -// 09/15/14 (Build 5.1.007) -// 03/01/20 (Build 5.1.014) -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // R. Dickinson (CDM) // // RDII processing functions. @@ -16,10 +12,11 @@ // Note: RDII means rainfall dependent infiltration/inflow, // UH means unit hydrograph. // +// Update History +// ============== // Build 5.1.007: // - Ignore RDII option implemented. // - Rainfall climate adjustment implemented. -// // Build 5.1.014: // - Fixes bug related to isUsed property of a unit hydrograph's rain gage. //----------------------------------------------------------------------------- diff --git a/src/solver/report.c b/src/solver/report.c index 12d73fd7c..4218ea39f 100644 --- a/src/solver/report.c +++ b/src/solver/report.c @@ -2,52 +2,41 @@ // report.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/21/2014 (Build 5.1.001) -// 04/14/14 (Build 5.1.004) -// 09/15/14 (Build 5.1.007) -// 04/02/15 (Build 5.1.008) -// 08/01/16 (Build 5.1.011) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// 03/01/20 (Build 5.1.014) -// 05/18/20 (Build 5.1.015) -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // // Report writing functions. // +// Update History +// ============== // Build 5.1.004: // - Ignore RDII option reported. -// // Build 5.1.007: // - Total exfiltration loss reported. -// // Build 5.1.008: // - Number of threads option reported. // - LID drainage volume and outfall runon reported. // - "Internal Outflow" label changed to "Flooding Loss" in Flow Routing // Continuity table. // - Exfiltration loss added into Quality Routing Continuity table. -// // Build 5.1.011: // - Blank line added after writing project title. // - Text of error message saved to global variable ErrorMsg. // - Global variable Warnings incremented after warning message issued. -// // Build 5.1.012: // - System time step statistics adjusted for time in steady state. -// // Build 5.1.013: // - Parsing of AVERAGES report option added to report_readOptions(). // - Name of surcharge method reported in report_writeOptions(). // - Missing format specifier added to fprintf() in report_writeErrorCode. -// // Build 5.1.014: // - Fixed bug in confusing keywords with ID names in report_readOptions(). -// // Build 5.1.015: // - Fixes bug in summary statistics when Report Start date > Start Date. // - Support added for grouped freqency table of routing time steps. +// Build 5.2.0: +// - Support added for reporting most frequent non-converging links. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -91,7 +80,7 @@ static void report_Nodes(void); static void report_NodeHeader(char *id); static void report_Links(void); static void report_LinkHeader(char *id); -static void report_RouteStepFreq(TSysStats* sysStats); //(5.1.015) +static void report_RouteStepFreq(TSysStats* sysStats); //============================================================================= @@ -149,12 +138,12 @@ int report_readOptions(char* tok[], int ntoks) else return error_setInpError(ERR_KEYWORD, tok[1]); return 0; - case 8: // Averages //(5.1.013) - m = findmatch(tok[1], NoYesWords); // - if (m == YES) RptFlags.averages = TRUE; // - else if (m == NO) RptFlags.averages = FALSE; // - else return error_setInpError(ERR_KEYWORD, tok[1]); // - return 0; // + case 8: // Averages + m = findmatch(tok[1], NoYesWords); + if (m == YES) RptFlags.averages = TRUE; + else if (m == NO) RptFlags.averages = FALSE; + else return error_setInpError(ERR_KEYWORD, tok[1]); + return 0; default: return error_setInpError(ERR_KEYWORD, tok[1]); } @@ -334,9 +323,9 @@ void report_writeOptions() fprintf(Frpt.file, "\n Flow Routing Method ...... %s", RouteModelWords[RouteModel]); - if (RouteModel == DW) //(5.1.013) - fprintf(Frpt.file, "\n Surcharge Method ......... %s", //(5.1.013) - SurchargeWords[SurchargeMethod]); //(5.1.013) + if (RouteModel == DW) + fprintf(Frpt.file, "\n Surcharge Method ......... %s", + SurchargeWords[SurchargeMethod]); datetime_dateToStr(StartDate, str); fprintf(Frpt.file, "\n Starting Date ............ %s", str); @@ -1028,6 +1017,31 @@ void report_writeMaxFlowTurns(TMaxStats flowTurns[], int nMaxStats) //============================================================================= +void report_writeNonconvergedStats(TMaxStats maxNonconverged[], int nMaxStats) +{ + int i, j; + if (Nobjects[NODE] == 0 || RouteModel != DW) return; + WRITE(""); + WRITE("*********************************"); + WRITE("Most Frequent Nonconverging Nodes"); + WRITE("*********************************"); + if (nMaxStats <= 0 || maxNonconverged[0].index <= 0) + fprintf(Frpt.file, "\n Convergence obtained at all time steps."); + else + { + for (i = 0; i < nMaxStats; i++) + { + j = maxNonconverged[i].index; + if (j < 0 || maxNonconverged[i].value <= 0.0) continue; + fprintf(Frpt.file, "\n Node %s (%.2f%%)", + Node[j].ID, 100.0 * maxNonconverged[i].value / TotalStepCount); + } + } + WRITE(""); +} + +//============================================================================= + void report_writeSysStats(TSysStats* sysStats) // // Input: sysStats = simulation statistics for overall system @@ -1036,9 +1050,9 @@ void report_writeSysStats(TSysStats* sysStats) // { double x; - double eventStepCount; // Routing steps taken during reporting period //(5.1.015) + double eventStepCount; // Routing steps taken during reporting period - eventStepCount = ReportStepCount - sysStats->steadyStateCount; //(5.1.015) + eventStepCount = ReportStepCount - sysStats->steadyStateCount; if ( Nobjects[LINK] == 0 || TotalStepCount == 0 || eventStepCount == 0.0 ) return; WRITE(""); @@ -1064,15 +1078,14 @@ void report_writeSysStats(TSysStats* sysStats) "\n Percent Not Converging : %7.2f", 100.0 * (double)NonConvergeCount / eventStepCount); - // --- write grouped frequency table of variable routing time steps //(5.1.015) - if (RouteModel == DW && CourantFactor > 0.0) // - report_RouteStepFreq(sysStats); // + // --- write grouped frequency table of variable routing time steps + if (RouteModel == DW && CourantFactor > 0.0) + report_RouteStepFreq(sysStats); WRITE(""); } //============================================================================= -//// New function added to release 5.1.015. //// //(5.1.015) void report_RouteStepFreq(TSysStats* sysStats) // // Input: sysStats = simulation statistics for overall system @@ -1447,7 +1460,7 @@ void report_writeErrorCode() if ( (ErrorCode >= ERR_MEMORY && ErrorCode <= ERR_TIMESTEP) || (ErrorCode >= ERR_FILE_NAME && ErrorCode <= ERR_OUT_FILE) || (ErrorCode == ERR_SYSTEM) ) - fprintf(Frpt.file, "%s", error_getMsg(ErrorCode)); //(5.1.013) + fprintf(Frpt.file, "%s", error_getMsg(ErrorCode)); } } diff --git a/src/solver/roadway.c b/src/solver/roadway.c index 0237ea96d..ca04faf5c 100644 --- a/src/solver/roadway.c +++ b/src/solver/roadway.c @@ -2,20 +2,22 @@ // roadway.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 08/05/15 (Build 5.1.010) -// 03/14/17 (Build 5.1.012) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Roadway Weir module for SWMM5 // // Computes flow overtopping a roadway (with a ROADWAY_WEIR object) using // the FWHA HDS-5 methodology. +// // Typically used in conjuction with a culvert crossing where the culvert // conduit is placed at zero offset at the upstream node and the Roadway // weir has the same upstream node but with an offset equal to the height // of the roadway. // +// Update History +// ============== // Build 5.1.012: // - Entries in discharge coeff. table for gravel roadways corrected. //----------------------------------------------------------------------------- diff --git a/src/solver/routing.c b/src/solver/routing.c index 1c359e89a..739d08522 100644 --- a/src/solver/routing.c +++ b/src/solver/routing.c @@ -2,48 +2,40 @@ // routing.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/19/14 (Build 5.1.000) -// 09/15/14 (Build 5.1.007) -// 04/02/15 (Build 5.1.008) -// 08/05/15 (Build 5.1.010) -// 08/01/16 (Build 5.1.011) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // M. Tryby (EPA) // // Conveyance system routing functions. // +// Update History +// ============== // Build 5.1.007: // - Nodal evap/seepage losses computed using conditions at start of time step. // - DWF pollutant concentrations ignored if DWF is negative. // - Separate mass balance accounting made for storage evap. & seepage. // - Nodal mass balance accounting for negative lateral inflows corrected. -// // Build 5.1.008: // - Initialization of flow and quality routing systems moved here from swmm5.c. // - Lateral inflows now evaluated at start (not end) of time step. // - Flows from LID drains included in lateral inflows. // - Conduit evap/seepage losses multiplied by number of barrels before // being added into mass balances. -// // Build 5.1.010: // - Time when a link's setting is changed is recorded. -// // Build 5.1.011: // - Support added for limiting flow routing to specific events. -// // Build 5.1.012: // - routing_execute() was re-written so that Routing Events and // Skip Steady Flow options work together correctly. -// // Build 5.1.013: // - Support added for evaluating controls rules at RuleStep time interval. // - Back flow through Outfall nodes now treated as External Inflows for // mass balance purposes. // - Global infiltration factor for storage seepage set in routing_execute. -// +// Build 5.2.0: +// - Adds support for flow capture & diversion through inlet drains. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -59,7 +51,7 @@ static int* SortedLinks; static int NextEvent; static int BetweenEvents; -static double NewRuleTime; //(5.1.013) +static double NewRuleTime; //----------------------------------------------------------------------------- // External functions (declared in funcs.h) @@ -122,7 +114,7 @@ int routing_open() if ( NumEvents > 0 ) sortEvents(); NextEvent = 0; BetweenEvents = (NumEvents > 0); - NewRuleTime = 0.0; //(5.1.013) + NewRuleTime = 0.0; return ErrorCode; } @@ -146,8 +138,6 @@ void routing_close(int routingModel) //============================================================================= -//// This function was modified for release 5.1.013. //// //(5.1.013) - double routing_getRoutingStep(int routingModel, double fixedStep) // // Input: routingModel = routing method code @@ -223,15 +213,15 @@ void routing_execute(int routingModel, double routingStep) // --- control rules (e.g., pump on/off depth limits) for (j=0; j= Event[NextEvent].start && BetweenEvents == TRUE ) { - BetweenEvents = FALSE; + BetweenEvents = FALSE; } } @@ -301,7 +291,7 @@ void routing_execute(int routingModel, double routingStep) Node[j].losses = node_getLosses(j, routingStep); } - // --- add lateral inflows and evap/seepage losses at nodes + // --- add lateral inflows at nodes addExternalInflows(currentDate); addDryWeatherInflows(currentDate); addWetWeatherInflows(OldRoutingTime); @@ -325,17 +315,24 @@ void routing_execute(int routingModel, double routingStep) { // --- replace old hydraulic state values with current ones for (j = 0; j < Nobjects[LINK]; j++) link_setOldHydState(j); + for (j = 0; j < Nobjects[NODE]; j++) node_setOldHydState(j); + + // --- apply inlet flows to node lateral flows + inlet_findInletFlows(routingStep); + + // --- initialize node external inflows & outflows for (j = 0; j < Nobjects[NODE]; j++) - { - node_setOldHydState(j); node_initInflow(j, routingStep); - } // --- route flow through the drainage network if ( Nobjects[LINK] > 0 ) { stepCount = flowrout_execute(SortedLinks, routingModel, routingStep); } + massbal_updateNodeTotals(routingStep); + + // --- convert any inlet overflow to backflow + inlet_convertOverflows(); } // --- route quality through the drainage network @@ -350,7 +347,7 @@ void routing_execute(int routingModel, double routingStep) removeOutflows(routingStep); } else inSteadyState = TRUE; - + // --- update continuity with new totals // applied over 1/2 of routing step massbal_updateRoutingTotals(routingStep/2.); @@ -397,7 +394,10 @@ void addExternalInflows(DateTime currentDate) // --- add flow inflow to node's lateral inflow Node[j].newLatFlow += q; - massbal_addInflowFlow(EXTERNAL_INFLOW, q); + if (q >= 0.0) + massbal_addInflowFlow(EXTERNAL_INFLOW, q); + else + massbal_addOutflowFlow(-q, FALSE); // --- add on any inflow (i.e., reverse flow) through an outfall if ( Node[j].type == OUTFALL && Node[j].oldNetInflow < 0.0 ) @@ -830,7 +830,7 @@ void removeOutflows(double tStep) // --- update mass balance with flow and mass leaving the system // through outfalls and flooded interior nodes q = node_getSystemOutflow(i, &isFlooded); - if ( q > 0.0 ) //(5.1.013) + if ( q > 0.0 ) { massbal_addOutflowFlow(q, isFlooded); for ( p = 0; p < Nobjects[POLLUT]; p++ ) @@ -839,7 +839,7 @@ void removeOutflows(double tStep) massbal_addOutflowQual(p, w, isFlooded); } } - else massbal_addInflowFlow(EXTERNAL_INFLOW, -q); //(5.1.013) + else massbal_addInflowFlow(EXTERNAL_INFLOW, -q); // --- update mass balance with mass leaving system through negative // lateral inflows (lateral flow was previously accounted for) @@ -852,7 +852,6 @@ void removeOutflows(double tStep) massbal_addOutflowQual(p, w, FALSE); } } - } } diff --git a/src/solver/runoff.c b/src/solver/runoff.c index a7a6f5d83..b56eb23cb 100644 --- a/src/solver/runoff.c +++ b/src/solver/runoff.c @@ -2,37 +2,32 @@ // runoff.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 08/01/16 (Build 5.1.011) -// 03/14/17 (Build 5.1.012) -// 03/01/20 (Build 5.1.014) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // M. Tryby // // Runoff analysis functions. // +// Update History +// ============== // Build 5.1.007: // - Climate file now opened in climate.c module. -// // Build 5.1.008: // - Memory for runoff pollutant load now allocated and freed in this module. // - Runoff time step chosen so that simulation does not exceed total duration. // - State of LIDs considered when choosing wet or dry time step. // - More checks added to skip over subcatchments with zero area. // - Support added for sending outfall node discharge onto a subcatchment. -// // Build 5.1.011: // - Runoff wet time step kept aligned with reporting times. // - Prior runoff time step used to convert returned outfall volume to flow. -// // Build 5.1.012: // - Runoff wet time step no longer kept aligned with reporting times. -// // Build 5.1.014: // - Fixed street sweeping bug. +// Build 5.2.0: +// - Support added for saving rainfall amounts in previous 24 hours. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -235,6 +230,10 @@ void runoff_execute() NewRunoffTime = TotalDuration; } + // --- update past 24 hour rain totals + for (j = 0; j < Nobjects[GAGE]; j++) + gage_updatePastRain(j, (int)runoffStep); + // --- update old state of each subcatchment, for (j = 0; j < Nobjects[SUBCATCH]; j++) subcatch_setOldState(j); diff --git a/src/solver/shape.c b/src/solver/shape.c index ebf38ad85..9e3bbef0c 100644 --- a/src/solver/shape.c +++ b/src/solver/shape.c @@ -2,8 +2,8 @@ // shape.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Geometry functions for custom cross-section shapes. diff --git a/src/solver/snow.c b/src/solver/snow.c index e188f8402..952f77529 100644 --- a/src/solver/snow.c +++ b/src/solver/snow.c @@ -2,19 +2,19 @@ // snow.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 03/19/15 (Build 5.1.008) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Models snow melt processes. // +// Update History +// ============== // Build 5.1.008: // - Adjustment of snowmelt and subcatchment's net precipitation for area // covered by snow was corrected. // - Area covered by snow now included in calculation of rate that liquid // water leaves a snowpack. -// //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE diff --git a/src/solver/stats.c b/src/solver/stats.c index 6eade9377..a5e7cc8f7 100644 --- a/src/solver/stats.c +++ b/src/solver/stats.c @@ -2,46 +2,39 @@ // stats.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 08/01/16 (Build 5.1.011) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// 04/01/20 (Build 5.1.015) -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // R. Dickinson (CDM) // // Simulation statistics functions. // +// Update History +// ============== // Build 5.1.007: // - Exfiltration losses added to storage node statistics. -// // Build 5.1.008: // - Support for updating groundwater statistics added. // - Support for updating maximum reported nodal depths added. // - OpenMP parallelization applied to updating node and link flow statistics. // - Updating of time that conduit is upstrm/dnstrm full was modified. -// // Build 5.1.011: // - Surcharging is now evaluated only under dynamic wave flow routing and // storage nodes cannot be classified as surcharged. -// // Build 5.1.012: // - Time step statistics now evaluated only in non-steady state periods. // - Check for full conduit flow now accounts for number of barrels. -// // Build 5.1.013: // - Include omp.h protected against lack of compiler support for OpenMP. // - Statistics on impervious and pervious runoff totals added. // - Storage nodes with a non-zero surcharge depth (e.g. enclosed tanks) // can now be classified as being surcharged. -// // Build 5.1.015: // - Fixes bug in summary statistics when Report Start date > Start Date. // - Fixes failure to initialize all subcatchment groundwater statistics. // - Support added for grouped freqency table of routing time steps. +// Build 5.2.0: +// - Support added for reporting most frequent non-converging links. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -50,7 +43,7 @@ #include #include "headers.h" #include "swmm5.h" -#if defined(_OPENMP) //(5.1.013) +#if defined(_OPENMP) #include #endif @@ -62,6 +55,7 @@ static TSysStats SysStats; static TMaxStats MaxMassBalErrs[MAX_STATS]; static TMaxStats MaxCourantCrit[MAX_STATS]; static TMaxStats MaxFlowTurns[MAX_STATS]; +static TMaxStats MaxNonConverged[MAX_STATS]; static double SysOutfallFlow; //----------------------------------------------------------------------------- @@ -93,6 +87,7 @@ extern double* NodeOutflow; // defined in massbal.c // stats_updateFlowStats (called from routing_execute) // stats_updateCriticalTimeCount (called from getVariableStep in dynwave.c) // stats_updateMaxNodeDepth (called from output_saveNodeResults) +// stats_updateNonconvergedCount (called from updateConvergenceStats in dynwave.c) //----------------------------------------------------------------------------- // Local functions @@ -112,9 +107,9 @@ int stats_open() // { int j, k; - double timeStepDelta; //(5.1.015) - double logMaxTimeStep; //(5.1.015) - double logMinTimeStep; //(5.1.015) + double timeStepDelta; + double logMaxTimeStep; + double logMinTimeStep; // --- set all pointers to NULL NodeStats = NULL; @@ -142,8 +137,8 @@ int stats_open() SubcatchStats[j].infil = 0.0; SubcatchStats[j].runoff = 0.0; SubcatchStats[j].maxFlow = 0.0; - SubcatchStats[j].impervRunoff = 0.0; //(5.1.013) - SubcatchStats[j].pervRunoff = 0.0; // + SubcatchStats[j].impervRunoff = 0.0; + SubcatchStats[j].pervRunoff = 0.0; } for (j=0; jstats.deepFlow = 0.0; Subcatch[j].groundwater->stats.evap = 0.0; Subcatch[j].groundwater->stats.maxFlow = 0.0; - Subcatch[j].groundwater->stats.finalUpperMoist = 0.0; //(5.1.015) - Subcatch[j].groundwater->stats.finalWaterTable = 0.0; // + Subcatch[j].groundwater->stats.finalUpperMoist = 0.0; + Subcatch[j].groundwater->stats.finalWaterTable = 0.0; } } @@ -189,6 +184,7 @@ int stats_open() NodeStats[j].maxInflow = 0.0; NodeStats[j].maxOverflow = 0.0; NodeStats[j].maxPondedVol = 0.0; + NodeStats[j].nonConvergedCount = 0; NodeStats[j].maxInflowDate = StartDateTime; NodeStats[j].maxOverflowDate = StartDateTime; } @@ -267,10 +263,10 @@ int stats_open() } // --- allocate memory & initialize pumping statistics - if ( Nlinks[PUMP] > 0 ) - { + if ( Nlinks[PUMP] > 0 ) + { PumpStats = (TPumpStats *) calloc(Nlinks[PUMP], sizeof(TPumpStats)); - if ( !PumpStats ) + if ( !PumpStats ) { report_writeErrorMsg(ERR_MEMORY, ""); return ErrorCode; @@ -280,14 +276,14 @@ int stats_open() PumpStats[j].utilized = 0.0; PumpStats[j].minFlow = 0.0; PumpStats[j].avgFlow = 0.0; - PumpStats[j].maxFlow = 0.0; + PumpStats[j].maxFlow = 0.0; PumpStats[j].volume = 0.0; PumpStats[j].energy = 0.0; PumpStats[j].startUps = 0; - PumpStats[j].offCurveLow = 0.0; + PumpStats[j].offCurveLow = 0.0; PumpStats[j].offCurveHigh = 0.0; - } - } + } + } // --- initialize system stats MaxRunoffFlow = 0.0; @@ -298,19 +294,19 @@ int stats_open() SysStats.avgStepCount = 0.0; SysStats.steadyStateCount = 0.0; - // --- divide range between min and max routing time steps into //(5.1.015) - // equal intervals using a logarithmic scale // - logMaxTimeStep = log10(RouteStep); // - logMinTimeStep = log10(MinRouteStep); // - timeStepDelta = (logMaxTimeStep - logMinTimeStep) / (TIMELEVELS-1); // - SysStats.timeStepIntervals[0] = RouteStep; // - for (j = 1; j < TIMELEVELS; j++) // - { // - SysStats.timeStepIntervals[j] = // - pow(10., logMaxTimeStep - j * timeStepDelta); // - SysStats.timeStepCounts[j] = 0; // - } // - SysStats.timeStepIntervals[TIMELEVELS - 1] = MinRouteStep; // + // --- divide range between min and max routing time steps into + // equal intervals using a logarithmic scale + logMaxTimeStep = log10(RouteStep); + logMinTimeStep = log10(MinRouteStep); + timeStepDelta = (logMaxTimeStep - logMinTimeStep) / (TIMELEVELS-1); + SysStats.timeStepIntervals[0] = RouteStep; + for (j = 1; j < TIMELEVELS; j++) + { + SysStats.timeStepIntervals[j] = + pow(10., logMaxTimeStep - j * timeStepDelta); + SysStats.timeStepCounts[j] = 0; + } + SysStats.timeStepIntervals[TIMELEVELS - 1] = MinRouteStep; return 0; } @@ -319,7 +315,7 @@ int stats_open() void stats_close() // // Input: none -// Output: +// Output: // Purpose: closes the simulation statistics system. // { @@ -328,7 +324,7 @@ void stats_close() FREE(SubcatchStats); FREE(NodeStats); FREE(LinkStats); - FREE(StorageStats); + FREE(StorageStats); if ( OutfallStats ) { for ( j=0; j= SysStats.timeStepIntervals[j]) // - { // - SysStats.timeStepCounts[j]++; // - break; // - } // + // --- locate interval that logged time step falls in + // and update its count + for (j = 1; j < TIMELEVELS; j++) + if (tStep >= SysStats.timeStepIntervals[j]) + { + SysStats.timeStepCounts[j]++; + break; + } } SysStats.avgTimeStep += tStep; SysStats.maxTimeStep = MAX(SysStats.maxTimeStep, tStep); @@ -503,7 +500,7 @@ void stats_updateFlowStats(double tStep, DateTime aDate, int stepCount, } //============================================================================= - + void stats_updateCriticalTimeCount(int node, int link) // // Input: node = node index @@ -518,6 +515,13 @@ void stats_updateCriticalTimeCount(int node, int link) //============================================================================= +void stats_updateConvergenceStats(int node, int converged) +{ + if (converged == FALSE) NodeStats[node].nonConvergedCount++; +} + +//============================================================================= + void stats_updateNodeStats(int j, double tStep, DateTime aDate) // // Input: j = node index @@ -535,12 +539,12 @@ void stats_updateNodeStats(int j, double tStep, DateTime aDate) // --- update depth statistics NodeStats[j].avgDepth += newDepth; - if ( (newDepth - NodeStats[j].maxDepth) > ZERO ) + if ( newDepth > NodeStats[j].maxDepth ) { NodeStats[j].maxDepth = newDepth; NodeStats[j].maxDepthDate = aDate; } - + // --- update flooding, ponding, and surcharge statistics if ( Node[j].type != OUTFALL ) { @@ -553,11 +557,11 @@ void stats_updateNodeStats(int j, double tStep, DateTime aDate) (newVolume - Node[j].fullVolume)); } - // --- for dynamic wave routing, classify a node as //(5.1.013) + // --- for dynamic wave routing, classify a node as // surcharged if its water level exceeds its crown elev. - if (RouteModel == DW) //(5.1.013) + if (RouteModel == DW) { - if ((Node[j].type != STORAGE || Node[j].surDepth > 0.0) && //(5.1.013) + if ((Node[j].type != STORAGE || Node[j].surDepth > 0.0) && newDepth + Node[j].invertElev + FUDGE >= Node[j].crownElev) { NodeStats[j].timeSurcharged += tStep; @@ -570,13 +574,13 @@ void stats_updateNodeStats(int j, double tStep, DateTime aDate) { k = Node[j].subIndex; StorageStats[k].avgVol += newVolume; - StorageStats[k].evapLosses += - Storage[Node[j].subIndex].evapLoss; + StorageStats[k].evapLosses += + Storage[Node[j].subIndex].evapLoss; StorageStats[k].exfilLosses += - Storage[Node[j].subIndex].exfilLoss; + Storage[Node[j].subIndex].exfilLoss; newVolume = MIN(newVolume, Node[j].fullVolume); - if ( (newVolume - StorageStats[k].maxVol) > ZERO ) + if ( newVolume > StorageStats[k].maxVol ) { StorageStats[k].maxVol = newVolume; StorageStats[k].maxVolDate = aDate; @@ -585,7 +589,7 @@ void stats_updateNodeStats(int j, double tStep, DateTime aDate) } // --- update outfall statistics - if ( Node[j].type == OUTFALL ) + if ( Node[j].type == OUTFALL ) { k = Node[j].subIndex; if ( Node[j].inflow >= MIN_RUNOFF_FLOW ) @@ -596,25 +600,25 @@ void stats_updateNodeStats(int j, double tStep, DateTime aDate) } for (p=0; p fabs(NodeStats[j].maxLatFlow) ) NodeStats[j].maxLatFlow = Node[j].newLatFlow; - if ( (Node[j].inflow - NodeStats[j].maxInflow) > ZERO ) + if ( Node[j].inflow > NodeStats[j].maxInflow ) { NodeStats[j].maxInflow = Node[j].inflow; NodeStats[j].maxInflowDate = aDate; } // --- update overflow statistics - if ( (Node[j].overflow - NodeStats[j].maxOverflow) > ZERO ) + if ( Node[j].overflow > NodeStats[j].maxOverflow ) { NodeStats[j].maxOverflow = Node[j].overflow; NodeStats[j].maxOverflowDate = aDate; @@ -639,7 +643,7 @@ void stats_updateLinkStats(int j, double tStep, DateTime aDate) // --- update max. flow dq = Link[j].newFlow - Link[j].oldFlow; q = fabs(Link[j].newFlow); - if ( (q - LinkStats[j].maxFlow) > ZERO ) + if ( q > LinkStats[j].maxFlow ) { LinkStats[j].maxFlow = q; LinkStats[j].maxFlowDate = aDate; @@ -653,7 +657,7 @@ void stats_updateLinkStats(int j, double tStep, DateTime aDate) } // --- update max. depth - if ( (Link[j].newDepth - LinkStats[j].maxDepth) > ZERO ) + if ( Link[j].newDepth > LinkStats[j].maxDepth ) { LinkStats[j].maxDepth = Link[j].newDepth; } @@ -686,10 +690,10 @@ void stats_updateLinkStats(int j, double tStep, DateTime aDate) else if ( Link[j].type == CONDUIT ) { - // --- update time under normal flow & inlet control + // --- update time under normal flow & inlet control if ( Link[j].normalFlow ) LinkStats[j].timeNormalFlow += tStep; if ( Link[j].inletControl ) LinkStats[j].timeInletControl += tStep; - + // --- update flow classification distribution k = Link[j].flowClass; if ( k >= 0 && k < MAX_FLOW_CLASSES ) @@ -700,7 +704,7 @@ void stats_updateLinkStats(int j, double tStep, DateTime aDate) // --- update time conduit is full k = Link[j].subIndex; if ( q >= Link[j].qFull * (double)Conduit[k].barrels ) - LinkStats[j].timeFullFlow += tStep; + LinkStats[j].timeFullFlow += tStep; if ( Conduit[k].capacityLimited ) LinkStats[j].timeCapacityLimited += tStep; @@ -738,7 +742,7 @@ void stats_findMaxStats() { int j; double x; - double stepCount = ReportStepCount - SysStats.steadyStateCount; //(5.1.015) + double stepCount = ReportStepCount - SysStats.steadyStateCount; // --- initialize max. stats arrays for (j=0; j 2 ) //(5.1.015) + // --- find links with most flow turns + if ( stepCount > 2 ) { for (j=0; j Start Date. -// - Insures that flow values listed in tables are separated by a space. -// - Adds a '-' entry to blank columns in the Link Flow Summary table. +// - Fixes bug in summary statistics when Report Start date > Start Date. +// Build 5.2.0: +// - Adds a new Street & Inlet Summary table. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -89,14 +81,14 @@ void statsrpt_writeReport() // { // --- set number of decimal places for reporting flow values - if ( FlowUnits == MGD || FlowUnits == CMS ) strcpy(FlowFmt, " %8.3f"); //(5.1.015) - else strcpy(FlowFmt, " %8.2f"); //(5.1.015) + if ( FlowUnits == MGD || FlowUnits == CMS ) strcpy(FlowFmt, "%9.3f"); + else strcpy(FlowFmt, "%9.2f"); // --- volume conversion factor from ft3 to Mgal or Mliters if (UnitSystem == US) Vcf = 7.48 / 1.0e6; else Vcf = 28.317 / 1.0e6; - // --- report summary results for subcatchment runoff + // --- report summary results for subcatchment runoff if ( Nobjects[SUBCATCH] > 0 ) { if ( !IgnoreRainfall || @@ -119,6 +111,7 @@ void statsrpt_writeReport() writeNodeFlooding(); writeStorageVolumes(); writeOutfallLoads(); + inlet_writeStatsReport(); writeLinkFlows(); writeFlowClass(); writeLinkSurcharge(); @@ -165,16 +158,16 @@ void writeSubcatchRunoff() fprintf(Frpt.file, "\n %-20s", Subcatch[j].ID); x = SubcatchStats[j].precip * UCF(RAINDEPTH); fprintf(Frpt.file, " %10.2f", x/a); - x = SubcatchStats[j].runon * UCF(RAINDEPTH); + x = SubcatchStats[j].runon * UCF(RAINDEPTH); fprintf(Frpt.file, " %10.2f", x/a); x = SubcatchStats[j].evap * UCF(RAINDEPTH); fprintf(Frpt.file, " %10.2f", x/a); - x = SubcatchStats[j].infil * UCF(RAINDEPTH); + x = SubcatchStats[j].infil * UCF(RAINDEPTH); + fprintf(Frpt.file, " %10.2f", x/a); + x = SubcatchStats[j].impervRunoff * UCF(RAINDEPTH); + fprintf(Frpt.file, " %10.2f", x/a); + x = SubcatchStats[j].pervRunoff * UCF(RAINDEPTH); fprintf(Frpt.file, " %10.2f", x/a); - x = SubcatchStats[j].impervRunoff * UCF(RAINDEPTH); //(5.1.013) - fprintf(Frpt.file, " %10.2f", x/a); // - x = SubcatchStats[j].pervRunoff * UCF(RAINDEPTH); // - fprintf(Frpt.file, " %10.2f", x/a); // x = SubcatchStats[j].runoff * UCF(RAINDEPTH); fprintf(Frpt.file, " %10.2f", x/a); x = SubcatchStats[j].runoff * Vcf; @@ -249,7 +242,7 @@ void writeSubcatchLoads() { int i, j, p; double x; - double* totals; + double* totals; char units[15]; char subcatchLine[] = "--------------------"; char pollutLine[] = "--------------"; @@ -258,7 +251,7 @@ void writeSubcatchLoads() totals = (double *) calloc(Nobjects[POLLUT], sizeof(double)); if ( totals ) { - // --- print the table headings + // --- print the table headings WRITE(""); WRITE("****************************"); WRITE("Subcatchment Washoff Summary"); @@ -289,7 +282,7 @@ void writeSubcatchLoads() x = Subcatch[j].totalLoad[p]; totals[p] += x; if ( Pollut[p].units == COUNT ) x = LOG10(x); - fprintf(Frpt.file, "%14.3f", x); + fprintf(Frpt.file, "%14.3f", x); } } @@ -301,7 +294,7 @@ void writeSubcatchLoads() { x = totals[p]; if ( Pollut[p].units == COUNT ) x = LOG10(x); - fprintf(Frpt.file, "%14.3f", x); + fprintf(Frpt.file, "%14.3f", x); } free(totals); WRITE(""); @@ -343,7 +336,7 @@ void writeNodeDepths() fprintf(Frpt.file, " %-9s ", NodeTypeWords[Node[j].type]); getElapsedTime(NodeStats[j].maxDepthDate, &days, &hrs, &mins); fprintf(Frpt.file, "%7.2f %7.2f %7.2f %4d %02d:%02d %10.2f", - NodeStats[j].avgDepth / ReportStepCount * UCF(LENGTH), //(5.1.015) + NodeStats[j].avgDepth / ReportStepCount * UCF(LENGTH), NodeStats[j].maxDepth * UCF(LENGTH), (NodeStats[j].maxDepth + Node[j].invertElev) * UCF(LENGTH), days, hrs, mins, NodeStats[j].maxRptDepth); @@ -396,7 +389,7 @@ void writeNodeFlows() VolUnitsWords2[UnitSystem]); else fprintf(Frpt.file, "%12.3f", (NodeInflow[j]-NodeOutflow[j]) / - NodeOutflow[j]*100.); + NodeOutflow[j]*100.); } WRITE(""); } @@ -422,7 +415,7 @@ void writeNodeSurcharge() if ( n == 0 ) { WRITE("Surcharging occurs when water rises above the top of the highest conduit."); - fprintf(Frpt.file, + fprintf(Frpt.file, "\n ---------------------------------------------------------------------" "\n Max. Height Min. Depth" "\n Hours Above Crown Below Rim"); @@ -470,14 +463,14 @@ void writeNodeFlooding() if ( n == 0 ) { WRITE("Flooding refers to all water that overflows a node, whether it ponds or not."); - fprintf(Frpt.file, + fprintf(Frpt.file, "\n --------------------------------------------------------------------------" "\n Total Maximum" "\n Maximum Time of Max Flood Ponded" "\n Hours Rate Occurrence Volume"); if ( RouteModel == DW ) fprintf(Frpt.file, " Depth"); else fprintf(Frpt.file, " Volume"); - fprintf(Frpt.file, + fprintf(Frpt.file, "\n Node Flooded %3s days hr:min %8s", FlowUnitWords[FlowUnits], VolUnitsWords[UnitSystem]); if ( RouteModel == DW ) fprintf(Frpt.file, " %6s", @@ -544,7 +537,7 @@ void writeStorageVolumes() if ( Node[j].type != STORAGE ) continue; k = Node[j].subIndex; fprintf(Frpt.file, "\n %-20s", Node[j].ID); - avgVol = StorageStats[k].avgVol / (double)ReportStepCount; //(5.1.015) + avgVol = StorageStats[k].avgVol / (double)ReportStepCount; maxVol = StorageStats[k].maxVol; pctMaxVol = 0.0; pctAvgVol = 0.0; @@ -608,7 +601,7 @@ void writeOutfallLoads() // --- print table column headers fprintf(Frpt.file, - "\n -----------------------------------------------------------"); + "\n -----------------------------------------------------------"); for (p = 0; p < Nobjects[POLLUT]; p++) fprintf(Frpt.file, "--------------"); fprintf(Frpt.file, "\n Flow Avg Max Total"); @@ -640,7 +633,7 @@ void writeOutfallLoads() // --- print node ID, flow freq., avg. flow, max. flow & flow vol. fprintf(Frpt.file, "\n %-20s", Node[j].ID); - x = 100.*flowCount/(double)ReportStepCount; //(5.1.015) + x = 100.*flowCount/(double)ReportStepCount; fprintf(Frpt.file, "%7.2f", x); freqSum += x; if ( flowCount > 0 ) @@ -662,14 +655,14 @@ void writeOutfallLoads() x = OutfallStats[k].totalLoad[p] * LperFT3 * Pollut[p].mcf; totals[p] += x; if ( Pollut[p].units == COUNT ) x = LOG10(x); - fprintf(Frpt.file, "%14.3f", x); + fprintf(Frpt.file, "%14.3f", x); } } // --- print total outfall loads outfallCount = Nnodes[OUTFALL]; fprintf(Frpt.file, - "\n -----------------------------------------------------------"); + "\n -----------------------------------------------------------"); for (p = 0; p < Nobjects[POLLUT]; p++) fprintf(Frpt.file, "--------------"); fprintf(Frpt.file, "\n System %7.2f ", @@ -683,11 +676,11 @@ void writeOutfallLoads() { x = totals[p]; if ( Pollut[p].units == COUNT ) x = LOG10(x); - fprintf(Frpt.file, "%14.3f", x); + fprintf(Frpt.file, "%14.3f", x); } WRITE(""); free(totals); - } + } } //============================================================================= @@ -701,7 +694,6 @@ void writeLinkFlows() { int j, k, days, hrs, mins; double v, fullDepth; - char nullValue = '-'; if (Nobjects[LINK] == 0) return; WRITE(""); @@ -742,7 +734,7 @@ void writeLinkFlows() // --- print max flow / flow capacity for pumps if (Link[j].type == PUMP && Link[j].qFull > 0.0) { - fprintf(Frpt.file, " %c", nullValue); + fprintf(Frpt.file, " "); fprintf(Frpt.file, " %6.2f", LinkStats[j].maxFlow / Link[j].qFull); continue; @@ -763,7 +755,7 @@ void writeLinkFlows() fprintf(Frpt.file, " %6.2f", LinkStats[j].maxFlow / Link[j].qFull / (double)Conduit[k].barrels); } - else fprintf(Frpt.file, " %c %c", nullValue, nullValue); + else fprintf(Frpt.file, " "); // --- print max/full depth fullDepth = Link[j].xsect.yFull; @@ -811,12 +803,12 @@ void writeFlowClass() for ( i=0; i +#include "street.h" + +//----------------------------------------------------------------------------- +// External functions (declared in street.h) +//----------------------------------------------------------------------------- +// street_create called by createObjects in project.c +// street_delete called by deleteObjects in project.c +// street_readParams called by parseLine in input.c + +//============================================================================= + +int street_create(int nStreets) +// +// Input: nStreets = number of Street objects to create +// Output: none +// Purpose: creats a collection of Street objects. +// +{ + Street = NULL; + StreetCount = 0; + Street = (TStreet *)calloc(nStreets, sizeof(TStreet)); + if (Street == NULL) return ERR_MEMORY; + StreetCount = nStreets; + return 0; +} + +//============================================================================= + +void street_delete() +{ + FREE(Street) +} + +//============================================================================= + +int street_readParams(char* tok[], int ntoks) +// +// Format is: +// ID Tcrown Hcurb Sx nRoad (Hdep Wg Sides Tback Sback nBack) +// where +// ID = name assigned to street cross section +// Tcrown = distance from curb to street crown (ft or m) +// Hcurb = curb height (ft or m) +// Sx = roadway cross slope (%) +// nRoad = roadway Manning's n +// Hdep = depressed gutter height (ft or m) +// Wg = depressed gutter width (ft or m) +// Sides = 1 or 2 sided +// Tback = width of street backing (ft or m) +// Sback = slope of street backing (ft or m) +// nBack = Manning's n of street backing +{ + int i, k, sides; + double x[11]; + TStreet *street; + + // --- check for minimum number of tokens + if (ntoks < 5) return error_setInpError(ERR_ITEMS, ""); + + // --- check that street exists in project + i = project_findObject(STREET, tok[0]); + if (i < 0) return error_setInpError(ERR_NAME, tok[0]); + Street[i].ID = project_findID(STREET, tok[0]); + + // --- parse required data + for (k = 0; k <= 9; k++) x[k] = 0.0; + for (k = 1; k <= 4; k++) + if (!getDouble(tok[k], &x[k]) || x[k] <= 0.0) + return error_setInpError(ERR_NUMBER, tok[k]); + + // --- read gutter depression + if (ntoks > 5) + if (!getDouble(tok[5], &x[5]) || x[5] < 0.0) + return error_setInpError(ERR_NUMBER, tok[5]); + + // --- read gutter width + if (ntoks > 6) + if (!getDouble(tok[6], &x[6]) || x[6] < 0.0) + return error_setInpError(ERR_NUMBER, tok[6]); + + // --- read if 1- or 2-sided + sides = 2; + if (ntoks > 7) + if (!getInt(tok[7], &sides) || sides < 1 || sides > 2) + return error_setInpError(ERR_NUMBER, tok[7]); + + // --- read street backing parameters + if (ntoks > 8) + { + if (!getDouble(tok[8], &x[8]) || x[k] < 0.0) + return error_setInpError(ERR_NUMBER, tok[k]); + if (x[8] > 0.0) + { + if (ntoks < 11) return error_setInpError(ERR_ITEMS, ""); + for (k = 9; k <= 10; k++) + if (!getDouble(tok[k], &x[k]) || x[k] <= 0.0) + return error_setInpError(ERR_NUMBER, tok[k]); + } + } + + // --- assign input values to street object + street = &Street[i]; + street->width = x[1] / UCF(LENGTH); + street->curbHeight = x[2] / UCF(LENGTH); + street->slope = x[3] / 100.0; + street->roughness = x[4]; + street->gutterDepression = x[5] / UCF(LENGTH); + street->gutterWidth = x[6] / UCF(LENGTH); + street->sides = sides; + street->backing.width = x[8] / UCF(LENGTH); + street->backing.slope = x[9] / 100.0; + street->backing.roughness = x[10]; + transect_createStreetTransect(i); + return 0; +} diff --git a/src/solver/street.h b/src/solver/street.h new file mode 100644 index 000000000..087c91418 --- /dev/null +++ b/src/solver/street.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// street.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman +// +// Definition of the Street Cross-Section Object +// +//----------------------------------------------------------------------------- +#ifndef STREET_H +#define STREET_H + +#include "headers.h" + +// Street backing structure +typedef struct +{ + double slope; + double width; + double roughness; +} TBacking; + +// Street object +typedef struct +{ + char* ID; // name of street section + int sides; // 1 or 2 sided street + double slope; // cross slope (Sx) + double width; // distance from curb to crown (ft) (Tmax) + double curbHeight; // curb height incl. depression (ft) (Hc) + double gutterDepression; // gutter depression (ft) (a) + double gutterWidth; // gutter width (ft) (W) + double roughness; // street's Manning n + TBacking backing; // street backing + TTransect transect; // street's transect +} TStreet; + +// Shared street variables +int StreetCount; // number of defined Street cross-sections +TStreet *Street; // array of defined Street cross-sections + +// Shared street functions +int street_create(int nStreets); +void street_delete(); +int street_readParams(char* tok[], int ntoks); + +#endif diff --git a/src/solver/subcatch.c b/src/solver/subcatch.c index e773137b5..7bd4656aa 100644 --- a/src/solver/subcatch.c +++ b/src/solver/subcatch.c @@ -2,20 +2,14 @@ // subcatch.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/19/14 (Build 5.1.000) -// 04/19/14 (Build 5.1.006) -// 03/19/15 (Build 5.1.008) -// 04/30/15 (Build 5.1.009) -// 08/05/15 (Build 5.1.010) -// 08/01/16 (Build 5.1.011) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// 05/18/20 (Build 5.1.015) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Subcatchment runoff functions. // +// Update History +// ============== // Build 5.1.008: // - Support added for keeping separate track of drain outflows from LIDs. // - Processing of inflow/outflow volumes over a time step was refactored. @@ -24,28 +18,22 @@ // - Runon now distributed only over non-LID area of a subcatchment, unless // LID covers full area. // - Pollutant buildup and washoff functions were moved to surfqual.c. -// // Build 5.1.009: // - Runon for full LID subcatchment added to statistical summary. -// // Build 5.1.010: // - Fixed a bug introduced in 5.1.008 that forgot to include LID // exfiltration as inflow sent to GW routine. -// // Build 5.1.011: // - Subcatchment percent imperviousness not allowed to exceed 100. -// // Build 5.1.012: // - Subcatchment bottom elevation used instead of aquifer's when // saving water table value to results file. -// // Build 5.1.013: // - Rain gage isUsed property now set in subcatch_validate(). // - Cumulative impervious and pervious area runoff volumes added // to subcatchment statistics. // - Support added for monthly adjustment of subcatchment's depression // storage, pervious N, and infiltration. -// // Build 5.1.015: // - Support added for multiple infiltration methods within a project. // - Only pervious area depression storage receives monthly adjustment. @@ -84,8 +72,8 @@ double VlidReturn; // LID outflow returned to pervious area // Locally shared variables //----------------------------------------------------------------------------- static TSubarea* theSubarea; // subarea to which getDdDt() is applied -static double Dstore; // monthly adjusted depression storage (ft) //(5.1.013) -static double Alpha; // monthly adjusted runoff coeff. // +static double Dstore; // monthly adjusted depression storage (ft) +static double Alpha; // monthly adjusted runoff coeff. static char *RunoffRoutingWords[] = { w_OUTLET, w_IMPERV, w_PERV, NULL}; //----------------------------------------------------------------------------- @@ -124,7 +112,7 @@ static double getSubareaInfil(int j, TSubarea* subarea, double precip, static double findSubareaRunoff(TSubarea* subarea, double tRunoff); static void updatePondedDepth(TSubarea* subarea, double* tx); static void getDdDt(double t, double* d, double* dddt); -static void adjustSubareaParams(int subareaType, int subcatch); //(5.1.013) +static void adjustSubareaParams(int subareaType, int subcatch); //============================================================================= @@ -190,9 +178,9 @@ int subcatch_readParams(int j, char* tok[], int ntoks) Subcatch[j].width = x[5] / UCF(LENGTH); Subcatch[j].slope = x[6] / 100.0; Subcatch[j].curbLength = x[7]; - Subcatch[j].nPervPattern = -1; //(5.1.013 - Subcatch[j].dStorePattern = -1; // - Subcatch[j].infilPattern = -1; // + Subcatch[j].nPervPattern = -1; + Subcatch[j].dStorePattern = -1; + Subcatch[j].infilPattern = -1; // --- create the snow pack object if it hasn't already been created if ( x[8] >= 0 ) @@ -420,9 +408,9 @@ void subcatch_validate(int j) } } - // --- set isUsed property of subcatchment's rain gage //(5.1.013) - i = Subcatch[j].gage; // - if (i >= 0) Gage[i].isUsed = TRUE; // + // --- set isUsed property of subcatchment's rain gage + i = Subcatch[j].gage; + if (i >= 0) Gage[i].isUsed = TRUE; } @@ -437,8 +425,6 @@ void subcatch_initState(int j) { int i; -//// isUsed property of subcatchment's rain gage now set in subcatch_validate //(5.1.013) - // --- initialize rainfall, runoff, & snow depth Subcatch[j].rainfall = 0.0; Subcatch[j].oldRunoff = 0.0; @@ -450,7 +436,7 @@ void subcatch_initState(int j) Subcatch[j].infilLoss = 0.0; // --- initialize state of infiltration, groundwater, & snow pack objects - if ( Subcatch[j].infil == j ) infil_initState(j); //(5.1.015) + if ( Subcatch[j].infil == j ) infil_initState(j); if ( Subcatch[j].groundwater ) gwater_initState(j); if ( Subcatch[j].snowpack ) snow_initSnowpack(j); @@ -665,9 +651,9 @@ double subcatch_getRunoff(int j, double tStep) double vOutflow = 0.0; // runoff volume leaving subcatch (ft3) double runoff = 0.0; // total runoff flow on subcatch (cfs) double evapRate = 0.0; // potential evaporation rate (ft/sec) - double subAreaRunoff; // sub-area runoff rate (cfs) //(5.1.013) - double vImpervRunoff = 0.0; // impervious area runoff volume (ft3) // - double vPervRunoff = 0.0; // pervious area runoff volume (ft3) // + double subAreaRunoff; // sub-area runoff rate (cfs) + double vImpervRunoff = 0.0; // impervious area runoff volume (ft3) + double vPervRunoff = 0.0; // pervious area runoff volume (ft3) // --- initialize shared water balance variables Vevap = 0.0; @@ -699,8 +685,8 @@ double subcatch_getRunoff(int j, double tStep) if ( Evap.dryOnly && Subcatch[j].rainfall > 0.0 ) evapRate = 0.0; else evapRate = Evap.rate; - // --- set monthly infiltration adjustment factor //(5.1.013) - infil_setInfilFactor(j); //(5.1.013) + // --- set monthly infiltration adjustment factor + infil_setInfilFactor(j); // --- examine each type of sub-area (impervious w/o depression storage, // impervious w/ depression storage, and pervious) @@ -711,10 +697,10 @@ double subcatch_getRunoff(int j, double tStep) area = nonLidArea * Subcatch[j].subArea[i].fArea; Subcatch[j].subArea[i].runoff = getSubareaRunoff(j, i, area, netPrecip[i], evapRate, tStep); - subAreaRunoff = Subcatch[j].subArea[i].runoff * area; //(5.1.013) - if (i == PERV) vPervRunoff = subAreaRunoff * tStep; // - else vImpervRunoff += subAreaRunoff * tStep; // - runoff += subAreaRunoff; // + subAreaRunoff = Subcatch[j].subArea[i].runoff * area; + if (i == PERV) vPervRunoff = subAreaRunoff * tStep; + else vImpervRunoff += subAreaRunoff * tStep; + runoff += subAreaRunoff; } // --- evaluate any LID treatment provided (updating Vevap, @@ -747,7 +733,7 @@ double subcatch_getRunoff(int j, double tStep) // --- update the cumulative stats for this subcatchment stats_updateSubcatchStats(j, vRain, vRunon, Vevap, Vinfil + VlidInfil, - vImpervRunoff, vPervRunoff, vOutflow + VlidDrain, //(5.1.013) + vImpervRunoff, vPervRunoff, vOutflow + VlidDrain, Subcatch[j].newRunoff + VlidDrain/tStep); // --- include this subcatchment's contribution to overall flow balance @@ -965,10 +951,10 @@ double getSubareaRunoff(int j, int i, double area, double precip, double evap, if ( i == PERV ) Vpevap += Vevap; Vinfil += infil * area * tStep; - // --- assign adjusted runoff coeff. & storage to shared variables //(5.1.013) - Alpha = subarea->alpha; // - Dstore = subarea->dStore; // - adjustSubareaParams(i, j); // + // --- assign adjusted runoff coeff. & storage to shared variables + Alpha = subarea->alpha; + Dstore = subarea->dStore; + adjustSubareaParams(i, j); // --- if losses exceed available moisture then no ponded water remains if ( surfEvap + infil >= surfMoisture ) @@ -1009,7 +995,7 @@ double getSubareaInfil(int j, TSubarea* subarea, double precip, double tStep) double infil = 0.0; // actual infiltration rate (ft/sec) // --- compute infiltration rate - infil = infil_getInfil(j, tStep, precip, //(5.1.015) + infil = infil_getInfil(j, tStep, precip, subarea->inflow, subarea->depth); // --- limit infiltration rate by available void space in unsaturated @@ -1032,7 +1018,7 @@ double findSubareaRunoff(TSubarea* subarea, double tRunoff) // Output: returns runoff rate (ft/s) // { - double xDepth = subarea->depth - Dstore; //(5.1.013) + double xDepth = subarea->depth - Dstore; double runoff = 0.0; if ( xDepth > ZERO ) @@ -1040,14 +1026,14 @@ double findSubareaRunoff(TSubarea* subarea, double tRunoff) // --- case where nonlinear routing is used if ( subarea->N > 0.0 ) { - runoff = Alpha * pow(xDepth, MEXP); //(5.1.013) + runoff = Alpha * pow(xDepth, MEXP); } // --- case where no routing is used (Mannings N = 0) else { runoff = xDepth / tRunoff; - subarea->depth = Dstore; //(5.1.013) + subarea->depth = Dstore; } } else @@ -1072,7 +1058,7 @@ void updatePondedDepth(TSubarea* subarea, double* dt) double tx = *dt; // time over which dx > 0 (sec) // --- see if not enough inflow to fill depression storage (dStore) - if ( subarea->depth + ix*tx <= Dstore ) //(5.1.013) + if ( subarea->depth + ix*tx <= Dstore ) { subarea->depth += ix * tx; } @@ -1080,16 +1066,16 @@ void updatePondedDepth(TSubarea* subarea, double* dt) // --- otherwise use the ODE solver to integrate flow depth else { - // --- if depth < Dstore then fill up Dstore & reduce time step //(5.1.013) - dx = Dstore - subarea->depth; // + // --- if depth < Dstore then fill up Dstore & reduce time step + dx = Dstore - subarea->depth; if ( dx > 0.0 && ix > 0.0 ) { tx -= dx / ix; - subarea->depth = Dstore; //(5.1.013) + subarea->depth = Dstore; } // --- now integrate depth over remaining time step tx - if ( Alpha > 0.0 && tx > 0.0 ) //(5.1.013) + if ( Alpha > 0.0 && tx > 0.0 ) { theSubarea = subarea; odesolve_integrate(&(subarea->depth), 1, 0, tx, ODETOL, tx, @@ -1122,28 +1108,26 @@ void getDdDt(double t, double* d, double* dddt) // { double ix = theSubarea->inflow; - double rx = *d - Dstore; //(5.1.013) + double rx = *d - Dstore; if ( rx < 0.0 ) { rx = 0.0; } else { - rx = Alpha * pow(rx, MEXP); //(5.1.013) + rx = Alpha * pow(rx, MEXP); } *dddt = ix - rx; } //============================================================================= -//// New function added to release 5.1.013. //// //(5.1.013) - void adjustSubareaParams(int i, int j) // // Input: i = type of subarea being analyzed // j = index of current subcatchment being analyzed // Output adjusted values of module-level variables Dstore & Alpha -// Purpose: adjusts a pervious subarea's depression storage and its //(5.1.015) +// Purpose: adjusts a pervious subarea's depression storage and its // runoff coeff. by month of the year. // { @@ -1151,7 +1135,7 @@ void adjustSubareaParams(int i, int j) int m; // current month of the year double f; // adjustment factor - if (i == PERV) //(5.1.015) + if (i == PERV) { // --- depression storage adjustment p = Subcatch[j].dStorePattern; @@ -1162,9 +1146,9 @@ void adjustSubareaParams(int i, int j) if (f >= 0.0) Dstore *= f; } - // --- roughness adjustment to runoff coeff. //(5.1.015) + // --- roughness adjustment to runoff coeff. p = Subcatch[j].nPervPattern; - if (p >= 0 && Pattern[p].type == MONTHLY_PATTERN) //(5.1.015) + if (p >= 0 && Pattern[p].type == MONTHLY_PATTERN) { m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; f = Pattern[p].factor[m]; diff --git a/src/solver/surfqual.c b/src/solver/surfqual.c index 244fab2f0..aa88cb6d7 100644 --- a/src/solver/surfqual.c +++ b/src/solver/surfqual.c @@ -2,18 +2,18 @@ // surfqual.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/19/15 (Build 5.1.008) -// 03/01/20 (Build 5.1.014) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Subcatchment water quality functions. // +// Update History +// ============== // Build 5.1.008: // - Pollutant surface buildup and washoff functions were moved here from // subcatch.c. // - Support for separate accounting of LID drain flows included. -// // Build 5.1.014: // - Fixed bug in computing effective BMP removal by LIDs. //----------------------------------------------------------------------------- diff --git a/src/solver/swmm5.c b/src/solver/swmm5.c index 1ddf9f77c..164a00e26 100644 --- a/src/solver/swmm5.c +++ b/src/solver/swmm5.c @@ -2,13 +2,8 @@ // swmm5.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/19/14 (Build 5.1.001) -// 03/19/15 (Build 5.1.008) -// 08/01/16 (Build 5.1.011) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// 04/01/20 (Build 5.1.015) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // This is the main module of the computational engine for Version 5 of @@ -18,12 +13,13 @@ // This engine should be compiled into a shared object library whose API // functions are listed in swmm5.h. // +// Update History +// ============== // Build 5.1.008: // - Support added for the MinGW compiler. // - 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: // - Made sure that MS exception handling only used with MS C compiler. // - Added name of module handling an exception to error report. @@ -32,14 +28,11 @@ // - Changed WarningCode to Warnings (# warnings issued). // - Added swmm_getWarnings() function to retrieve value of Warnings. // - Fixed error code returned on swmm_xxx functions. -// // Build 5.1.012: // - #include only used when compiled for Windows. -// // Build 5.1.013: // - Support added for saving average results within a reporting period. // - SWMM engine now always compiled to a shared object library. -// // Build 5.1.015: // - Fixes bug in summary statistics when Report Start date > Start Date. //----------------------------------------------------------------------------- @@ -86,7 +79,7 @@ //----------------------------------------------------------------------------- #include "consts.h" // defined constants #include "macros.h" // macros used throughout SWMM -#include "enums.h" // enumerated variables +#include "enums.h" // enumerated constants #include "error.h" // error message codes #include "datetime.h" // date/time functions #include "objects.h" // definitions of SWMM's data objects @@ -167,10 +160,10 @@ int DLLEXPORT swmm_run(char* f1, char* f2, char* f3) long theDay, theHour; double elapsedTime = 0.0; - // --- initialize flags //(5.1.013) - IsOpenFlag = FALSE; // - IsStartedFlag = FALSE; // - SaveResultsFlag = TRUE; // + // --- initialize flags + IsOpenFlag = FALSE; + IsStartedFlag = FALSE; + SaveResultsFlag = TRUE; // --- open the files & read input data ErrorCode = 0; @@ -195,7 +188,7 @@ int DLLEXPORT swmm_run(char* f1, char* f2, char* f3) theDay = (long)elapsedTime; theHour = (long)((elapsedTime - floor(elapsedTime)) * 24.0); writecon("\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); - sprintf(Msg, "%-5ld hour: %-2ld", theDay, theHour); //(5.1.013) + sprintf(Msg, "%-5ld hour: %-2ld", theDay, theHour); writecon(Msg); oldHour = newHour; } @@ -228,8 +221,8 @@ int DLLEXPORT swmm_open(char* f1, char* f2, char* f3) // Purpose: opens a SWMM project. // { -// --- to be safe, reset the state of the floating point unit //(5.1.013) -#ifdef WINDOWS //(5.1.013) +// --- to be safe, reset the state of the floating point unit +#ifdef WINDOWS _fpreset(); #endif @@ -309,8 +302,8 @@ int DLLEXPORT swmm_start(int saveResults) NewRunoffTime = 0.0; NewRoutingTime = 0.0; ReportTime = (double)(1000 * ReportStep); - TotalStepCount = 0; //(5.1.015) - ReportStepCount = 0; //(5.1.015) + TotalStepCount = 0; + ReportStepCount = 0; NonConvergeCount = 0; IsStartedFlag = TRUE; @@ -396,7 +389,6 @@ int DLLEXPORT swmm_step(double* elapsedTime) execRouting(); } -//// Following code segment modified for release 5.1.013. //// //(5.1.013) // --- if saving results to the binary file if ( SaveResultsFlag ) { @@ -429,7 +421,6 @@ int DLLEXPORT swmm_step(double* elapsedTime) // --- not a reporting period so update average results if applicable else if ( RptFlags.averages ) output_updateAvgResults(); } -//// // --- update elapsed time (days) if ( NewRoutingTime < TotalDuration ) @@ -444,7 +435,7 @@ int DLLEXPORT swmm_step(double* elapsedTime) #ifdef EXH // --- end of try loop; handle exception here - __except(xfilter(GetExceptionCode(), "swmm_step", ElapsedTime, TotalStepCount)) //(5.1.015) + __except(xfilter(GetExceptionCode(), "swmm_step", ElapsedTime, TotalStepCount)) { ErrorCode = ERR_SYSTEM; } @@ -470,7 +461,7 @@ void execRouting() #endif { // --- determine when next routing time occurs - TotalStepCount++; //(5.1.015) + TotalStepCount++; if ( !DoRouting ) routingStep = MIN(WetStep, ReportStep); else routingStep = routing_getRoutingStep(RouteModel, RouteStep); if ( routingStep <= 0.0 ) @@ -508,7 +499,7 @@ void execRouting() #ifdef EXH // --- end of try loop; handle exception here __except(xfilter(GetExceptionCode(), "execRouting", - ElapsedTime, TotalStepCount)) //(5.1.015) + ElapsedTime, TotalStepCount)) { ErrorCode = ERR_SYSTEM; return; diff --git a/src/solver/swmm5.def b/src/solver/swmm5.def new file mode 100644 index 000000000..98391ee06 --- /dev/null +++ b/src/solver/swmm5.def @@ -0,0 +1,14 @@ +LIBRARY SWMM5.DLL + +EXPORTS + swmm_close = _swmm_close@0 + swmm_end = _swmm_end@0 + swmm_getError = _swmm_getError@8 + swmm_getMassBalErr = _swmm_getMassBalErr@12 + swmm_getVersion = _swmm_getVersion@0 + swmm_getWarnings = _swmm_getWarnings@0 + swmm_open = _swmm_open@12 + swmm_report = _swmm_report@0 + swmm_run = _swmm_run@12 + swmm_start = _swmm_start@4 + swmm_step = _swmm_step@4 diff --git a/src/solver/swmm5.h b/src/solver/swmm5.h new file mode 100644 index 000000000..1d4d0f00d --- /dev/null +++ b/src/solver/swmm5.h @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------------- +// swmm5.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman +// +// Prototypes for SWMM5 functions exported to swmm5.dll. +// +//----------------------------------------------------------------------------- + +#ifndef SWMM5_H +#define SWMM5_H + +// --- define WINDOWS + +#undef WINDOWS +#ifdef _WIN32 + #define WINDOWS +#endif +#ifdef __WIN32__ + #define WINDOWS +#endif + +// --- define DLLEXPORT + +#ifdef WINDOWS + #define DLLEXPORT __declspec(dllexport) __stdcall +#else + #define DLLEXPORT +#endif + +// --- use "C" linkage for C++ programs + +#ifdef __cplusplus +extern "C" { +#endif + +int DLLEXPORT swmm_run(char* f1, char* f2, char* f3); +int DLLEXPORT swmm_open(char* f1, char* f2, char* f3); +int DLLEXPORT swmm_start(int saveFlag); +int DLLEXPORT swmm_step(double* elapsedTime); +int DLLEXPORT swmm_end(void); +int DLLEXPORT swmm_report(void); +int DLLEXPORT swmm_getMassBalErr(float* runoffErr, float* flowErr, + float* qualErr); +int DLLEXPORT swmm_close(void); +int DLLEXPORT swmm_getVersion(void); +int DLLEXPORT swmm_getError(char* errMsg, int msgLen); +int DLLEXPORT swmm_getWarnings(void); + +#ifdef __cplusplus +} // matches the linkage specification from above */ +#endif + +#endif //SWMM5_H diff --git a/src/solver/table.c b/src/solver/table.c index 5ba5095f6..024290dd6 100644 --- a/src/solver/table.c +++ b/src/solver/table.c @@ -2,10 +2,8 @@ // table.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Table (curve and time series) functions. @@ -16,12 +14,19 @@ // The table_getFirstEntry and table_getNextEntry functions, as well as the // Time Series functions that use them, are not thread safe. // +// Update History +// ============== // Build 5.1.008: // - The lookup functions used for Curve tables (table_lookup, table_lookupEx, // table_intervalLookup, table_inverseLookup, table_getSlope, table_getMaxY, // table_getArea, and table_getInverseArea) were made thread-safe (thanks to // suggestions by CHI). -// +// Build 5.2.0: +// - First line of Curve's input data can contain just the curve name and type. +// - The table_getArea function was renamed table_getStorageVolume and was +// - refactored. +// - The table_getInverseArea function was renamed table_getStorageDepth and +// was refactored. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -68,7 +73,7 @@ int table_readCurve(char* tok[], int ntoks) double x, y; // --- check for minimum number of tokens - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); // --- check that curve exists in database j = project_findObject(CURVE, tok[0]); @@ -83,6 +88,7 @@ int table_readCurve(char* tok[], int ntoks) m = findmatch(tok[1], CurveTypeWords); if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); Curve[j].curveType = m; + if (ntoks == 2) return 0; k1 = 2; } @@ -573,144 +579,144 @@ double table_getMaxY(TTable *table, double x) //============================================================================= -double table_getArea(TTable* table, double x) +double table_getStorageVolume(TTable *table, double x) // // Input: table = pointer to a TTable structure -// x = an value -// Output: returns area value -// Purpose: finds area under a tabulated curve from 0 to x; -// requires that table's x values be non-negative -// and non-decreasing. -// -// The area within each interval i of the table is given by: -// Integral{ y(x)*dx } from x(i) to x -// where y(x) = y(i) + s*dx -// dx = x - x(i) -// s = [y(i+1) - y(i)] / [x(i+1) - x(i)] -// This results in the following expression for a(i): -// a(i) = y(i)*dx + s*dx*dx/2 +// x = a depth value +// Output: returns a storage volume +// Purpose: finds volume for a given depth in a Storage Curve table. // { - double x1, x2; - double y1, y2; - double dx = 0.0, dy = 0.0; - double a, s = 0.0; + double a, a1, x1, v, dx, dy, s; TTableEntry* entry; - // --- get area up to first table entry - // and see if x-value lies in this interval + // --- get first entry in table + v = 0.0; entry = table->firstEntry; - if (entry == NULL ) return 0.0; + if (entry == NULL) return 0.0; x1 = entry->x; - y1 = entry->y; - if ( x1 > 0.0 ) s = y1/x1; - if ( x <= x1 ) return s*x*x/2.0; - a = y1*x1/2.0; - - // --- add next table entry to area until target x-value is bracketed - while ( entry->next ) + a1 = entry->y; + + // --- target depth is below first tabulated depth + if (x <= x1) + { + if (x1 < 1.e-6) return 0.0; + return (a1/x1) * x * x / 2.0; + } + + // --- otherwise traverse table entries until target depth is bracketed + while (entry->next) { entry = entry->next; - x2 = entry->x; - y2 = entry->y; - dx = x2 - x1; - dy = y2 - y1; - if ( x <= x2 ) + // --- target is bracketed - apply end area method to interpolated area + if (entry->x >= x) { - if ( dx <= 0.0 ) return a; - y2 = table_interpolate(x, x1, y1, x2, y2); - return a + (x - x1) * (y1 + y2) / 2.0; + a = table_interpolate(x, x1, a1, entry->x, entry->y); + return v + (a1 + a) / 2.0 * (x - x1); + } + // --- target not yet bracketed so update volume using end area method + else + { + dx = entry->x - x1; + dy = entry->y - a1; + v = v + (a1 + entry->y) / 2.0 * dx; + x1 = entry->x; + a1 = entry->y; } - a += (y1 + y2) * dx / 2.0; - x1 = x2; - y1 = y2; } // --- extrapolate area if table limit exceeded - if ( dx > 0.0 ) s = dy/dx; - else s = 0.0; - dx = x - x1; - return a + y1*dx + s*dx*dx/2.0; + if (dx > 1.0e-6) + { + s = dy / dx; + a = a1 + s * (x - x1); + // --- don't extrapolate below 0 in case s is negative + if (a < 0.0) + { + v = v - a1 * a1 / s / 2.0; + } + // --- apply end area method to extrapolated area + else v = v + (a1 + a) / 2.0 * (x - x1); + } + return v; } //============================================================================= -double table_getInverseArea(TTable* table, double a) +double table_getStorageDepth(TTable *table, double v) // // Input: table = pointer to a TTable structure -// a = an area value -// Output: returns an x value -// Purpose: finds x value for given area under a curve. -// -// Refer to table_getArea function to see how area is computed. +// v = a storage volume +// Output: returns a storage depth +// Purpose: finds depth for a given volume in a Storage Curve table. // { - double x1, x2; - double y1, y2; - double dx = 0.0, dy = 0.0; - double a1, a2, s; + double a1, a2, d1, d2, dd, da, v1, v2, s; TTableEntry* entry; - // --- see if target area is below that of 1st table entry + // --- see if target volume is below that of 1st table entry + if (v == 0.0) return 0.0; entry = table->firstEntry; - if (entry == NULL ) return 0.0; - x1 = entry->x; - y1 = entry->y; - a1 = y1*x1/2.0; - if ( a <= a1 ) + if (entry == NULL) return 0.0; + d1 = entry->x; + a1 = entry->y; + v1 = a1 * d1 / 2.0; + if (v <= v1) { - if ( y1 > 0.0 ) return sqrt(2.0*a*x1/y1); + if (a1 > 0.0) return sqrt(2.0 * v * d1 / a1); else return 0.0; } - // --- add next table entry to area until target area is bracketed - while ( entry->next ) + // --- add next table entry to volume until target volume is bracketed + while (entry->next) { entry = entry->next; - x2 = entry->x; - y2 = entry->y; - dx = x2 - x1; - dy = y2 - y1; - a2 = a1 + y1*dx + dy*dx/2.0; - if ( a <= a2 ) + d2 = entry->x; + a2 = entry->y; + dd = d2 - d1; + da = a2 - a1; + v2 = v1 + (a1 + a2) / 2.0 * dd; + + // target volume is bracketed + if (v <= v2) { - if ( dx <= 0.0 ) return x1; - if ( dy == 0.0 ) + // --- target coincides with point on curve + if (dd <= 0.0) return d1; + if (da == 0.0) { - if ( a2 == a1 ) return x1; - else return x1 + dx * (a - a1) / (a2 - a1); + if (fabs(v2 - v1) < 1.e-6) return d1; + else return d1 + dd * (v - v1) / (v2 - v1); } - - // --- if y decreases with x then replace point 1 with point 2 - if ( dy < 0.0 ) + // --- if area decreases with depth then replace point 1 with point 2 + if (da < 0.0) { - x1 = x2; - y1 = y2; + d1 = d2; a1 = a2; + v1 = v2; } - - s = dy/dx; - dx = (sqrt(y1*y1 + 2.0*s*(a-a1)) - y1) / s; - return x1 + dx; + // --- interpolate between volumes derived from curve + s = da / dd; + return d1 + (sqrt(a1*a1 + 2.0*s*(v-v1)) - a1) / s; } - x1 = x2; - y1 = y2; + // --- replace point 1 with point 2 + d1 = d2; a1 = a2; + v1 = v2; } - // --- extrapolate area if table limit exceeded - if ( dx == 0.0 || dy == 0.0 ) + // --- extrapolate volume if table limit exceeded + if (dd == 0.0 || da == 0.0) { - if ( y1 > 0.0 ) dx = (a - a1) / y1; - else dx = 0.0; + if (a1 > 0.0) dd = (v - v1) / a1; + else dd = 0.0; } else { - s = dy/dx; - dx = (sqrt(y1*y1 + 2.0*s*(a-a1)) - y1) / s; - if (dx < 0.0) dx = 0.0; + s = da / dd; + dd = (sqrt(a1*a1 + 2.0*s*(v - v1)) - a1) / s; + if (dd < 0.0) dd = 0.0; } - return x1 + dx; + return d1 + dd; } //============================================================================= diff --git a/src/solver/text.h b/src/solver/text.h index 204f6d518..23586ef12 100644 --- a/src/solver/text.h +++ b/src/solver/text.h @@ -2,22 +2,8 @@ // text.h // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/19/14 (Build 5.1.001) -// 04/02/14 (Build 5.1.003) -// 04/14/14 (Build 5.1.004) -// 04/23/14 (Build 5.1.005) -// 05/19/14 (Build 5.1.006) -// 09/15/14 (Build 5.1.007) -// 03/19/15 (Build 5.1.008) -// 04/30/15 (Build 5.1.009) -// 08/05/15 (Build 5.1.010) -// 08/01/16 (Build 5.1.011) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// 03/01/20 (Build 5.1.014) -// 04/01/20 (Build 5.1.015) -// +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Text strings @@ -36,9 +22,9 @@ #define FMT06 "\n o Retrieving project data" #define FMT07 "\n o Writing output report" #define FMT08 \ - "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.1 (Build 5.1.015)" //(5.1.015) + "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.2 (Build 5.2.0)" #define FMT09 \ - "\n --------------------------------------------------------------" + "\n ------------------------------------------------------------" #define FMT10 "\n" #define FMT11 "\n Cannot use duplicate file names." #define FMT12 "\n Cannot open input file " @@ -46,8 +32,8 @@ #define FMT14 "\n Cannot open output file " #define FMT15 "\n Cannot open temporary output file" #define FMT16 "\n ERROR %d detected. Execution halted." -#define FMT17 "at line %ld of input file:" //(5.1.013) -#define FMT18 "at line %ld of %s] section:" //(5.1.013) +#define FMT17 "at line %ld of input file:" +#define FMT18 "at line %ld of %s] section:" #define FMT19 "\n Maximum error count exceeded." #define FMT20 "\n\n Analysis begun on: %s" #define FMT20a " Analysis ended on: %s" @@ -64,10 +50,12 @@ #define WARN08 "WARNING 08: elevation drop exceeds length for Conduit" #define WARN09 "WARNING 09: time series interval greater than recording interval for Rain Gage" #define WARN10a \ -"WARNING 10: crest elevation is below downstream invert for regulator Link" //(5.1.013) +"WARNING 10: crest elevation is below downstream invert for regulator Link" #define WARN10b \ -"WARNING 10: crest elevation raised to downstream invert for regulator Link" //(5.1.013) +"WARNING 10: crest elevation raised to downstream invert for regulator Link" #define WARN11 "WARNING 11: non-matching attributes in Control Rule" +#define WARN12 \ +"WARNING 12: inlet removed due to unsupported shape for Conduit" // Analysis Option Keywords #define w_FLOW_UNITS "FLOW_UNITS" @@ -86,7 +74,7 @@ #define w_DRY_STEP "DRY_STEP" #define w_ROUTE_STEP "ROUTING_STEP" #define w_REPORT_STEP "REPORT_STEP" -#define w_RULE_STEP "RULE_STEP" //(5.1.013) +#define w_RULE_STEP "RULE_STEP" #define w_ALLOW_PONDING "ALLOW_PONDING" #define w_INERT_DAMPING "INERTIAL_DAMPING" #define w_SLOPE_WEIGHTING "SLOPE_WEIGHTING" @@ -112,7 +100,7 @@ #define w_IGNORE_RDII "IGNORE_RDII" #define w_MIN_ROUTE_STEP "MINIMUM_STEP" #define w_NUM_THREADS "THREADS" -#define w_SURCHARGE_METHOD "SURCHARGE_METHOD" //(5.1.013) +#define w_SURCHARGE_METHOD "SURCHARGE_METHOD" // Flow Units #define w_CFS "CFS" @@ -133,7 +121,7 @@ #define w_XKINWAVE "XKINWAVE" #define w_DYNWAVE "DYNWAVE" -// Surcharge Methods //(5.1.013) +// Surcharge Methods #define w_EXTRAN "EXTRAN" #define w_SLOT "SLOT" @@ -197,11 +185,16 @@ #define w_CUTOFF "CUTOFF" #define w_OVERFLOW "OVERFLOW" +// Storage Node Shapes +#define w_CONICAL "CONICAL" +#define w_PYRAMIDAL "PYRAMIDAL" + // Pump Curve Types #define w_TYPE1 "TYPE1" #define w_TYPE2 "TYPE2" #define w_TYPE3 "TYPE3" #define w_TYPE4 "TYPE4" +#define w_TYPE5 "TYPE5" #define w_IDEAL "IDEAL" // Pump Curve Variables @@ -229,6 +222,7 @@ #define w_TRIANGULAR "TRIANGULAR" #define w_PARABOLIC "PARABOLIC" #define w_POWERFUNC "POWER" +#define w_STREET "STREET" #define w_RECT_TRIANG "RECT_TRIANGULAR" #define w_RECT_ROUND "RECT_ROUND" #define w_MOD_BASKET "MODBASKETHANDLE" @@ -317,6 +311,8 @@ #define w_PUMP2 "PUMP2" #define w_PUMP3 "PUMP3" #define w_PUMP4 "PUMP4" +#define w_PUMP5 "PUMP5" +#define w_INLET "INLET" // Reporting Options #define w_INPUT "INPUT" @@ -324,7 +320,7 @@ #define w_FLOWSTATS "FLOWSTATS" #define w_CONTROLS "CONTROL" #define w_NODESTATS "NODESTATS" -#define w_AVERAGES "AVERAGES" //(5.1.013) +#define w_AVERAGES "AVERAGES" // Interface File Types #define w_RAINFALL "RAINFALL" @@ -380,6 +376,9 @@ #define w_ELSE "ELSE" #define w_PRIORITY "PRIORITY" +#define w_VARIABLE "VARIABLE" +#define w_EXPRESSION "EXPRESSION" + // External Inflow Types #define w_FLOW "FLOW" #define w_CONCEN "CONCEN" @@ -450,6 +449,8 @@ #define ws_GWF "[GWF" #define ws_ADJUST "[ADJUSTMENT" #define ws_EVENT "[EVENT" - +#define ws_STREET "[STREET" +#define ws_INLET "[INLET" +#define ws_INLET_USAGE "[INLET_USAGE" #endif //TEXT_H diff --git a/src/solver/toposort.c b/src/solver/toposort.c index 19a92f597..fc25e30ec 100644 --- a/src/solver/toposort.c +++ b/src/solver/toposort.c @@ -2,8 +2,8 @@ // toposort.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Topological sorting of conveyance network links @@ -114,7 +114,10 @@ void toposort_sortLinks(int sortedLinks[]) // --- find number of links entering each node for (i = 0; i < Nobjects[NODE]; i++) InDegree[i] = 0; - for (i = 0; i < Nobjects[LINK]; i++) InDegree[ Link[i].node2 ]++; + for (i = 0; i < Nobjects[LINK]; i++) + { + InDegree[ Link[i].node2 ]++; + } // --- topo sort the links n = topoSort(sortedLinks); @@ -264,7 +267,6 @@ int topoSort(int sortedLinks[]) // --- reduce in-degree of link's downstream node i2 = Link[j].node2; InDegree[i2]--; - // --- add downstream node to stack if its in-degree is zero if ( InDegree[i2] == 0 ) { diff --git a/src/solver/transect.c b/src/solver/transect.c index 2d8fb1cce..19a80647d 100644 --- a/src/solver/transect.c +++ b/src/solver/transect.c @@ -2,11 +2,16 @@ // transect.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Geometry processing for irregular cross-section transects. +// +// Update History +// ============== +// Build 5.2.0: +// - Adds a function to create a transect for a Street cross-section. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -14,6 +19,7 @@ #include #include #include "headers.h" +#include "street.h" //----------------------------------------------------------------------------- // Constants @@ -43,6 +49,7 @@ static double Lfactor; // main channel/flood plain length // transect_delete (called by deleteObjects in project.c) // transect_readParams (called by parseLine in input.c) // transect_validate (called by input_readData) +// transect_createStreetTransect (called by street_readparams) //----------------------------------------------------------------------------- // Local functions @@ -51,10 +58,11 @@ static int setParams(int transect, char* id, double x[]); static int setManning(double n[]); static int addStation(double x, double y); static double getFlow(int k, double a, double wp, int findFlow); -static void getGeometry(int i, int j, double y); +static void createTables(TTransect *transect, double ymin, double ymax); +static void getGeometry(TTransect *transect, int i, double y); static void getSliceGeom(int k, double y, double yu, double yd, double *w, double *a, double *wp); -static void setMaxSectionFactor(int transect); +static void setMaxSectionFactor(TTransect *transect); //============================================================================= @@ -189,8 +197,8 @@ void transect_validate(int j) // Purpose: validates transect data and creates its geometry tables. // { - int i, nLast; - double dy, y, ymin, ymax; + int i; + double ymin, ymax; double oldNchannel = Nchannel; // --- check for valid transect data @@ -234,7 +242,6 @@ void transect_validate(int j) report_writeErrorMsg(ERR_TRANSECT_NO_DEPTH, Transect[j].ID); return; } - Transect[j].yFull = ymax - ymin; // --- add vertical sides to transect to reach full ht. on both ends Station[0] = Station[1]; @@ -243,49 +250,60 @@ void transect_validate(int j) Station[Nstations] = Station[Nstations-1]; Elev[Nstations] = Elev[0]; - // --- determine size & depth increment for geometry tables + // --- create geometry tables Transect[j].nTbl = N_TRANSECT_TBL; - dy = (ymax - ymin) / (double)(Transect[j].nTbl - 1); + createTables(&Transect[j], ymin, ymax); + + // --- save unadjusted main channel roughness + Transect[j].roughness = oldNchannel; +} + +//============================================================================= + +void createTables(TTransect *transect, double ymin, double ymax) +{ + int i, nLast; + double dy, y; + + transect->yFull = ymax - ymin; + transect->wMax = 0.0; // --- set 1st table entries to zero - Transect[j].areaTbl[0] = 0.0; - Transect[j].hradTbl[0] = 0.0; - Transect[j].widthTbl[0] = 0.0; + transect->areaTbl[0] = 0.0; + transect->hradTbl[0] = 0.0; + transect->widthTbl[0] = 0.0; // --- compute geometry for each depth increment + dy = (ymax - ymin) / (double)(transect->nTbl - 1); y = ymin; - Transect[j].wMax = 0.0; - for (i = 1; i < Transect[j].nTbl; i++) + for (i = 1; i < transect->nTbl; i++) { y += dy; - Transect[j].areaTbl[i] = 0.0; - Transect[j].hradTbl[i] = 0.0; - Transect[j].widthTbl[i] = 0.0; - getGeometry(i, j, y); + transect->areaTbl[i] = 0.0; + transect->hradTbl[i] = 0.0; + transect->widthTbl[i] = 0.0; + getGeometry(transect, i, y); } // --- determine max. section factor - setMaxSectionFactor(j); + setMaxSectionFactor(transect); // --- normalize geometry table entries // (full cross-section values are last table entries) - nLast = Transect[j].nTbl - 1; - Transect[j].aFull = Transect[j].areaTbl[nLast]; - Transect[j].rFull = Transect[j].hradTbl[nLast]; - Transect[j].wMax = Transect[j].widthTbl[nLast]; + nLast = transect->nTbl - 1; + transect->aFull = transect->areaTbl[nLast]; + transect->rFull = transect->hradTbl[nLast]; + transect->wMax = transect->widthTbl[nLast]; for (i = 1; i <= nLast; i++) { - Transect[j].areaTbl[i] /= Transect[j].aFull; - Transect[j].hradTbl[i] /= Transect[j].rFull; - Transect[j].widthTbl[i] /= Transect[j].wMax; + transect->areaTbl[i] /= transect->aFull; + transect->hradTbl[i] /= transect->rFull; + transect->widthTbl[i] /= transect->wMax; } // --- set width at 0 height equal to width at 4% of max. height - Transect[j].widthTbl[0] = Transect[j].widthTbl[1]; - - // --- save unadjusted main channel roughness - Transect[j].roughness = oldNchannel; + transect->widthTbl[0] = transect->widthTbl[1]; } //============================================================================= @@ -366,10 +384,10 @@ int addStation(double y, double x) //============================================================================= -void getGeometry(int i, int j, double y) +void getGeometry(TTransect *transect, int i, double y) // -// Input: i = index of current entry in geometry tables -// j = transect index +// Input: transect = transect being analyzed +// i = index of current entry in geometry tables // y = depth of current entry in geometry tables // Output: none // Purpose: computes entries in a transect's geometry tables at a given depth. @@ -417,8 +435,8 @@ void getGeometry(int i, int j, double y) // --- update total transect values wpSum += wp; aSum += a; - Transect[j].areaTbl[i] += a; - Transect[j].widthTbl[i] += w; + transect->areaTbl[i] += a; + transect->widthTbl[i] += w; // --- must update flow if station elevation is above water level if ( Elev[k] >= y ) findFlow = TRUE; @@ -437,9 +455,11 @@ void getGeometry(int i, int j, double y) // --- find hyd. radius table entry solving Manning eq. with // total flow, total area, and main channel n - aSum = Transect[j].areaTbl[i]; - if ( aSum == 0.0 ) Transect[j].hradTbl[i] = Transect[j].hradTbl[i-1]; - else Transect[j].hradTbl[i] = pow(qSum * Nchannel / 1.49 / aSum, 1.5); + aSum = transect->areaTbl[i]; + if ( aSum == 0.0 ) + transect->hradTbl[i] = transect->hradTbl[i-1]; + else + transect->hradTbl[i] = pow(qSum * Nchannel / 1.49 / aSum, 1.5); } //============================================================================= @@ -538,6 +558,7 @@ double getFlow(int k, double a, double wp, int findFlow) if ( Station[k] > Xrightbank ) n = Nright; // --- compute flow through flow area + // (PHI is the Manning Eqn. constant defined in consts.h) return PHI / n * a * pow(a/wp, 2./3.); } return 0.0; @@ -545,9 +566,9 @@ double getFlow(int k, double a, double wp, int findFlow) //============================================================================= -void setMaxSectionFactor(int j) +void setMaxSectionFactor(TTransect *transect) // -// Input: j = transect index +// Input: transect = transect being analyzed // Output: none // Purpose: determines the maximum section factor for a transect and the // area where this maxumum occurs. @@ -556,17 +577,104 @@ void setMaxSectionFactor(int j) int i; double sf; - Transect[j].aMax = 0.0; - Transect[j].sMax = 0.0; - for (i=1; iaMax = 0.0; + transect->sMax = 0.0; + for (i = 1; i < transect->nTbl; i++) { - sf = Transect[j].areaTbl[i] * pow(Transect[j].hradTbl[i], 2./3.); - if ( sf > Transect[j].sMax ) + sf = transect->areaTbl[i] * pow(transect->hradTbl[i], 2. / 3.); + if (sf > transect->sMax) { - Transect[j].sMax = sf; - Transect[j].aMax = Transect[j].areaTbl[i]; + transect->sMax = sf; + transect->aMax = transect->areaTbl[i]; } } } //============================================================================= + +void transect_createStreetTransect(int streetIndex) +// +{ + double ymin, ymax, y1, y3, y4; + double w1, w2, w3, w4; + TStreet *street = &Street[streetIndex]; + + // Point 0 = top of backing + // Point 1 = top of curb + // Point 2 = bottom of curb + // Point 3 = bottom of depressed gutter + // Point 4 = top of depressed gutter + // Point 5 = street crown + + // --- assign height (y) and width (w) to road & gutter sections + ymin = 0.0; + w1 = street->backing.width; + w2 = street->gutterWidth; + w3 = street->width; + w4 = w3 - w2; + y3 = street->gutterDepression; + y1 = street->curbHeight + y3; + ymax = street->backing.slope * street->backing.width + y1; + y4 = y3 + street->slope * w4; + ymax = MAX(ymax, y4); + + // --- assign Station,Elevation points to the street's sections + Station[0] = 0.0; + Elev[0] = ymax; + Station[1] = w1; + Elev[1] = y1; + Station[2] = w1; + Elev[2] = 0.0; + Station[3] = w1 + w2; + Elev[3] = y3; + Station[4] = w1 + w3; + Elev[4] = y4; + + // --- a half street ends here + if (street->sides == 1) + { + Station[5] = Station[4]; + Elev[5] = ymax; + Nstations = 5; + street->transect.nTbl = N_TRANSECT_TBL; + } + + // --- the right side of a full street mirrors the left side + else + { + Station[5] = Station[4] + w4; + Elev[5] = y3; + Station[6] = Station[5] + w2; + Elev[6] = 0.0; + Station[7] = Station[6]; + Elev[7] = y1; + Station[8] = Station[7] + w1; + Elev[8] = ymax; + Nstations = 8; + street->transect.nTbl = N_TRANSECT_TBL; + } + + // --- assign Manning's N to street + Nchannel = street->roughness; + if (street->backing.width == 0.0) + { + Nleft = Nchannel; + Nright = Nchannel; + Xleftbank = Station[0]; + Xrightbank = Station[Nstations]; + } + else + { + Nleft = street->backing.roughness; + Nright = Nleft; + Xleftbank = Station[1]; + if (street->sides == 2) + Xrightbank = Station[Nstations - 1]; + else + Xrightbank = Station[Nstations]; + } + + // --- create the street's geometry tables + createTables(&(street->transect), ymin, ymax); + street->transect.roughness = street->roughness; +} diff --git a/src/solver/treatmnt.c b/src/solver/treatmnt.c index 0843b5f96..5defadc23 100644 --- a/src/solver/treatmnt.c +++ b/src/solver/treatmnt.c @@ -2,16 +2,18 @@ // treatmnt.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 03/19/15 (Build 5.1.008) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Pollutant treatment functions. // +// Update History +// ============== // Build 5.1.008: // - A bug in evaluating recursive calls to treatment functions was fixed. -// +// Build 5.2.0: +// - Changed enumerated constant used to indicate a math expression error. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -147,7 +149,7 @@ int treatmnt_readExpression(char* tok[], int ntoks) // variable's name into an index number) equation = mathexpr_create(expr, getVariableIndex); if ( equation == NULL ) - return error_setInpError(ERR_TREATMENT_EXPR, ""); + return error_setInpError(ERR_MATH_EXPR, ""); // --- save the treatment parameters in the node's treatment object Node[j].treatment[p].treatType = k; @@ -449,5 +451,3 @@ double getRemoval(int p) } return R[p]; } - -//============================================================================= diff --git a/src/solver/xsect.c b/src/solver/xsect.c index 9a4cc6dab..cfbb59121 100644 --- a/src/solver/xsect.c +++ b/src/solver/xsect.c @@ -2,11 +2,9 @@ // xsect.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) -// 03/14/17 (Build 5.1.012) -// 05/10/18 (Build 5.1.013) -// Author: L. Rossman (EPA) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) +// Author: L. Rossman // M. Tryby (EPA) // // Cross section geometry functions. @@ -26,16 +24,20 @@ // R = hyd. radius // S = section factor = A*R^(2/3) // +// Update History +// ============== // Build 5.1.012: // - Height at max. width for Modified Baskethandle shape corrected. -// // Build 5.1.013: // - Width at full height set to 0 for closed rectangular shape. +// Build 5.2.0: +// - Support added for Street cross sections. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE #include #include "headers.h" +#include "street.h" #include "findroot.h" #define RECT_ALFMAX 0.97 @@ -74,7 +76,8 @@ double Amax[] = { 0.96, // SEMICIRCULAR 1.0, // IRREGULAR 0.96, // CUSTOM - 0.9756}; // FORCE_MAIN + 0.9756, // FORCE_MAIN + 1.0}; // STREET_XSECT //----------------------------------------------------------------------------- // Shared variables @@ -92,6 +95,7 @@ typedef struct // xsect_isOpen // xsect_setParams // xsect_setIrregXsectParams +// xsect_setStreetXsectParams // xsect_setCustomXsectParams // xsect_getAmax // xsect_getSofA @@ -107,6 +111,8 @@ typedef struct //----------------------------------------------------------------------------- // Local functions //----------------------------------------------------------------------------- +static void getTransectParams(TXsect *xsect, TTransect *transect); + static double generic_getAofS(TXsect* xsect, double s); static void evalSofA(double a, double* f, double* df, void* p); static double tabular_getdSdA(TXsect* xsect, double a, double *table, int nItems); @@ -210,7 +216,7 @@ int xsect_setParams(TXsect *xsect, int type, double p[], double ucf) // // Input: xsect = ptr. to a cross section data structure // type = xsection shape type -// p[] = vector or xsection parameters +// p[] = vector of xsection parameters // ucf = units correction factor // Output: returns TRUE if successful, FALSE if not // Purpose: assigns parameters to a cross section's data structure. @@ -496,7 +502,7 @@ int xsect_setParams(TXsect *xsect, int type, double p[], double ucf) case TRIANGULAR: if ( p[1] <= 0.0 ) return FALSE; xsect->yFull = p[0]/ucf; - xsect->wMax = p[1]/ucf; + xsect->wMax = p[1]/ucf; xsect->ywMax = xsect->yFull; // --- slope of side walls @@ -634,30 +640,20 @@ void xsect_setIrregXsectParams(TXsect *xsect) // { int index = xsect->transect; - int i, iMax; - double wMax; - double* wTbl = Transect[index].widthTbl; - - xsect->yFull = Transect[index].yFull; - xsect->wMax = Transect[index].wMax; - xsect->aFull = Transect[index].aFull; - xsect->rFull = Transect[index].rFull; - xsect->sFull = xsect->aFull * pow(xsect->rFull, 2./3.); - xsect->sMax = Transect[index].sMax; - xsect->aBot = Transect[index].aMax; + getTransectParams(xsect, &Transect[index]); +} - // Search transect's width table up to point where width decreases - iMax = 0; - wMax = wTbl[0]; - for (i = 1; i < N_TRANSECT_TBL; i++) - { - if ( wTbl[i] < wMax ) break; - wMax = wTbl[i]; - iMax = i; - } +//============================================================================= - // Determine height at lowest widest point - xsect->ywMax = xsect->yFull * (double)iMax / (double)(N_TRANSECT_TBL-1); +void xsect_setStreetXsectParams(TXsect *xsect) +// +// Input: xsect = ptr. to a cross section data structure +// Output: none +// Purpose: assigns transect parameters to a street cross section. +// +{ + int index = xsect->transect; + getTransectParams(xsect, &Street[index].transect); } //============================================================================= @@ -687,9 +683,9 @@ void xsect_setCustomXsectParams(TXsect *xsect) wMax = wTbl[0]; for (i = 1; i < N_SHAPE_TBL; i++) { - if ( wTbl[i] < wMax ) break; - wMax = wTbl[i]; - iMax = i; + if ( wTbl[i] < wMax ) break; + wMax = wTbl[i]; + iMax = i; } // Determine height at lowest widest point @@ -823,6 +819,11 @@ double xsect_getYofA(TXsect *xsect, double a) return xsect->yFull * invLookup(alpha, Shape[Curve[xsect->transect].refersTo].areaTbl, N_SHAPE_TBL); + case STREET_XSECT: + return xsect->yFull * invLookup(alpha, + Street[xsect->transect].transect.areaTbl, + Street[xsect->transect].transect.nTbl); + case ARCH: return xsect->yFull * invLookup(alpha, A_Arch, N_A_Arch); @@ -907,6 +908,11 @@ double xsect_getAofY(TXsect *xsect, double y) return xsect->aFull * lookup(yNorm, Shape[Curve[xsect->transect].refersTo].areaTbl, N_SHAPE_TBL); + case STREET_XSECT: + return xsect->aFull * lookup(yNorm, + Street[xsect->transect].transect.areaTbl, + Street[xsect->transect].transect.nTbl); + case RECT_CLOSED: return y * xsect->wMax; case RECT_TRIANG: return rect_triang_getAofY(xsect, y); @@ -988,8 +994,13 @@ double xsect_getWofY(TXsect *xsect, double y) return xsect->wMax * lookup(yNorm, Shape[Curve[xsect->transect].refersTo].widthTbl, N_SHAPE_TBL); + case STREET_XSECT: + return xsect->wMax * lookup(yNorm, + Street[xsect->transect].transect.widthTbl, + Street[xsect->transect].transect.nTbl); + case RECT_CLOSED: - if (yNorm == 1.0) return 0.0; //(5.1.013) + if (yNorm == 1.0) return 0.0; return xsect->wMax; case RECT_TRIANG: return rect_triang_getWofY(xsect, y); @@ -1060,6 +1071,11 @@ double xsect_getRofY(TXsect *xsect, double y) return xsect->rFull * lookup(yNorm, Shape[Curve[xsect->transect].refersTo].hradTbl, N_SHAPE_TBL); + case STREET_XSECT: + return xsect->rFull * lookup(yNorm, + Street[xsect->transect].transect.hradTbl, + Street[xsect->transect].transect.nTbl); + case RECT_TRIANG: return rect_triang_getRofY(xsect, y); case RECT_ROUND: return rect_round_getRofY(xsect, y); @@ -1096,6 +1112,7 @@ double xsect_getRofA(TXsect *xsect, double a) case IRREGULAR: case FILLED_CIRCULAR: case CUSTOM: + case STREET_XSECT: return xsect_getRofY( xsect, xsect_getYofA(xsect, a) ); case RECT_CLOSED: return rect_closed_getRofA(xsect, a); @@ -1214,19 +1231,19 @@ double xsect_getdSdA(TXsect* xsect, double a) return rect_open_getdSdA(xsect, a); case RECT_TRIANG: - return rect_triang_getdSdA(xsect, a); + return rect_triang_getdSdA(xsect, a); case RECT_ROUND: - return rect_round_getdSdA(xsect, a); + return rect_round_getdSdA(xsect, a); case MOD_BASKET: - return mod_basket_getdSdA(xsect, a); + return mod_basket_getdSdA(xsect, a); case TRAPEZOIDAL: - return trapez_getdSdA(xsect, a); + return trapez_getdSdA(xsect, a); case TRIANGULAR: - return triang_getdSdA(xsect, a); + return triang_getdSdA(xsect, a); default: return generic_getdSdA(xsect, a); } @@ -1300,6 +1317,42 @@ double xsect_getYcrit(TXsect* xsect, double q) //============================================================================= +void getTransectParams(TXsect *xsect, TTransect *transect) +// +// Input: xsect = ptr. to a cross section data structure +// transect = ptr. to a transect data structure +// Output: none +// Purpose: gets a cross section's properties from its transect's properties. +// +{ + int i, iMax; + double wMax; + double* wTbl = transect->widthTbl; + + xsect->yFull = transect->yFull; + xsect->wMax = transect->wMax; + xsect->aFull = transect->aFull; + xsect->rFull = transect->rFull; + xsect->sFull = xsect->aFull * pow(xsect->rFull, 2. / 3.); + xsect->sMax = transect->sMax; + xsect->aBot = transect->aMax; + + // Search transect's width table up to point where width decreases + iMax = 0; + wMax = wTbl[0]; + for (i = 1; i < transect->nTbl; i++) + { + if (wTbl[i] < wMax) break; + wMax = wTbl[i]; + iMax = i; + } + + // Determine height at lowest widest point + xsect->ywMax = xsect->yFull * (double)iMax / (double)(transect->nTbl - 1); +} + +//============================================================================= + double generic_getAofS(TXsect* xsect, double s) // // Input: xsect = ptr. to a cross section data structure @@ -1537,13 +1590,13 @@ int locate(double y, double *table, int jLast) // While a portion of the table still remains while ( j2 - j1 > 1) { - // Find midpoint of remaining portion of table + // Find midpoint of remaining portion of table j = (j1 + j2) >> 1; - // Value is greater or equal to midpoint: search from midpoint to j2 + // Value is greater or equal to midpoint: search from midpoint to j2 if ( y >= table[j] ) j1 = j; - // Value is less than midpoint: search from j1 to midpoint + // Value is less than midpoint: search from j1 to midpoint else j2 = j; } @@ -2381,7 +2434,7 @@ double circ_getdSdA(TXsect* xsect, double a) if ( a1 < 0.0 ) { a1 = 0.0; - da = alpha + 0.001; + da = alpha + 0.001; } s1 = getScircular(a1); s2 = getScircular(a2); @@ -2560,5 +2613,3 @@ double getThetaOfPsi(double psi) } return theta1; } - -//============================================================================= diff --git a/src/solver/xsect.dat b/src/solver/xsect.dat index b154ea4e5..fa199bb40 100644 --- a/src/solver/xsect.dat +++ b/src/solver/xsect.dat @@ -2,8 +2,8 @@ // xsection.dat // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 03/20/14 (Build 5.1.001) +// Version: 5.2 +// Date: 03/24/21 (Build 5.2.0) // Author: L. Rossman // // Tables of relative geometric properties for rounded cross-sections. From 7977eaafdb69cbfdab97e66eae4628b373c89d27 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Wed, 31 Mar 2021 10:11:45 -0400 Subject: [PATCH 194/266] Update main.c --- src/run/main.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/run/main.c b/src/run/main.c index 20cfda408..cc99865b6 100644 --- a/src/run/main.c +++ b/src/run/main.c @@ -2,11 +2,11 @@ // main.c // // Project: EPA SWMM5 -// Version: 5.1 -// Date: 05/10/2018 +// Version: 5.2 +// Date: 03/24/2021 // Author: L. Rossman -// Main stub for the command line version of EPA SWMM 5.1 +// Main stub for the command line version of EPA SWMM 5.2 // to be run with swmm5.dll. #include @@ -19,9 +19,9 @@ int main(int argc, char *argv[]) // Input: argc = number of command line arguments // argv = array of command line arguments // Output: returns error status -// Purpose: runs the command line version of EPA SWMM 5.1. +// Purpose: runs the command line version of EPA SWMM 5.2. // -// Command line is: swmm5 f1 f2 f3 +// Command line is: runswmm f1 f2 f3 // where f1 = name of input file, f2 = name of report file, and // f3 = name of binary output file if saved (or blank if not saved). // @@ -56,12 +56,12 @@ int main(int argc, char *argv[]) if (strcmp(arg1, "--help") == 0 || strcmp(arg1, "-h") == 0) { // Help - printf("\n\nSTORMWATER MANAGEMENT MODEL (SWMM5) HELP\n\n"); + printf("\n\nSTORMWATER MANAGEMENT MODEL (SWMM) HELP\n\n"); printf("COMMANDS:\n"); - printf("\t--help (-h) Help Docs\n"); + printf("\t--help (-h) SWMM Help\n"); printf("\t--version (-v) Build Version\n"); printf("\nRUNNING A SIMULATION:\n"); - printf("\t swmm5

+// +// consists of: +// value / +// where is +// E.g.: Node 123 Depth > 4.5 +// Node 456 Depth < Node 123 Depth +// +// consists of: +// = setting +// E.g.: Pump abc status = OFF +// Weir xyz setting = 0.5 +// +// Update History +// ============== +// Build 5.1.008: +// - Support added for r.h.s. variables in rule premises. +// - Node volume added as a premise variable. +// Build 5.1.009: +// - Fixed problem with parsing a RHS premise variable. +// Build 5.1.010: +// - Support added for link TIMEOPEN & TIMECLOSED premises. +// Build 5.1.011: +// - Support added for DAYOFYEAR attribute. +// - Modulated controls no longer included in reported control actions. +// Build 5.2.0: +// - Additional attributes added to condition clauses. +// - Support added for named variables in condition clauses. +// - Support added for math expressions in condition clauses. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +enum RuleState {r_RULE, r_IF, r_AND, r_OR, r_THEN, r_ELSE, r_PRIORITY, + r_VARIABLE, r_EXPRESSION, r_ERROR}; +enum RuleObject {r_GAGE, r_NODE, r_LINK, r_CONDUIT, r_PUMP, r_ORIFICE, + r_WEIR, r_OUTLET, r_SIMULATION}; +enum RuleAttrib {r_DEPTH, r_MAXDEPTH, r_HEAD, r_VOLUME, r_INFLOW, + r_FLOW, r_FULLFLOW, r_FULLDEPTH, r_STATUS, r_SETTING, + r_LENGTH, r_SLOPE, r_VELOCITY, r_TIMEOPEN, r_TIMECLOSED, + r_TIME, r_DATE, r_CLOCKTIME, r_DAYOFYEAR, r_DAY, r_MONTH}; +enum RuleRelation {EQ, NE, LT, LE, GT, GE}; +enum RuleSetting {r_CURVE, r_TIMESERIES, r_PID, r_NUMERIC}; + +#define MAXVARNAME 32 + +static char* ObjectWords[] = + {"GAGE", "NODE", "LINK", "CONDUIT", "PUMP", "ORIFICE", "WEIR", "OUTLET", + "SIMULATION", NULL}; +static char* AttribWords[] = + {"DEPTH", "MAXDEPTH", "HEAD", "VOLUME", "INFLOW", + "FLOW", "FULLFLOW", "FULLDEPTH", "STATUS", "SETTING", + "LENGTH", "SLOPE", "VELOCITY", "TIMEOPEN", "TIMECLOSED", + "TIME", "DATE", "CLOCKTIME", "DAYOFYEAR", "DAY", "MONTH", NULL}; +static char* RelOpWords[] = {"=", "<>", "<", "<=", ">", ">=", NULL}; +static char* StatusWords[] = {"OFF", "ON", NULL}; +static char* ConduitWords[] = {"CLOSED", "OPEN", NULL}; +static char* SettingTypeWords[] = {"CURVE", "TIMESERIES", "PID", NULL}; +static char* IntensityWord = "INTENSITY"; + +//----------------------------------------------------------------------------- +// Data Structures +//----------------------------------------------------------------------------- +// Rule Premise Variable +struct TVariable +{ + int object; // type of object + int index; // index in object's array + int attribute; // object's attribute +}; + +// Named Variable +struct TNamedVariable +{ + struct TVariable variable; // a rule premise variable + char name[MAXVARNAME+1]; // name used in math expression +}; + +// Rule Premise Function +struct TExpression +{ + MathExpr* expression; // tokenized math expression + char name[MAXVARNAME+1]; // expression name +}; + +// Rule Premise Clause +struct TPremise +{ + int type; // clause type (IF/AND/OR) + int exprIndex; // expression index (-1 if N/A) + struct TVariable lhsVar; // left hand side variable + struct TVariable rhsVar; // right hand side variable + int relation; // relational operator (>, <, =, etc) + double value; // right hand side value + struct TPremise *next; // next premise clause of rule +}; + +// Rule Action Clause +struct TAction +{ + int rule; // index of rule that action belongs to + int link; // index of link being controlled + int attribute; // attribute of link being controlled + int curve; // index of curve for modulated control + int tseries; // index of time series for modulated control + double value; // control setting for link attribute + double kp, ki, kd; // coeffs. for PID modulated control + double e1, e2; // PID set point error from previous time steps + struct TAction *next; // next action clause of rule +}; + +// List of Control Actions +struct TActionList +{ + struct TAction* action; + struct TActionList* next; +}; + +// Control Rule +struct TRule +{ + char* ID; // rule ID + double priority; // priority level + struct TPremise* firstPremise; // pointer to first premise of rule + struct TPremise* lastPremise; // pointer to last premise of rule + struct TAction* thenActions; // linked list of actions if true + struct TAction* elseActions; // linked list of actions if false +}; + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +struct TRule* Rules; // array of control rules +struct TActionList* ActionList; // linked list of control actions +int InputState; // state of rule interpreter +int RuleCount; // total number of rules +double ControlValue; // value of controller variable +double SetPoint; // value of controller setpoint +DateTime CurrentDate; // current date in whole days +DateTime CurrentTime; // current time of day (decimal) + +int VariableCount; +int ExpressionCount; +int CurrentVariable; +int CurrentExpression; +struct TNamedVariable* NamedVariable; // array of named variables +struct TExpression* Expression; // array of math expressions + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// controls_create +// controls_delete +// controls_init +// controls_addToCount +// controls_addVariable +// controls_addExpression +// controls_addRuleClause +// controls_evaluate + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +int addPremise(int r, int type, char* Tok[], int nToks); +int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v); +int getPremiseValue(char* token, int attrib, double* value); +int addAction(int r, char* Tok[], int nToks); + +int evaluatePremise(struct TPremise* p, double tStep); +double getVariableValue(struct TVariable v); +int compareTimes(double lhsValue, int relation, double rhsValue, + double halfStep); +int compareValues(double lhsValue, int relation, double rhsValue); + +void updateActionList(struct TAction* a); +int executeActionList(DateTime currentTime); +void clearActionList(void); +void deleteActionList(void); +void deleteRules(void); + +int findExactMatch(char *s, char *keyword[]); +int setActionSetting(char* tok[], int nToks, int* curve, int* tseries, + int* attrib, double value[]); +void updateActionValue(struct TAction* a, DateTime currentTime, double dt); +double getPIDSetting(struct TAction* a, double dt); + +int getVariableIndex(char* varName); +double getNamedVariableValue(int varIndex); +int getExpressionIndex(char* exprName); +int getGageAttrib(char* token); +double getRainValue(struct TVariable v); + +//============================================================================= + +void controls_init() +// +// Input: none +// Output: none +// Purpose: initializes the control rule system. +// +{ + Rules = NULL; + NamedVariable = NULL; + Expression = NULL; + RuleCount = 0; + VariableCount = 0; + ExpressionCount = 0; +} + +//============================================================================= + +void controls_addToCount(char* s) +// +// Input: s = either VARIABLE or EXPRESSION +// Output: none +// Purpose: updates the number of named variables or math expressions used +// by control rules. +// +{ + if (match(s, w_VARIABLE)) VariableCount++; + else if (match(s, w_EXPRESSION)) ExpressionCount++; +} + +//============================================================================= + +int controls_create(int n) +// +// Input: n = total number of control rules +// Output: returns error code +// Purpose: creates an array of control rules. +// +{ + int r; + ActionList = NULL; + InputState = r_PRIORITY; + RuleCount = n; + if (RuleCount > 0) + { + Rules = (struct TRule *) calloc(RuleCount, sizeof(struct TRule)); + if (Rules == NULL) return ERR_MEMORY; + for ( r=0; r 0) + { + NamedVariable = (struct TNamedVariable *) calloc(VariableCount, + sizeof(struct TNamedVariable)); + if (NamedVariable == NULL) return ERR_MEMORY; + } + if (ExpressionCount > 0) + { + Expression = (struct TExpression *) calloc(ExpressionCount, + sizeof(struct TExpression)); + if (Expression == NULL) return ERR_MEMORY; + } + return 0; +} + +//============================================================================= + +void controls_delete(void) +// +// Input: none +// Output: none +// Purpose: deletes all control rules. +// +{ + int i; + + for (i = 0; i < ExpressionCount; i++) + { + mathexpr_delete(Expression[i].expression); + Expression[i].expression = NULL; + } + FREE(Expression); + FREE(NamedVariable); + + if ( RuleCount == 0 ) return; + deleteActionList(); + deleteRules(); +} + +//============================================================================= + +int controls_addVariable(char* tok[], int nToks) +// +// Input: tok = an array of string tokens +// n = the size of tok[] +// Output: returns error code +// Purpose: adds a named variable to the control rule system from a +// tokenized line of input with formats: +// VARIABLE name = Object id attribute +// VARIABLE name = SIMULATION attribute +// +{ + struct TVariable v1; + int k, err; + + CurrentVariable++; + if (nToks < 5) return ERR_ITEMS; + if (findExactMatch(tok[1], AttribWords) >= 0) + return error_setInpError(ERR_KEYWORD, tok[1]); + if (!match(tok[2], "=")) return error_setInpError(ERR_KEYWORD, tok[2]); + if (!match(tok[3], "SIMULATION") && nToks < 6) return ERR_ITEMS; + k = 3; + err = getPremiseVariable(tok, nToks, &k, &v1); + if (err > 0) return err; + k = CurrentVariable; + NamedVariable[k].variable = v1; + sstrncpy(NamedVariable[k].name, tok[1], MAXVARNAME); + return 0; +} + +//============================================================================= + +int controls_addExpression(char* tok[], int nToks) +// +// Input: tok = an array of string tokens +// n = number of tokens +// Output: returns error code +// Purpose: adds a math expression to the control rule system from a +// a tokenized line of input with format: +// EXPRESSION name = +// +{ + int i, k; + char s[MAXLINE + 1]; + MathExpr* expr; + + CurrentExpression++; + if (nToks < 4) return ERR_ITEMS; + k = CurrentExpression; + Expression[k].expression = NULL; + sstrncpy(Expression[k].name, tok[1], MAXVARNAME); + sstrncpy(s, tok[3], MAXLINE); + for (i = 4; i < nToks; i++) + { + sstrcat(s, " ", MAXLINE); + sstrcat(s, tok[i], MAXLINE); + } + + expr = mathexpr_create(s, getVariableIndex); + if (expr == NULL) + return error_setInpError(ERR_MATH_EXPR, ""); + + Expression[k].expression = expr; + return 0; +} + +//============================================================================= + +int getVariableIndex(char* varName) +// +// Input: varName = string containing a variable name +// Output: returns the index of the named variable or -1 if not found +// Purpose: finds the array index of a named variable. +// +{ + int i; + for (i = 0; i < VariableCount; i++) + { + if (match(varName, NamedVariable[i].name)) return i; + } + return -1; +} + +//============================================================================= + +double getNamedVariableValue(int varIndex) +// +// Input: varIndex = index of a named variable +// Output: returns the current value of the variable +// Purpose: finds the value of a named variable. +// +{ + return getVariableValue(NamedVariable[varIndex].variable); +} + +//============================================================================= + +int getExpressionIndex(char* exprName) +// +// Input: exprName = string containing an expression name +// Output: returns the index of the expression or -1 if not found +// Purpose: finds the array index of a math expression +// +{ + int i; + for (i = 0; i < ExpressionCount; i++) + { + if (match(exprName, Expression[i].name)) return i; + } + return -1; +} + +//============================================================================= + +int controls_addRuleClause(int r, int keyword, char* tok[], int nToks) +// +// Input: r = rule index +// keyword = the clause's keyword code (IF, THEN, etc.) +// tok = an array of string tokens that comprises the clause +// nToks = number of tokens +// Output: returns an error code +// Purpose: addd a new clause to a control rule. +// +{ + switch (keyword) + { + case r_RULE: + if ( Rules[r].ID == NULL ) + Rules[r].ID = project_findID(CONTROL, tok[1]); + InputState = r_RULE; + if ( nToks > 2 ) return ERR_RULE; + return 0; + + case r_IF: + if ( InputState != r_RULE ) return ERR_RULE; + InputState = r_IF; + return addPremise(r, r_AND, tok, nToks); + + case r_AND: + if ( InputState == r_IF ) return addPremise(r, r_AND, tok, nToks); + else if ( InputState == r_THEN || InputState == r_ELSE ) + return addAction(r, tok, nToks); + else return ERR_RULE; + + case r_OR: + if ( InputState != r_IF ) return ERR_RULE; + return addPremise(r, r_OR, tok, nToks); + + case r_THEN: + if ( InputState != r_IF ) return ERR_RULE; + InputState = r_THEN; + return addAction(r, tok, nToks); + + case r_ELSE: + if ( InputState != r_THEN ) return ERR_RULE; + InputState = r_ELSE; + return addAction(r, tok, nToks); + + case r_PRIORITY: + if ( InputState != r_THEN && InputState != r_ELSE ) return ERR_RULE; + InputState = r_PRIORITY; + if ( !getDouble(tok[1], &Rules[r].priority) ) return ERR_NUMBER; + if ( nToks > 2 ) return ERR_RULE; + return 0; + } + return 0; +} + +//============================================================================= + +int controls_evaluate(DateTime currentTime, DateTime elapsedTime, double tStep) +// +// Input: currentTime = current simulation date/time +// elapsedTime = decimal days since start of simulation +// tStep = simulation time step (days) +// Output: returns number of new actions taken +// Purpose: evaluates all control rules at current time of the simulation. +// +{ + int r; // control rule index + int result; // TRUE if rule premises satisfied + struct TPremise* p; // pointer to rule premise clause + struct TAction* a; // pointer to rule action clause + + // --- save date and time to shared variables + CurrentDate = floor(currentTime); + CurrentTime = currentTime - floor(currentTime); + ElapsedTime = elapsedTime; + + // --- evaluate each rule + if ( RuleCount == 0 ) return 0; + clearActionList(); + for (r=0; rtype == r_OR ) + { + if ( result == FALSE ) + result = evaluatePremise(p, tStep); + } + else + { + if ( result == FALSE ) break; + result = evaluatePremise(p, tStep); + } + p = p->next; + } + + // --- if premises true, add THEN clauses to action list + // else add ELSE clauses to action list + if ( result == TRUE ) a = Rules[r].thenActions; + else a = Rules[r].elseActions; + while (a) + { + updateActionValue(a, currentTime, tStep); + updateActionList(a); + a = a->next; + } + } + + // --- execute actions on action list + if ( ActionList ) return executeActionList(currentTime); + else return 0; +} + +//============================================================================= + +int addPremise(int r, int type, char* tok[], int nToks) +// +// Input: r = control rule index +// type = type of premise (IF, AND, OR) +// tok = array of string tokens containing premise statement +// nToks = number of string tokens +// Output: returns an error code +// Purpose: adds a new premise to a control rule. +// +{ + int relation, n, err = 0; + double value = MISSING; + struct TPremise* p; + struct TVariable v1; + struct TVariable v2; + int obj, exprIndex, varIndex = -1; + + // --- initialize LHS variable v1 + if (nToks < 4) return ERR_ITEMS; + v1.attribute = -1; + v1.object = -1; + v1.index = -1; + n = 1; + + // --- check if 2nd token is a math expression + exprIndex = getExpressionIndex(tok[1]); + + // --- if not then check if it's a named variable + if (exprIndex < 0) + { + varIndex = getVariableIndex(tok[n]); + if (varIndex >= 0) + { + v1 = NamedVariable[varIndex].variable; + } + + // otherwise parse object|index|attribute tokens + else + { + err = getPremiseVariable(tok, nToks, &n, &v1); + if ( err > 0 ) return err; + } + } + + // --- get relational operator + n++; + if ( n >= nToks ) return error_setInpError(ERR_ITEMS, ""); + relation = findExactMatch(tok[n], RelOpWords); + if ( relation < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); + + // --- initialize RHS variable v2 + v2.attribute = -1; + v2.object = -1; + v2.index = -1; + n++; + if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); + + // --- check for named RHS variable + varIndex = getVariableIndex(tok[n]); + if (varIndex >= 0) + { + v2 = NamedVariable[varIndex].variable; + } + + // --- check for object|index|attribute variable + else + { + obj = findmatch(tok[n], ObjectWords); + if (obj >= 0) + { + err = getPremiseVariable(tok, nToks, &n, &v2); + if ( err > 0 ) return ERR_RULE; + if (exprIndex < 0 && v1.attribute != v2.attribute) + report_writeWarningMsg(WARN11, Rules[r].ID); + } + + // --- check for a single RHS value + else + { + err = getPremiseValue(tok[n], v1.attribute, &value); + if ( err > 0 ) return err; + } + } + + // --- make sure another clause is not on same line + n++; + if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE; + + // --- create the premise object + p = (struct TPremise *) malloc(sizeof(struct TPremise)); + if ( !p ) return ERR_MEMORY; + p->type = type; + p->exprIndex = exprIndex; + p->lhsVar = v1; + p->rhsVar = v2; + p->relation = relation; + p->value = value; + p->next = NULL; + if ( Rules[r].firstPremise == NULL ) + { + Rules[r].firstPremise = p; + } + else + { + Rules[r].lastPremise->next = p; + } + Rules[r].lastPremise = p; + return 0; +} + +//============================================================================= + +int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v) +// +// Input: tok = array of string tokens +// nToks = number of tokens +// k = index of current token +// Output: returns an error code; updates k to new current token and +// places identity of specified variable in v +// Purpose: parses a variable (e.g., Node 123 Depth) used in a control rule. +// +{ + int n = *k; + int object = -1; + int index = -1; + int obj, attrib; + + // --- get object type + obj = findmatch(tok[n], ObjectWords); + if ( obj < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); + + // --- get object index from its name + n++; + if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); + switch (obj) + { + case r_GAGE: + index = project_findObject(GAGE, tok[n]); + if (index < 0) return error_setInpError(ERR_NAME, tok[n]); + object = r_GAGE; + break; + + case r_NODE: + index = project_findObject(NODE, tok[n]); + if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]); + object = r_NODE; + break; + + case r_LINK: + case r_CONDUIT: + case r_PUMP: + case r_ORIFICE: + case r_WEIR: + case r_OUTLET: + index = project_findObject(LINK, tok[n]); + if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]); + object = r_LINK; + break; + default: n--; + } + n++; + if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); + + // --- get attribute index from its name + if (object == r_GAGE) + attrib = getGageAttrib(tok[n]); + else + attrib = findmatch(tok[n], AttribWords); + if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); + + // --- check that attribute belongs to object type + if (obj == r_GAGE) + { + + } + + else if ( obj == r_NODE ) switch (attrib) + { + case r_DEPTH: + case r_MAXDEPTH: + case r_HEAD: + case r_VOLUME: + case r_INFLOW: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + + // --- check for link TIMEOPEN & TIMECLOSED attributes + else if ( object == r_LINK && index >= 0 && + ( (attrib == r_TIMEOPEN || attrib == r_TIMECLOSED) + )) + { + // nothing to do here + } + + else if ( obj == r_LINK || obj == r_CONDUIT ) switch (attrib) + { + case r_STATUS: + case r_DEPTH: + case r_FULLFLOW: + case r_FULLDEPTH: + case r_FLOW: + case r_LENGTH: + case r_SLOPE: + case r_VELOCITY: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + else if ( obj == r_PUMP ) switch (attrib) + { + case r_FLOW: + case r_SETTING: + case r_STATUS: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + else if ( obj == r_ORIFICE || obj == r_WEIR || + obj == r_OUTLET ) switch (attrib) + { + case r_FLOW: + case r_SETTING: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + else switch (attrib) + { + case r_TIME: + case r_DATE: + case r_CLOCKTIME: + case r_DAY: + case r_MONTH: + case r_DAYOFYEAR: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + + // --- populate variable structure + v->object = object; + v->index = index; + v->attribute = attrib; + *k = n; + return 0; +} + +//============================================================================= + +int getGageAttrib(char* token) +// +// Input: token = a string token +// Output: returns an attribute code or -1 if an error occurred +// Purpose: determines the atrribute code for a rain gage variable. +// Note: a valid token is INTENSITY for current rainfall intensity +// (attribute code = 0) or nHR_PRECIP for total rain depth +// over past n hours (attribute code = n). +// +{ + int attrib; + + // --- check if token is currrent rainfall intensity + if (match(token, IntensityWord)) + return 0; + + // --- token is past rain depth - read number of past hours + attrib = atoi(token); + + // --- check that number of hours is in allowable range + if (attrib < 1 || attrib > MAXPASTRAIN) + return -1; + return attrib; +} + +//============================================================================= + +int getPremiseValue(char* token, int attrib, double* value) +// +// Input: token = a string token +// attrib = index of a node/link attribute +// Output: value = attribute value; +// returns an error code; +// Purpose: parses the numerical value of a particular node/link attribute +// in the premise clause of a control rule. +// +{ + char strDate[25]; + switch (attrib) + { + case r_STATUS: + *value = findmatch(token, StatusWords); + if ( *value < 0.0 ) *value = findmatch(token, ConduitWords); + if ( *value < 0.0 ) return error_setInpError(ERR_KEYWORD, token); + break; + + case r_TIME: + case r_CLOCKTIME: + case r_TIMEOPEN: + case r_TIMECLOSED: + if ( !datetime_strToTime(token, value) ) + return error_setInpError(ERR_DATETIME, token); + break; + + case r_DATE: + if ( !datetime_strToDate(token, value) ) + return error_setInpError(ERR_DATETIME, token); + break; + + case r_DAY: + if ( !getDouble(token, value) ) + return error_setInpError(ERR_NUMBER, token); + if ( *value < 1.0 || *value > 7.0 ) + return error_setInpError(ERR_DATETIME, token); + break; + + case r_MONTH: + if ( !getDouble(token, value) ) + return error_setInpError(ERR_NUMBER, token); + if ( *value < 1.0 || *value > 12.0 ) + return error_setInpError(ERR_DATETIME, token); + break; + + case r_DAYOFYEAR: + sstrncpy(strDate, token, 6); + sstrcat(strDate, "/1947", 25); + if ( datetime_strToDate(strDate, value) ) + { + *value = datetime_dayOfYear(*value); + } + else if ( !getDouble(token, value) || *value < 1 || *value > 365 ) + return error_setInpError(ERR_DATETIME, token); + break; + + default: if ( !getDouble(token, value) ) + return error_setInpError(ERR_NUMBER, token); + } + return 0; +} + +//============================================================================= + +int addAction(int r, char* tok[], int nToks) +// +// Input: r = control rule index +// tok = array of string tokens containing action statement +// nToks = number of string tokens +// Output: returns an error code +// Purpose: adds a new action to a control rule. +// +{ + int obj, link, attrib; + int curve = -1, tseries = -1; + int n; + int err; + double values[] = {1.0, 0.0, 0.0}; + + struct TAction* a; + + // --- check for proper number of tokens + if ( nToks < 6 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check for valid object type + obj = findmatch(tok[1], ObjectWords); + if ( obj != r_LINK && obj != r_CONDUIT && obj != r_PUMP && + obj != r_ORIFICE && obj != r_WEIR && obj != r_OUTLET ) + return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- check that object name exists and is of correct type + link = project_findObject(LINK, tok[2]); + if ( link < 0 ) return error_setInpError(ERR_NAME, tok[2]); + switch (obj) + { + case r_CONDUIT: + if ( Link[link].type != CONDUIT ) + return error_setInpError(ERR_NAME, tok[2]); + break; + case r_PUMP: + if ( Link[link].type != PUMP ) + return error_setInpError(ERR_NAME, tok[2]); + break; + case r_ORIFICE: + if ( Link[link].type != ORIFICE ) + return error_setInpError(ERR_NAME, tok[2]); + break; + case r_WEIR: + if ( Link[link].type != WEIR ) + return error_setInpError(ERR_NAME, tok[2]); + break; + case r_OUTLET: + if ( Link[link].type != OUTLET ) + return error_setInpError(ERR_NAME, tok[2]); + break; + } + + // --- check for valid attribute name + attrib = findmatch(tok[3], AttribWords); + if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); + + // --- get control action setting + if ( obj == r_CONDUIT ) + { + if ( attrib == r_STATUS ) + { + values[0] = findmatch(tok[5], ConduitWords); + if ( values[0] < 0.0 ) + return error_setInpError(ERR_KEYWORD, tok[5]); + } + else return error_setInpError(ERR_KEYWORD, tok[3]); + } + + else if ( obj == r_PUMP ) + { + if ( attrib == r_STATUS ) + { + values[0] = findmatch(tok[5], StatusWords); + if ( values[0] < 0.0 ) + return error_setInpError(ERR_KEYWORD, tok[5]); + } + else if ( attrib == r_SETTING ) + { + err = setActionSetting(tok, nToks, &curve, &tseries, + &attrib, values); + if ( err > 0 ) return err; + } + else return error_setInpError(ERR_KEYWORD, tok[3]); + } + + else if ( obj == r_ORIFICE || obj == r_WEIR || obj == r_OUTLET ) + { + if ( attrib == r_SETTING ) + { + err = setActionSetting(tok, nToks, &curve, &tseries, + &attrib, values); + if ( err > 0 ) return err; + if ( attrib == r_SETTING + && (values[0] < 0.0 || values[0] > 1.0) ) + return error_setInpError(ERR_NUMBER, tok[5]); + } + else return error_setInpError(ERR_KEYWORD, tok[3]); + } + else return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- check if another clause is on same line + n = 6; + if ( curve >= 0 || tseries >= 0 ) n = 7; + if ( attrib == r_PID ) n = 9; + if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE; + + // --- create the action object + a = (struct TAction *) malloc(sizeof(struct TAction)); + if ( !a ) return ERR_MEMORY; + a->rule = r; + a->link = link; + a->attribute = attrib; + a->curve = curve; + a->tseries = tseries; + a->value = values[0]; + if ( attrib == r_PID ) + { + a->kp = values[0]; + a->ki = values[1]; + a->kd = values[2]; + a->e1 = 0.0; + a->e2 = 0.0; + } + if ( InputState == r_THEN ) + { + a->next = Rules[r].thenActions; + Rules[r].thenActions = a; + } + else + { + a->next = Rules[r].elseActions; + Rules[r].elseActions = a; + } + return 0; +} + +//============================================================================= + +int setActionSetting(char* tok[], int nToks, int* curve, int* tseries, + int* attrib, double values[]) +// +// Input: tok = array of string tokens containing action statement +// nToks = number of string tokens +// Output: curve = index of controller curve +// tseries = index of controller time series +// attrib = r_PID if PID controller used +// values = values of control settings +// returns an error code +// Purpose: identifies how control actions settings are determined. +// +{ + int k, m; + + // --- see if control action is determined by a Curve or Time Series + if (nToks < 6) return error_setInpError(ERR_ITEMS, ""); + k = findmatch(tok[5], SettingTypeWords); + if ( k >= 0 && nToks < 7 ) return error_setInpError(ERR_ITEMS, ""); + switch (k) + { + + // --- control determined by a curve - find curve index + case r_CURVE: + m = project_findObject(CURVE, tok[6]); + if ( m < 0 ) return error_setInpError(ERR_NAME, tok[6]); + *curve = m; + break; + + // --- control determined by a time series - find time series index + case r_TIMESERIES: + m = project_findObject(TSERIES, tok[6]); + if ( m < 0 ) return error_setInpError(ERR_NAME, tok[6]); + *tseries = m; + Tseries[m].refersTo = CONTROL; + break; + + // --- control determined by PID controller + case r_PID: + if (nToks < 9) return error_setInpError(ERR_ITEMS, ""); + for (m=6; m<=8; m++) + { + if ( !getDouble(tok[m], &values[m-6]) ) + return error_setInpError(ERR_NUMBER, tok[m]); + } + *attrib = r_PID; + break; + + // --- direct numerical control is used + default: + if ( !getDouble(tok[5], &values[0]) ) + return error_setInpError(ERR_NUMBER, tok[5]); + } + return 0; +} + +//============================================================================= + +void updateActionValue(struct TAction* a, DateTime currentTime, double dt) +// +// Input: a = an action object +// currentTime = current simulation date/time (days) +// dt = time step (days) +// Output: none +// Purpose: updates value of actions found from Curves or Time Series. +// +{ + if ( a->curve >= 0 ) + { + a->value = table_lookup(&Curve[a->curve], ControlValue); + } + else if ( a->tseries >= 0 ) + { + a->value = table_tseriesLookup(&Tseries[a->tseries], currentTime, TRUE); + } + else if ( a->attribute == r_PID ) + { + a->value = getPIDSetting(a, dt); + } +} + +//============================================================================= + +double getPIDSetting(struct TAction* a, double dt) +// +// Input: a = an action object +// dt = current time step (days) +// Output: returns a new link setting +// Purpose: computes a new setting for a link subject to a PID controller. +// +// Note: a->kp = gain coefficient, +// a->ki = integral time (minutes) +// a->k2 = derivative time (minutes) +// a->e1 = error from previous time step +// a->e2 = error from two time steps ago +{ + double e0, setting; + double p, i, d, update; + double tolerance = 0.0001; + + // --- convert time step from days to minutes + dt *= 1440.0; + + // --- determine relative error in achieving controller set point + e0 = SetPoint - ControlValue; + if ( fabs(e0) > TINY ) + { + if ( SetPoint != 0.0 ) e0 = e0/SetPoint; + else e0 = e0/ControlValue; + } + + // --- reset previous errors to 0 if controller gets stuck + if (fabs(e0 - a->e1) < tolerance) + { + a->e2 = 0.0; + a->e1 = 0.0; + } + + // --- use the recursive form of the PID controller equation to + // determine the new setting for the controlled link + p = (e0 - a->e1); + if ( a->ki == 0.0 ) i = 0.0; + else i = e0 * dt / a->ki; + d = a->kd * (e0 - 2.0*a->e1 + a->e2) / dt; + update = a->kp * (p + i + d); + if ( fabs(update) < tolerance ) update = 0.0; + setting = Link[a->link].targetSetting + update; + + // --- update previous errors + a->e2 = a->e1; + a->e1 = e0; + + // --- check that new setting lies within feasible limits + if ( setting < 0.0 ) setting = 0.0; + if (Link[a->link].type != PUMP && setting > 1.0 ) setting = 1.0; + return setting; +} + +//============================================================================= + +void updateActionList(struct TAction* a) +// +// Input: a = an action object +// Output: none +// Purpose: adds a new action to the list of actions to be taken. +// +{ + struct TActionList* listItem; + struct TAction* a1; + double priority = Rules[a->rule].priority; + + // --- check if link referred to in action is already listed + listItem = ActionList; + while ( listItem ) + { + a1 = listItem->action; + if ( !a1 ) break; + if ( a1->link == a->link ) + { + // --- replace old action if new action has higher priority + if ( priority > Rules[a1->rule].priority ) listItem->action = a; + return; + } + listItem = listItem->next; + } + + // --- action not listed so add it to ActionList + listItem = (struct TActionList *) malloc(sizeof(struct TActionList)); + if (listItem) + { + listItem->next = ActionList; + ActionList = listItem; + listItem->action = a; + } +} + +//============================================================================= + +int executeActionList(DateTime currentTime) +// +// Input: currentTime = current date/time of the simulation +// Output: returns number of new actions taken +// Purpose: executes all actions required by fired control rules. +// +{ + struct TActionList* listItem; + struct TActionList* nextItem; + struct TAction* a1; + int count = 0; + + listItem = ActionList; + while ( listItem ) + { + a1 = listItem->action; + if ( !a1 ) break; + if ( a1->link >= 0 ) + { + if ( Link[a1->link].targetSetting != a1->value ) + { + Link[a1->link].targetSetting = a1->value; + if ( RptFlags.controls && a1->curve < 0 + && a1->tseries < 0 && a1->attribute != r_PID ) + report_writeControlAction(currentTime, Link[a1->link].ID, + a1->value, Rules[a1->rule].ID); + count++; + } + } + nextItem = listItem->next; + listItem = nextItem; + } + return count; +} + +//============================================================================= + +int evaluatePremise(struct TPremise* p, double tStep) +// +// Input: p = a control rule premise condition +// tStep = current time step (days) +// Output: returns TRUE if the condition is true or FALSE otherwise +// Purpose: evaluates the truth of a control rule premise condition. +// +{ + double lhsValue, rhsValue; + int result = FALSE; + + // --- check if left hand side (lhs) of premise is an expression + if (p->exprIndex >= 0) + lhsValue = mathexpr_eval(Expression[p->exprIndex].expression, + getNamedVariableValue); + + // --- otherwise get value of the lhs variable + else + lhsValue = getVariableValue(p->lhsVar); + + // --- if right hand side (rhs) of premise is a variable then get its value + if ( p->value == MISSING ) rhsValue = getVariableValue(p->rhsVar); + else rhsValue = p->value; + if ( lhsValue == MISSING || rhsValue == MISSING ) return FALSE; + + // --- compare the lhs of the premise to the rhs + switch (p->lhsVar.attribute) + { + case r_TIME: + case r_CLOCKTIME: + return compareTimes(lhsValue, p->relation, rhsValue, tStep/2.0); + case r_TIMEOPEN: + case r_TIMECLOSED: + result = compareTimes(lhsValue, p->relation, rhsValue, tStep/2.0); + ControlValue = lhsValue * 24.0; // convert time from days to hours + return result; + default: + return compareValues(lhsValue, p->relation, rhsValue); + } +} + +//============================================================================= + +double getVariableValue(struct TVariable v) +{ + int i = -1; // a node index + int j = -1; // a link index + + if (v.object == r_GAGE) + return getRainValue(v); + if (v.object == r_NODE) i = v.index; + if (v.object == r_LINK) j = v.index; + + switch ( v.attribute ) + { + case r_TIME: + return ElapsedTime; + + case r_DATE: + return CurrentDate; + + case r_CLOCKTIME: + return CurrentTime; + + case r_DAY: + return datetime_dayOfWeek(CurrentDate); + + case r_MONTH: + return datetime_monthOfYear(CurrentDate); + + case r_DAYOFYEAR: + return datetime_dayOfYear(CurrentDate); + + case r_STATUS: + if ( j < 0 || + (Link[j].type != CONDUIT && Link[j].type != PUMP) ) return MISSING; + else return Link[j].setting; + + case r_SETTING: + if ( j < 0 || (Link[j].type != PUMP && + Link[j].type != ORIFICE && + Link[j].type != WEIR) ) + return MISSING; + else return Link[j].setting; + + case r_FLOW: + if ( j < 0 ) return MISSING; + else return Link[j].direction*Link[j].newFlow*UCF(FLOW); + + case r_FULLFLOW: + case r_FULLDEPTH: + case r_VELOCITY: + case r_LENGTH: + case r_SLOPE: + if ( j < 0 ) return MISSING; + else if (Link[j].type != CONDUIT) return MISSING; + switch (v.attribute) + { + case r_FULLFLOW: return Link[j].qFull * UCF(FLOW); + case r_FULLDEPTH: return Link[j].xsect.yFull * UCF(LENGTH); + case r_VELOCITY: + return link_getVelocity(j, Link[j].newFlow, Link[j].newDepth) + * UCF(LENGTH); + case r_LENGTH: return Conduit[Link[j].subIndex].length * UCF(LENGTH); + case r_SLOPE: return Conduit[Link[j].subIndex].slope; + default: return MISSING; + } + case r_DEPTH: + if ( j >= 0 ) return Link[j].newDepth*UCF(LENGTH); + else if ( i >= 0 ) + return Node[i].newDepth*UCF(LENGTH); + else return MISSING; + + case r_MAXDEPTH: + if (i >= 0) return Node[i].fullDepth*UCF(LENGTH); + else return MISSING; + + case r_HEAD: + if ( i < 0 ) return MISSING; + return (Node[i].newDepth + Node[i].invertElev) * UCF(LENGTH); + + case r_VOLUME: + if ( i < 0 ) return MISSING; + return (Node[i].newVolume * UCF(VOLUME)); + + case r_INFLOW: + if ( i < 0 ) return MISSING; + else return Node[i].newLatFlow*UCF(FLOW); + + case r_TIMEOPEN: + if ( j < 0 ) return MISSING; + if ( Link[j].setting <= 0.0 ) return MISSING; + return CurrentDate + CurrentTime - Link[j].timeLastSet; + + case r_TIMECLOSED: + if ( j < 0 ) return MISSING; + if ( Link[j].setting > 0.0 ) return MISSING; + return CurrentDate + CurrentTime - Link[j].timeLastSet; + + default: return MISSING; + } +} + +//============================================================================= + +double getRainValue(struct TVariable v) +// +// Input: v = a rule premise variable for a rain gage +// Output: returns current or past rainfall amount +// Purpose: retrieves either the current rainfall intensity or the past +// rainfall total for a rain gage. +// +{ + if (v.index < 0) return MISSING; + else if (Gage[v.index].isUsed == FALSE) return 0.0; + else if (v.attribute == 0) + return Gage[v.index].rainfall; + else return gage_getPastRain(v.index, v.attribute); +} + +//============================================================================= + +int compareTimes(double lhsValue, int relation, double rhsValue, double halfStep) +// +// Input: lhsValue = date/time value on left hand side of relation +// relation = relational operator code (see RuleRelation enumeration) +// rhsValue = date/time value on right hand side of relation +// halfStep = 1/2 the current time step (days) +// Output: returns TRUE if time relation is satisfied +// Purpose: evaluates the truth of a relation between two date/times. +// +{ + if ( relation == EQ ) + { + if ( lhsValue >= rhsValue - halfStep + && lhsValue < rhsValue + halfStep ) return TRUE; + return FALSE; + } + else if ( relation == NE ) + { + if ( lhsValue < rhsValue - halfStep + || lhsValue >= rhsValue + halfStep ) return TRUE; + return FALSE; + } + else return compareValues(lhsValue, relation, rhsValue); +} + +//============================================================================= + +int compareValues(double lhsValue, int relation, double rhsValue) +// Input: lhsValue = value on left hand side of relation +// relation = relational operator code (see RuleRelation enumeration) +// rhsValue = value on right hand side of relation +// Output: returns TRUE if relation is satisfied +// Purpose: evaluates the truth of a relation between two values. +{ + SetPoint = rhsValue; + ControlValue = lhsValue; + switch (relation) + { + case EQ: if ( lhsValue == rhsValue ) return TRUE; break; + case NE: if ( lhsValue != rhsValue ) return TRUE; break; + case LT: if ( lhsValue < rhsValue ) return TRUE; break; + case LE: if ( lhsValue <= rhsValue ) return TRUE; break; + case GT: if ( lhsValue > rhsValue ) return TRUE; break; + case GE: if ( lhsValue >= rhsValue ) return TRUE; break; + } + return FALSE; +} + +//============================================================================= + +void clearActionList(void) +// +// Input: none +// Output: none +// Purpose: clears the list of actions to be executed. +// +{ + struct TActionList* listItem; + listItem = ActionList; + while ( listItem ) + { + listItem->action = NULL; + listItem = listItem->next; + } +} + +//============================================================================= + +void deleteActionList(void) +// +// Input: none +// Output: none +// Purpose: frees the memory used to hold the list of actions to be executed. +// +{ + struct TActionList* listItem; + struct TActionList* nextItem; + listItem = ActionList; + while ( listItem ) + { + nextItem = listItem->next; + free(listItem); + listItem = nextItem; + } + ActionList = NULL; +} + +//============================================================================= + +void deleteRules(void) +// +// Input: none +// Output: none +// Purpose: frees the memory used for all of the control rules. +// +{ + struct TPremise* p; + struct TPremise* pnext; + struct TAction* a; + struct TAction* anext; + int r; + for (r=0; rnext; + free(p); + p = pnext; + } + a = Rules[r].thenActions; + while (a ) + { + anext = a->next; + free(a); + a = anext; + } + a = Rules[r].elseActions; + while (a ) + { + anext = a->next; + free(a); + a = anext; + } + } + FREE(Rules); + RuleCount = 0; +} + +//============================================================================= + +int findExactMatch(char *s, char *keyword[]) +// +// Input: s = character string +// keyword = array of keyword strings +// Output: returns index of keyword which matches s or -1 if no match found +// Purpose: finds exact match between string and array of keyword strings. +// +{ + int i = 0; + while (keyword[i] != NULL) + { + if ( strcomp(s, keyword[i]) ) return(i); + i++; + } + return(-1); +} + +//============================================================================= diff --git a/src/culvert.c b/src/culvert.c new file mode 100644 index 000000000..5f62877b7 --- /dev/null +++ b/src/culvert.c @@ -0,0 +1,411 @@ +//----------------------------------------------------------------------------- +// culvert.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Culvert equations for SWMM5 +// +// Computes flow reduction in a culvert-type conduit due to +// inlet control using equations from the FHWA HEC-5 circular. +// +// Update History +// ============== +// Build 5.1.013: +// - C parameter corrected for Arch, Corrugated Metal, Mitered culvert. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "findroot.h" +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +enum CulvertParam {FORM, K, M, C, Y}; +static const int MAX_CULVERT_CODE = 57; +static const double Params[58][5] = { + +// FORM K M C Y +//------------------------------------ + {0.0, 0.0, 0.0, 0.0, 0.00}, + + //Circular concrete + {1.0, 0.0098, 2.00, 0.0398, 0.67}, //Square edge w/headwall + {1.0, 0.0018, 2.00, 0.0292, 0.74}, //Groove end w/headwall + {1.0, 0.0045, 2.00, 0.0317, 0.69}, //Groove end projecting + + //Circular Corrugated Metal Pipe + {1.0, 0.0078, 2.00, 0.0379, 0.69}, //Headwall + {1.0, 0.0210, 1.33, 0.0463, 0.75}, //Mitered to slope + {1.0, 0.0340, 1.50, 0.0553, 0.54}, //Projecting + + //Circular Pipe, Beveled Ring Entrance + {1.0, 0.0018, 2.50, 0.0300, 0.74}, //Beveled ring, 45 deg bevels + {1.0, 0.0018, 2.50, 0.0243, 0.83}, //Beveled ring, 33.7 deg bevels + + //Rectangular Box with Flared Wingwalls + {1.0, 0.026, 1.0, 0.0347, 0.81}, //30-75 deg. wingwall flares + {1.0, 0.061, 0.75, 0.0400, 0.80}, //90 or 15 deg. wingwall flares + {1.0, 0.061, 0.75, 0.0423, 0.82}, //0 deg. wingwall flares (striaght sides) + + //Rectanglar Box with Flared Wingwalls & Top Edge Bevel + {2.0, 0.510, 0.667, 0.0309, 0.80}, //45 deg. flare; 0.43D top edge bevel + {2.0, 0.486, 0.667, 0.0249, 0.83}, //18-33.7 deg flare; 0.083D top edge bevel + + //Rectangular Box; 90-deg Headwall; Chamfered or Beveled Inlet Edges + {2.0, 0.515, 0.667, 0.0375, 0.79}, //chamfered 3/4-in + {2.0, 0.495, 0.667, 0.0314, 0.82}, //beveled 1/2-in/ft at 45 deg (1:1) + {2.0, 0.486, 0.667, 0.0252, 0.865}, //beveled 1-in/ft at 33.7 deg (1:1.5) + + //Rectangular Box; Skewed Headwall; Chamfered or Beveled Inlet Edges + {2.0, 0.545, 0.667, 0.04505,0.73}, //3/4" chamfered edge, 45 deg skewed headwall + {2.0, 0.533, 0.667, 0.0425, 0.705}, //3/4" chamfered edge, 30 deg skewed headwall + {2.0, 0.522, 0.667, 0.0402, 0.68}, //3/4" chamfered edge, 15 deg skewed headwall + {2.0, 0.498, 0.667, 0.0327, 0.75}, //45 deg beveled edge, 10-45 deg skewed headwall + + //Rectangular box, Non-offset Flared Wingwalls; 3/4" Chamfer at Top of Inlet + {2.0, 0.497, 0.667, 0.0339, 0.803}, //45 deg (1:1) wingwall flare + {2.0, 0.493, 0.667, 0.0361, 0.806}, //18.4 deg (3:1) wingwall flare + {2.0, 0.495, 0.667, 0.0386, 0.71}, //18.4 deg (3:1) wingwall flare, 30 deg inlet skew + + //Rectangular box, Offset Flared Wingwalls, Beveled Edge at Inlet Top + {2.0, 0.497, 0.667, 0.0302, 0.835}, //45 deg (1:1) flare, 0.042D top edge bevel + {2.0, 0.495, 0.667, 0.0252, 0.881}, //33.7 deg (1.5:1) flare, 0.083D top edge bevel + {2.0, 0.493, 0.667, 0.0227, 0.887}, //18.4 deg (3:1) flare, 0.083D top edge bevel + + // Corrugated Metal Box + {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall + {1.0, 0.0145, 1.75, 0.0419, 0.64}, //Thick wall projecting + {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Thin wall projecting + + // Horizontal Ellipse Concrete + {1.0, 0.0100, 2.00, 0.0398, 0.67}, //Square edge w/headwall + {1.0, 0.0018, 2.50, 0.0292, 0.74}, //Grooved end w/headwall + {1.0, 0.0045, 2.00, 0.0317, 0.69}, //Grooved end projecting + + // Vertical Ellipse Concrete + {1.0, 0.0100, 2.00, 0.0398, 0.67}, //Square edge w/headwall + {1.0, 0.0018, 2.50, 0.0292, 0.74}, //Grooved end w/headwall + {1.0, 0.0095, 2.00, 0.0317, 0.69}, //Grooved end projecting + + // Pipe Arch, 18" Corner Radius, Corrugated Metal + {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall + {1.0, 0.0300, 1.00, 0.0463, 0.75}, //Mitered to slope + {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Projecting + + // Pipe Arch, 18" Corner Radius, Corrugated Metal + {1.0, 0.0300, 1.50, 0.0496, 0.57}, //Projecting + {1.0, 0.0088, 2.00, 0.0368, 0.68}, //No bevels + {1.0, 0.0030, 2.00, 0.0269, 0.77}, //33.7 deg bevels + + // Pipe Arch, 31" Corner Radius, Corrugated Metal + {1.0, 0.0300, 1.50, 0.0496, 0.57}, //Projecting + {1.0, 0.0088, 2.00, 0.0368, 0.68}, //No bevels + {1.0, 0.0030, 2.00, 0.0269, 0.77}, //33.7 deg. bevels + + // Arch, Corrugated Metal + {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall + {1.0, 0.0300, 1.00, 0.0473, 0.75}, //Mitered to slope + {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Thin wall projecting + + // Circular Culvert + {2.0, 0.534, 0.555, 0.0196, 0.90}, //Smooth tapered inlet throat + {2.0, 0.519, 0.640, 0.0210, 0.90}, //Rough tapered inlet throat + + // Elliptical Inlet Face + {2.0, 0.536, 0.622, 0.0368, 0.83}, //Tapered inlet, beveled edges + {2.0, 0.5035,0.719, 0.0478, 0.80}, //Tapered inlet, square edges + {2.0, 0.547, 0.800, 0.0598, 0.75}, //Tapered inlet, thin edge projecting + + // Rectangular + {2.0, 0.475, 0.667, 0.0179, 0.97}, //Tapered inlet throat + + // Rectangular Concrete + {2.0, 0.560, 0.667, 0.0446, 0.85}, //Side tapered, less favorable edges + {2.0, 0.560, 0.667, 0.0378, 0.87}, //Side tapered, more favorable edges + + // Rectangular Concrete + {2.0, 0.500, 0.667, 0.0446, 0.65}, //Slope tapered, less favorable edges + {2.0, 0.500, 0.667, 0.0378, 0.71} //Slope tapered, more favorable edges + + }; + +//----------------------------------------------------------------------------- +// Culvert data structure +//----------------------------------------------------------------------------- +typedef struct +{ + double yFull; // full depth of culvert (ft) + double scf; // slope correction factor + double dQdH; // Derivative of flow w.r.t. head + double qc; // Unsubmerged critical flow + double kk; + double mm; // Coeffs. for unsubmerged flow + double ad; + double hPlus; // Intermediate terms + TXsect* xsect; // Pointer to culvert cross section +} TCulvert; + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// double culvert_getInflow + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static double getUnsubmergedFlow(int code, double h, TCulvert* culvert); +static double getSubmergedFlow(int code, double h, TCulvert* culvert); +static double getTransitionFlow(int code, double h, double h1, double h2, + TCulvert* culvert); +static double getForm1Flow(double h, TCulvert* culvert); +static double form1Eqn(double yc, void* p); +/* +static void report_CulvertControl(int j, double q0, double q, int condition, + double yRatio); //for debugging only +*/ + +//============================================================================= + +double culvert_getInflow(int j, double q0, double h) +// +// Input: j = link index +// q0 = unmodified flow rate (cfs) +// h = upstream head (ft) +// Output: returns modified flow rate through culvert (cfs) +// Purpose: uses FHWA HEC-5 equations to find flow through inlet +// controlled culverts +// +{ + int code, //culvert type code number + k, //conduit index + condition; //flow condition + double y, //current depth (ft) + y1, //unsubmerged depth limit (ft) + y2, //submerged depth limit (ft) + q; //inlet-controlled flow (cfs) + TCulvert culvert; //intermediate results + + // --- check that we have a culvert conduit + if ( Link[j].type != CONDUIT ) return q0; + culvert.xsect = &Link[j].xsect; + code = culvert.xsect->culvertCode; + if ( code <= 0 || code > MAX_CULVERT_CODE ) return q0; + + // --- compute often-used variables + k = Link[j].subIndex; + culvert.yFull = culvert.xsect->yFull; + culvert.ad = culvert.xsect->aFull * sqrt(culvert.yFull); + + // --- slope correction factor (-7 for mitered inlets, 0.5 for others) + switch (code) + { + case 5: + case 37: + case 46: culvert.scf = -7.0 * Conduit[k].slope; break; + default: culvert.scf = 0.5 * Conduit[k].slope; + } + + // --- find head relative to culvert's upstream invert + // (can be greater than yFull when inlet is submerged) + y = h - (Node[Link[j].node1].invertElev + Link[j].offset1); + + // --- check for submerged flow (based on FHWA criteria of Q/AD > 4) + y2 = culvert.yFull * (16.0 * Params[code][C] + Params[code][Y] - culvert.scf); + if ( y >= y2 ) + { + q = getSubmergedFlow(code, y, &culvert); + condition = 2; + } + else + { + // --- check for unsubmerged flow (based on arbitrary limit of 0.95 full) + y1 = 0.95 * culvert.yFull; + if ( y <= y1 ) + { + q = getUnsubmergedFlow(code, y, &culvert); + condition = 1; + } + // --- flow is in transition zone + else + { + q = getTransitionFlow(code, y, y1, y2, &culvert); + condition = 0; + } + } + + // --- check if inlet controls and replace conduit's value of dq/dh + if ( q < q0 ) + { + // --- for debugging only + //if ( RptFlags.controls ) report_CulvertControl(j, q0, q, condition, + // y / culvert.yFull); + + Link[j].inletControl = TRUE; + Link[j].dqdh = culvert.dQdH; + return q; + } + else return q0; +} + +//============================================================================= + +double getUnsubmergedFlow(int code, double h, TCulvert* culvert) +// +// Input: code = culvert type code number +// h = inlet water depth above culvert invert +// culvert = pointer to a culvert data structure +// Output: returns flow rate; +// computes value of variable Dqdh +// Purpose: computes flow rate and its derivative for unsubmerged +// culvert inlet. +// +{ + double arg; + double q; + + // --- assign shared variables + culvert->kk = Params[code][K]; + culvert->mm = Params[code][M]; + arg = h / culvert->yFull / culvert->kk; + + // --- evaluate correct equation form + if ( Params[code][FORM] == 1.0) + { + q = getForm1Flow(h, culvert); + } + else q = culvert->ad * pow(arg, 1.0/culvert->mm); + culvert->dQdH = q / h / culvert->mm; + return q; +} + +//============================================================================= + +double getSubmergedFlow(int code, double h, TCulvert* culvert) +// +// Input: code = culvert type code number +// h = inlet head (ft) +// culvert = pointer to a culvert data structure +// Output: returns flow rate; +// computes value of Dqdh +// Purpose: computes flow rate and its derivative for submerged +// culvert inlet. +// +{ + double cc = Params[code][C]; + double yy = Params[code][Y]; + double arg = (h/culvert->yFull - yy + culvert->scf) / cc ; + double q; + + if ( arg <= 0.0 ) + { + culvert->dQdH = 0.0; + return BIG; + } + q = sqrt(arg) * culvert->ad; + culvert->dQdH = 0.5 * q / arg / culvert->yFull / cc; + return q; +} + +//============================================================================= + +double getTransitionFlow(int code, double h, double h1, double h2, TCulvert* culvert) +// +// Input: code = culvert type code number +// h = inlet water depth above culvert invert (ft) +// h1 = head limit for unsubmerged condition (ft) +// h2 = head limit for submerged condition (ft) +// culvert = pointer to a culvert data structure +// Output: returns flow rate )cfs); +// computes value of Dqdh (cfs/ft) +// Purpose: computes flow rate and its derivative for inlet-controlled flow +// when inlet water depth lies in the transition range between +// submerged and unsubmerged conditions. +// +{ + double q1 = getUnsubmergedFlow(code, h1, culvert); + double q2 = getSubmergedFlow(code, h2, culvert); + double q = q1 + (q2 - q1) * (h - h1) / (h2 - h1); + culvert->dQdH = (q2 - q1) / (h2 - h1); + return q; +} + +//============================================================================= + +double getForm1Flow(double h, TCulvert* culvert) +// +// Input: h = inlet water depth above culvert invert +// culvert = pointer to a culvert data structure +// Output: returns inlet controlled flow rate +// Purpose: computes inlet-controlled flow rate for unsubmerged culvert +// using FHWA Equation Form1. +// +// See pages 195-196 of FHWA HEC-5 (2001) for details. +// +{ + // --- save re-used terms in culvert structure + culvert->hPlus = h / culvert->yFull + culvert->scf; + + // --- use Ridder's method to solve Equation Form 1 for critical depth + // between a range of 0.01h and h + findroot_Ridder(0.01*h, h, 0.001, form1Eqn, culvert); + + // --- return the flow value used in evaluating Equation Form 1 + return culvert->qc; +} + +//============================================================================= + +double form1Eqn(double yc, void* p) +// +// Input: yc = critical depth +// p = pointer to a TCulvert object +// Output: returns residual error +// Purpose: evaluates the error in satisfying FHWA culvert Equation Form1: +// +// h/yFull + 0.5*s = yc/yFull + yh/2/yFull + K[ac/aFull*sqrt(g*yh/yFull)]^M +// +// for a given value of critical depth yc where: +// h = inlet depth above culvert invert +// s = culvert slope +// yFull = full depth of culvert +// yh = hydraulic depth at critical depth +// ac = flow area at critical depth +// g = accel. of gravity +// K and M = coefficients +// +{ + double ac, wc, yh; + TCulvert* culvert = (TCulvert *)p; + + ac = xsect_getAofY(culvert->xsect, yc); + wc = xsect_getWofY(culvert->xsect, yc); + yh = ac/wc; + + culvert->qc = ac * sqrt(GRAVITY * yh); + return culvert->hPlus - yc/culvert->yFull - yh/2.0/culvert->yFull - + culvert->kk * pow(culvert->qc/culvert->ad, culvert->mm); +} + +//============================================================================= +/* +void report_CulvertControl(int j, double q0, double q, int condition, double yRatio) +// +// Used for debugging only +// +{ + static char* conditionTxt[] = {"transition", "unsubmerged", "submerged"}; + char theDate[12]; + char theTime[9]; + DateTime aDate = getDateTime(NewRoutingTime); + datetime_dateToStr(aDate, theDate); + datetime_timeToStr(aDate, theTime); + fprintf(Frpt.file, + "\n %11s: %8s Culvert %s flow reduced from %.3f to %.3f cfs for %s flow (%.2f).", + theDate, theTime, Link[j].ID, q0, q, conditionTxt[condition], yRatio); +} +*/ diff --git a/src/datetime.c b/src/datetime.c new file mode 100644 index 000000000..d96ef7f3b --- /dev/null +++ b/src/datetime.c @@ -0,0 +1,528 @@ +//----------------------------------------------------------------------------- +// datetime.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// DateTime functions. +// +// Update History +// ============== +// Build 5.1.011: +// - decodeTime() no longer rounds up. +// - New getTimeStamp function added. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include +#include "datetime.h" + +// Macro to convert charcter x to upper case +#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const char* MonthTxt[] = + {"JAN", "FEB", "MAR", "APR", + "MAY", "JUN", "JUL", "AUG", + "SEP", "OCT", "NOV", "DEC"}; +static const int DaysPerMonth[2][12] = // days per month + {{31, 28, 31, 30, 31, 30, // normal years + 31, 31, 30, 31, 30, 31}, + {31, 29, 31, 30, 31, 30, // leap years + 31, 31, 30, 31, 30, 31}}; +static const int DateDelta = 693594; // days since 01/01/00 +static const double SecsPerDay = 86400.; // seconds per day + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +static int DateFormat; + + +//============================================================================= + +void divMod(int n, int d, int* result, int* remainder) + +// Input: n = numerator +// d = denominator +// Output: result = integer part of n/d +// remainder = remainder of n/d +// Purpose: finds integer part and remainder of n/d. + +{ + if (d == 0) + { + *result = 0; + *remainder = 0; + } + else + { + *result = n/d; + *remainder = n - d*(*result); + } +} + +//============================================================================= + +int isLeapYear(int year) + +// Input: year = a year +// Output: returns 1 if year is a leap year, 0 if not +// Purpose: determines if year is a leap year. + +{ + if ((year % 4 == 0) + && ((year % 100 != 0) + || (year % 400 == 0))) return 1; + else return 0; +} + +//============================================================================= + +int datetime_findMonth(char* month) + +// Input: month = month of year as character string +// Output: returns: month of year as a number (1-12) +// Purpose: finds number (1-12) of month. + +{ + int i; + for (i = 0; i < 12; i++) + { + if (UCHAR(month[0]) == MonthTxt[i][0] + && UCHAR(month[1]) == MonthTxt[i][1] + && UCHAR(month[2]) == MonthTxt[i][2]) return i+1; + } + return 0; +} + +//============================================================================= + +DateTime datetime_encodeDate(int year, int month, int day) + +// Input: year = a year +// month = a month (1 to 12) +// day = a day of month +// Output: returns encoded value of year-month-day +// Purpose: encodes year-month-day to a DateTime value. + +{ + int i, j; + i = isLeapYear(year); + if ((year >= 1) + && (year <= 9999) + && (month >= 1) + && (month <= 12) + && (day >= 1) + && (day <= DaysPerMonth[i][month-1])) + { + for (j = 0; j < month-1; j++) day += DaysPerMonth[i][j]; + i = year - 1; + i = i*365 + i/4 - i/100 + i/400 + day - DateDelta; + return i; + } + else return -DateDelta; +} + +//============================================================================= + +DateTime datetime_encodeTime(int hour, int minute, int second) + +// Input: hour = hour of day (0-24) +// minute = minute of hour (0-60) +// second = seconds of minute (0-60) +// Output: returns time encoded as fractional part of a day +// Purpose: encodes hour:minute:second to a DateTime value + +{ + int s; + if ((hour >= 0) + && (minute >= 0) + && (second >= 0)) + { + s = (hour * 3600 + minute * 60 + second); + return (double)s/SecsPerDay; + } + else return 0.0; +} + +//============================================================================= + +void datetime_decodeDate(DateTime date, int* year, int* month, int* day) + +// Input: date = encoded date/time value +// Output: year = 4-digit year +// month = month of year (1-12) +// day = day of month +// Purpose: decodes DateTime value to year-month-day. + +{ + int D1, D4, D100, D400; + int y, m, d, i, k, t; + + D1 = 365; //365 + D4 = D1 * 4 + 1; //1461 + D100 = D4 * 25 - 1; //36524 + D400 = D100 * 4 + 1; //146097 + + t = (int)(floor (date)) + DateDelta; + if (t <= 0) + { + *year = 0; + *month = 1; + *day = 1; + } + else + { + t--; + y = 1; + while (t >= D400) + { + t -= D400; + y += 400; + } + divMod(t, D100, &i, &d); + if (i == 4) + { + i--; + d += D100; + } + y += i*100; + divMod(d, D4, &i, &d); + y += i*4; + divMod(d, D1, &i, &d); + if (i == 4) + { + i--; + d += D1; + } + y += i; + k = isLeapYear(y); + m = 1; + for (;;) + { + i = DaysPerMonth[k][m-1]; + if (d < i) break; + d -= i; + m++; + } + *year = y; + *month = m; + *day = d + 1; + } +} + +//============================================================================= + +void datetime_decodeTime(DateTime time, int* h, int* m, int* s) + +// Input: time = decimal fraction of a day +// Output: h = hour of day (0-23) +// m = minute of hour (0-59) +// s = second of minute (0-59) +// Purpose: decodes DateTime value to hour:minute:second. + +{ + int secs; + int mins; + double fracDay = (time - floor(time)) * SecsPerDay; + secs = (int)(floor(fracDay + 0.5)); + if ( secs >= 86400 ) secs = 86399; + divMod(secs, 60, &mins, s); + divMod(mins, 60, h, m); + if ( *h > 23 ) *h = 0; +} + +//============================================================================= + +void datetime_dateToStr(DateTime date, char* s) + +// Input: date = encoded date/time value +// Output: s = formatted date string +// Purpose: represents DateTime date value as a formatted string. + +{ + int y, m, d; + datetime_decodeDate(date, &y, &m, &d); + switch (DateFormat) + { + case Y_M_D: + snprintf(s, DATE_STR_SIZE, "%4d-%3s-%02d", y, MonthTxt[m-1], d); + break; + + case M_D_Y: + //sprintf(dateStr, "%3s-%02d-%4d", MonthTxt[m-1], d, y); + snprintf(s, DATE_STR_SIZE, "%02d/%02d/%04d", m, d, y); + break; + + default: + snprintf(s, DATE_STR_SIZE, "%02d-%3s-%4d", d, MonthTxt[m-1], y); + } +} + +void datetime_timeToStr(DateTime time, char* s) + +// Input: time = decimal fraction of a day +// Output: s = time in hr:min:sec format +// Purpose: represents DateTime time value as a formatted string. + +{ + int hr, min, sec; + datetime_decodeTime(time, &hr, &min, &sec); + snprintf(s, TIME_STR_SIZE, "%02d:%02d:%02d", hr, min, sec); +} + +//============================================================================= + +int datetime_strToDate(char* s, DateTime* d) + +// Input: s = date as string +// Output: d = encoded date; +// returns 1 if conversion successful, 0 if not +// Purpose: converts string date s to DateTime value. +// +{ + int yr = 0, mon = 0, day = 0, n; + char month[4]; + char sep1, sep2; + *d = -DateDelta; + if (strchr(s, '-') || strchr(s, '/')) + { + switch (DateFormat) + { + case Y_M_D: + n = sscanf(s, "%d%c%d%c%d", &yr, &sep1, &mon, &sep2, &day); + if ( n < 3 ) + { + mon = 0; + n = sscanf(s, "%d%c%3s%c%d", &yr, &sep1, month, &sep2, &day); + if ( n < 3 ) return 0; + } + break; + + case D_M_Y: + n = sscanf(s, "%d%c%d%c%d", &day, &sep1, &mon, &sep2, &yr); + if ( n < 3 ) + { + mon = 0; + n = sscanf(s, "%d%c%3s%c%d", &day, &sep1, month, &sep2, &yr); + if ( n < 3 ) return 0; + } + break; + + default: // M_D_Y + n = sscanf(s, "%d%c%d%c%d", &mon, &sep1, &day, &sep2, &yr); + if ( n < 3 ) + { + mon = 0; + n = sscanf(s, "%3s%c%d%c%d", month, &sep1, &day, &sep2, &yr); + if ( n < 3 ) return 0; + } + } + if (mon == 0) mon = datetime_findMonth(month); + *d = datetime_encodeDate(yr, mon, day); + } + if (*d == -DateDelta) return 0; + else return 1; +} + +//============================================================================= + +int datetime_strToTime(char* s, DateTime* t) + +// Input: s = time as string +// Output: t = encoded time, +// returns 1 if conversion successful, 0 if not +// Purpose: converts a string time to a DateTime value. +// Note: accepts time as hr:min:sec or as decimal hours. + +{ + int n, hr, min = 0, sec = 0; + char *endptr; + + // Attempt to read time as decimal hours + *t = strtod(s, &endptr); + if ( *endptr == 0 ) + { + *t /= 24.0; + return 1; + } + + // Read time in hr:min:sec format + *t = 0.0; + n = sscanf(s, "%d:%d:%d", &hr, &min, &sec); + if ( n == 0 ) return 0; + *t = datetime_encodeTime(hr, min, sec); + if ( (hr >= 0) && (min >= 0) && (sec >= 0) ) return 1; + else return 0; +} + +//============================================================================= + +void datetime_setDateFormat(int fmt) + +// Input: fmt = date format code +// Output: none +// Purpose: sets date format + +{ + if ( fmt >= Y_M_D && fmt <= M_D_Y) DateFormat = fmt; +} + +//============================================================================= + +DateTime datetime_addSeconds(DateTime date1, double seconds) + +// Input: date1 = an encoded date/time value +// seconds = number of seconds to add to date1 +// Output: returns updated value of date1 +// Purpose: adds a given number of seconds to a date/time. + +{ + double d = floor(date1); + int h, m, s; + datetime_decodeTime(date1, &h, &m, &s); + return d + (3600.0*h + 60.0*m + s + seconds)/SecsPerDay; +} + +//============================================================================= + +DateTime datetime_addDays(DateTime date1, DateTime date2) + +// Input: date1 = an encoded date/time value +// date2 = decimal days to be added to date1 +// Output: returns date1 + date2 +// Purpose: adds a given number of decimal days to a date/time. + +{ + double d1 = floor(date1); + double d2 = floor(date2); + int h1, m1, s1; + int h2, m2, s2; + datetime_decodeTime(date1, &h1, &m1, &s1); + datetime_decodeTime(date2, &h2, &m2, &s2); + return d1 + d2 + datetime_encodeTime(h1+h2, m1+m2, s1+s2); +} + +//============================================================================= + +long datetime_timeDiff(DateTime date1, DateTime date2) + +// Input: date1 = an encoded date/time value +// date2 = an encoded date/time value +// Output: returns date1 - date2 in seconds +// Purpose: finds number of seconds between two dates. + +{ + double d1 = floor(date1); + double d2 = floor(date2); + int h, m, s; + long s1, s2, secs; + datetime_decodeTime(date1, &h, &m, &s); + s1 = 3600*h + 60*m + s; + datetime_decodeTime(date2, &h, &m, &s); + s2 = 3600*h + 60*m + s; + secs = (int)(floor((d1 - d2)*SecsPerDay + 0.5)); + secs += (s1 - s2); + return secs; +} + +//============================================================================= + +int datetime_monthOfYear(DateTime date) + +// Input: date = an encoded date/time value +// Output: returns index of month of year (1..12) +// Purpose: finds month of year (Jan = 1 ...) for a given date. + +{ + int year, month, day; + datetime_decodeDate(date, &year, &month, &day); + return month; +} + +//============================================================================= + +int datetime_dayOfYear(DateTime date) + +// Input: date = an encoded date/time value +// Output: returns day of year (1..365) +// Purpose: finds day of year (Jan 1 = 1) for a given date. + +{ + int year, month, day; + DateTime startOfYear; + datetime_decodeDate(date, &year, &month, &day); + startOfYear = datetime_encodeDate(year, 1, 1); + return (int)(floor(date - startOfYear)) + 1; +} + +//============================================================================= + +int datetime_dayOfWeek(DateTime date) + +// Input: date = an encoded date/time value +// Output: returns index of day of week (1..7) +// Purpose: finds day of week (Sun = 1, ... Sat = 7) for a given date. + +{ + int t = (int)(floor(date)) + DateDelta; + return (t % 7) + 1; +} + +//============================================================================= + +int datetime_hourOfDay(DateTime date) + +// Input: date = an encoded date/time value +// Output: returns hour of day (0..23) +// Purpose: finds hour of day (0 = 12 AM, ..., 23 = 11 PM) for a given date. + +{ + int hour, min, sec; + datetime_decodeTime(date, &hour, &min, &sec); + return hour; +} + +//============================================================================= + +int datetime_daysPerMonth(int year, int month) + +// Input: year = year in which month falls +// month = month of year (1..12) +// Output: returns number of days in the month +// Purpose: finds number of days in a given month of a specified year. + +{ + if ( month < 1 || month > 12 ) return 0; + return DaysPerMonth[isLeapYear(year)][month-1]; +} + +//============================================================================= + +void datetime_getTimeStamp(int fmt, DateTime aDate, int stampSize, char* timeStamp) + +// Input: fmt = desired date format code +// aDate = a date/time value in decimal days +// stampSize = the number of bytes allocated for the time stamp +// Output: returns a time stamp string (e.g., Year-Month-Day Hr:Min:Sec) +// Purpose: Expresses a decimal day date by a time stamp. +{ + char dateStr[DATE_STR_SIZE]; + char timeStr[TIME_STR_SIZE]; + int oldDateFormat = DateFormat; + + if ( stampSize < TIME_STAMP_SIZE ) return; + datetime_setDateFormat(fmt); + datetime_dateToStr(aDate, dateStr); + DateFormat = oldDateFormat; + datetime_timeToStr(aDate, timeStr); + snprintf(timeStamp, stampSize, "%s %s", dateStr, timeStr); +} diff --git a/src/datetime.h b/src/datetime.h new file mode 100644 index 000000000..98b87b48e --- /dev/null +++ b/src/datetime.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// datetime.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// The DateTime type is used to store date and time values. It is +// equivalent to a double floating point type. +// +// The integral part of a DateTime value is the number of days that have +// passed since 12/31/1899. The fractional part of a DateTime value is the +// fraction of a 24 hour day that has elapsed. +// +// Update History +// ============== +// Build 5.1.011: +// - New getTimeStamp function added. +//----------------------------------------------------------------------------- + +#ifndef DATETIME_H +#define DATETIME_H + + +typedef double DateTime; + +#define Y_M_D 0 +#define M_D_Y 1 +#define D_M_Y 2 +#define NO_DATE -693594 // 1/1/0001 +#define DATE_STR_SIZE 12 +#define TIME_STR_SIZE 9 +#define TIME_STAMP_SIZE 21 + +// Functions for encoding a date or time value to a DateTime value +DateTime datetime_encodeDate(int year, int month, int day); +DateTime datetime_encodeTime(int hour, int minute, int second); + +// Functions for decoding a DateTime value to a date and time +void datetime_decodeDate(DateTime date, int* y, int* m, int* d); +void datetime_decodeTime(DateTime time, int* h, int* m, int* s); + +// Function for finding day of week for a date (1 = Sunday) +// month of year, days per month, and hour of day +int datetime_monthOfYear(DateTime date); +int datetime_dayOfYear(DateTime date); +int datetime_dayOfWeek(DateTime date); +int datetime_hourOfDay(DateTime date); +int datetime_daysPerMonth(int year, int month); + +// Functions for converting a DateTime value to a string +void datetime_dateToStr(DateTime date, char* s); +void datetime_timeToStr(DateTime time, char* s); +void datetime_getTimeStamp(int fmt, DateTime aDate, int stampSize, + char* timeStamp); + +// Functions for converting a string date or time to a DateTime value +int datetime_findMonth(char* s); +int datetime_strToDate(char* s, DateTime* d); +int datetime_strToTime(char* s, DateTime* t); + +// Function for setting date format +void datetime_setDateFormat(int fmt); + +// Functions for adding and subtracting dates +DateTime datetime_addSeconds(DateTime date1, double seconds); +DateTime datetime_addDays(DateTime date1, DateTime date2); +long datetime_timeDiff(DateTime date1, DateTime date2); + + +#endif //DATETIME_H diff --git a/src/dwflow.c b/src/dwflow.c new file mode 100644 index 000000000..14338b2de --- /dev/null +++ b/src/dwflow.c @@ -0,0 +1,683 @@ +//----------------------------------------------------------------------------- +// dwflow.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// M. Tryby (EPA) +// R. Dickinson (CDM) +// +// Solves the momentum equation for flow in a conduit under dynamic wave +// flow routing. +// +// Update History +// ============== +// Build 5.1.008: +// - Bug in finding if conduit was upstrm/dnstrm full was fixed. +// Build 5.1.012: +// - Modified uniform loss rate term of conduit momentum equation. +// Build 5.1.013: +// - Preissmann slot surcharge option implemented. +// - Changed sign of uniform loss rate term (dq6) in flow updating equation. +// Build 5.1.014: +// - Conduit evap. and seepage loss initialized to 0 in dwflow_findConduitFlow. +// - Most current flow (qLast) used instead of previous time period flow +// (qOld) in call to link_getLossRate. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "headers.h" + +static const double MAXVELOCITY = 50.; // max. allowable velocity (ft/sec) + +static int getFlowClass(int link, double q, double h1, double h2, + double y1, double y2, double* criticalDepth, double* normalDepth, + double* fasnh); +static void findSurfArea(int link, double q, double length, double* h1, + double* h2, double* y1, double* y2); +static double findLocalLosses(int link, double a1, double a2, double aMid, + double q); + +static double getWidth(TXsect* xsect, double y); +static double getSlotWidth(TXsect* xsect, double y); +static double getArea(TXsect* xsect, double y, double wSlot); +static double getHydRad(TXsect* xsect, double y); + +static double checkNormalFlow(int j, double q, double y1, double y2, + double a1, double r1); + +//============================================================================= + +void dwflow_findConduitFlow(int j, int steps, double omega, double dt) +// +// Input: j = link index +// steps = number of iteration steps taken +// omega = under-relaxation parameter +// dt = time step (sec) +// Output: returns new flow value (cfs) +// Purpose: updates flow in conduit link by solving finite difference +// form of continuity and momentum equations. +// +{ + int k; // index of conduit + int n1, n2; // indexes of end nodes + double z1, z2; // upstream/downstream invert elev. (ft) + double h1, h2; // upstream/dounstream flow heads (ft) + double y1, y2; // upstream/downstream flow depths (ft) + double a1, a2; // upstream/downstream flow areas (ft2) + double r1; // upstream hyd. radius (ft) + double yMid, rMid, aMid; // mid-stream or avg. values of y, r, & a + double aWtd, rWtd; // upstream weighted area & hyd. radius + double qLast; // flow from previous iteration (cfs) + double qOld; // flow from previous time step (cfs) + double aOld; // area from previous time step (ft2) + double v; // velocity (ft/sec) + double rho; // upstream weighting factor + double sigma; // inertial damping factor + double length; // effective conduit length (ft) + double wSlot; // Preissmann slot width (ft) + double dq1, dq2, dq3, dq4, dq5, // terms in momentum eqn. + dq6; // term for evap and infil losses + double denom; // denominator of flow update formula + double q; // new flow value (cfs) + double barrels; // number of barrels in conduit + TXsect* xsect = &Link[j].xsect; // ptr. to conduit's cross section data + char isFull = FALSE; // TRUE if conduit flowing full + char isClosed = FALSE; // TRUE if conduit closed + + + + // --- adjust isClosed status by any control action + if ( Link[j].setting == 0 ) isClosed = TRUE; + + // --- get flow from last time step & previous iteration + k = Link[j].subIndex; + barrels = Conduit[k].barrels; + qOld = Link[j].oldFlow / barrels; + qLast = Conduit[k].q1; + Conduit[k].evapLossRate = 0.0; + Conduit[k].seepLossRate = 0.0; + + // --- get most current heads at upstream and downstream ends of conduit + n1 = Link[j].node1; + n2 = Link[j].node2; + z1 = Node[n1].invertElev + Link[j].offset1; + z2 = Node[n2].invertElev + Link[j].offset2; + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + h1 = MAX(h1, z1); + h2 = MAX(h2, z2); + + // --- get unadjusted upstream and downstream flow depths in conduit + // (flow depth = head in conduit - elev. of conduit invert) + y1 = h1 - z1; + y2 = h2 - z2; + y1 = MAX(y1, FUDGE); + y2 = MAX(y2, FUDGE); + + // --- flow depths can't exceed full depth of conduit if slot not used + if ( SurchargeMethod != SLOT ) + { + y1 = MIN(y1, xsect->yFull); + y2 = MIN(y2, xsect->yFull); + } + + // -- get area from solution at previous time step + aOld = Conduit[k].a2; + aOld = MAX(aOld, FUDGE); + + // --- use Courant-modified length instead of conduit's actual length + length = Conduit[k].modLength; + + // --- find surface area contributions to upstream and downstream nodes + // based on previous iteration's flow estimate + findSurfArea(j, qLast, length, &h1, &h2, &y1, &y2); + + // --- compute area at each end of conduit & hyd. radius at upstream end + wSlot = getSlotWidth(xsect, y1); + a1 = getArea(xsect, y1, wSlot); + r1 = getHydRad(xsect, y1); + wSlot = getSlotWidth(xsect, y2); + a2 = getArea(xsect, y2, wSlot); + + // --- compute area & hyd. radius at midpoint + yMid = 0.5 * (y1 + y2); + wSlot = getSlotWidth(xsect, yMid); + aMid = getArea(xsect, yMid, wSlot); + rMid = getHydRad(xsect, yMid); + + // --- alternate approach not currently used, but might produce better + // Bernoulli energy balance for steady flows + //aMid = (a1+a2)/2.0; + //rMid = (r1+getHydRad(xsect,y2))/2.0; + + // --- check if conduit is flowing full + if ( y1 >= xsect->yFull && + y2 >= xsect->yFull) isFull = TRUE; + + // --- set new flow to zero if conduit is dry or if flap gate is closed + if ( Link[j].flowClass == DRY || + Link[j].flowClass == UP_DRY || + Link[j].flowClass == DN_DRY || + isClosed || + aMid <= FUDGE ) + { + Conduit[k].a1 = 0.5 * (a1 + a2); + Conduit[k].q1 = 0.0;; + Conduit[k].q2 = 0.0; + Link[j].dqdh = GRAVITY * dt * aMid / length * barrels; + Link[j].froude = 0.0; + Link[j].newDepth = MIN(yMid, Link[j].xsect.yFull); + Link[j].newVolume = Conduit[k].a1 * link_getLength(j) * barrels; + Link[j].newFlow = 0.0; + return; + } + + // --- compute velocity from last flow estimate + v = qLast / aMid; + if ( fabs(v) > MAXVELOCITY ) v = MAXVELOCITY * SGN(qLast); + + // --- compute Froude No. + Link[j].froude = link_getFroude(j, v, yMid); + if ( Link[j].flowClass == SUBCRITICAL && + Link[j].froude > 1.0 ) Link[j].flowClass = SUPCRITICAL; + + // --- find inertial damping factor (sigma) + if ( Link[j].froude <= 0.5 ) sigma = 1.0; + else if ( Link[j].froude >= 1.0 ) sigma = 0.0; + else sigma = 2.0 * (1.0 - Link[j].froude); + + // --- get upstream-weighted area & hyd. radius based on damping factor + // (modified version of R. Dickinson's slope weighting) + rho = 1.0; + if ( !isFull && qLast > 0.0 && h1 >= h2 ) rho = sigma; + aWtd = a1 + (aMid - a1) * rho; + rWtd = r1 + (rMid - r1) * rho; + + // --- determine how much inertial damping to apply + if ( InertDamping == NO_DAMPING ) sigma = 1.0; + else if ( InertDamping == FULL_DAMPING ) sigma = 0.0; + + // --- use full inertial damping if closed conduit is surcharged + if ( isFull && !xsect_isOpen(xsect->type) ) sigma = 0.0; + + // --- compute terms of momentum eqn.: + // --- 1. friction slope term + if ( xsect->type == FORCE_MAIN && isFull ) + dq1 = dt * forcemain_getFricSlope(j, fabs(v), rMid); + else dq1 = dt * Conduit[k].roughFactor / pow(rWtd, 1.33333) * fabs(v); + + // --- 2. energy slope term + dq2 = dt * GRAVITY * aWtd * (h2 - h1) / length; + + // --- 3 & 4. inertial terms + dq3 = 0.0; + dq4 = 0.0; + if ( sigma > 0.0 ) + { + dq3 = 2.0 * v * (aMid - aOld) * sigma; + dq4 = dt * v * v * (a2 - a1) / length * sigma; + } + + // --- 5. local losses term + dq5 = 0.0; + if ( Conduit[k].hasLosses ) + { + dq5 = findLocalLosses(j, a1, a2, aMid, qLast) / 2.0 / length * dt; + } + + // --- 6. term for evap and seepage losses per unit length + dq6 = link_getLossRate(j, qLast) * 2.5 * dt * v / link_getLength(j); + + // --- combine terms to find new conduit flow + denom = 1.0 + dq1 + dq5; + q = (qOld - dq2 + dq3 + dq4 + dq6) / denom; + + // --- compute derivative of flow w.r.t. head + Link[j].dqdh = 1.0 / denom * GRAVITY * dt * aWtd / length * barrels; + + // --- check if any flow limitation applies + Link[j].inletControl = FALSE; + Link[j].normalFlow = FALSE; + if ( q > 0.0 ) + { + // --- check for inlet controlled culvert flow + if ( xsect->culvertCode > 0 && !isFull ) + q = culvert_getInflow(j, q, h1); + + // --- check for normal flow limitation based on surface slope & Fr + else + if ( y1 < Link[j].xsect.yFull && + ( Link[j].flowClass == SUBCRITICAL || + Link[j].flowClass == SUPCRITICAL ) + ) q = checkNormalFlow(j, q, y1, y2, a1, r1); + } + + // --- apply under-relaxation weighting between new & old flows; + // --- do not allow change in flow direction without first being zero + if ( steps > 0 ) + { + q = (1.0 - omega) * qLast + omega * q; + if ( q * qLast < 0.0 ) q = 0.001 * SGN(q); + } + + // --- check if user-supplied flow limit applies + if ( Link[j].qLimit > 0.0 ) + { + if ( fabs(q) > Link[j].qLimit ) q = SGN(q) * Link[j].qLimit; + } + + // --- check for reverse flow with closed flap gate + if ( link_setFlapGate(j, n1, n2, q) ) q = 0.0; + + // --- do not allow flow out of a dry node + // (as suggested by R. Dickinson) + if( q > FUDGE && Node[n1].newDepth <= FUDGE ) q = FUDGE; + if( q < -FUDGE && Node[n2].newDepth <= FUDGE ) q = -FUDGE; + + // --- save new values of area, flow, depth, & volume + Conduit[k].a1 = aMid; + Conduit[k].q1 = q; + Conduit[k].q2 = q; + Link[j].newDepth = MIN(yMid, xsect->yFull); + aMid = (a1 + a2) / 2.0; +// aMid = MIN(aMid, xsect->aFull); //Slot can have aMid > aFull + Conduit[k].fullState = link_getFullState(a1, a2, xsect->aFull); + Link[j].newVolume = aMid * link_getLength(j) * barrels; + Link[j].newFlow = q * barrels; +} + +//============================================================================= + +int getFlowClass(int j, double q, double h1, double h2, double y1, double y2, + double *yC, double *yN, double* fasnh) +// +// Input: j = conduit link index +// q = current conduit flow (cfs) +// h1 = head at upstream end of conduit (ft) +// h2 = head at downstream end of conduit (ft) +// y1 = upstream flow depth in conduit (ft) +// y2 = downstream flow depth in conduit (ft) +// yC = critical flow depth (ft) +// yN = normal flow depth (ft) +// fasnh = fraction between norm. & crit. depth +// Output: returns flow classification code +// Purpose: determines flow class for a conduit based on depths at each end. +// +{ + int n1, n2; // indexes of upstrm/downstrm nodes + int flowClass; // flow classification code + double ycMin, ycMax; // min/max critical depths (ft) + double z1, z2; // offsets of conduit inverts (ft) + + // --- get upstream & downstream node indexes + n1 = Link[j].node1; + n2 = Link[j].node2; + + // --- get upstream & downstream conduit invert offsets + z1 = Link[j].offset1; + z2 = Link[j].offset2; + + // --- base offset of an outfall conduit on outfall's depth + if ( Node[n1].type == OUTFALL ) z1 = MAX(0.0, (z1 - Node[n1].newDepth)); + if ( Node[n2].type == OUTFALL ) z2 = MAX(0.0, (z2 - Node[n2].newDepth)); + + // --- default class is SUBCRITICAL + flowClass = SUBCRITICAL; + *fasnh = 1.0; + + // --- case where both ends of conduit are wet + if ( y1 > FUDGE && y2 > FUDGE ) + { + if ( q < 0.0 ) + { + // --- upstream end at critical depth if flow depth is + // below conduit's critical depth and an upstream + // conduit offset exists + if ( z1 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + ycMin = MIN(*yN, *yC); + if ( y1 < ycMin ) flowClass = UP_CRITICAL; + } + } + + // --- case of normal direction flow + else + { + // --- downstream end at smaller of critical and normal depth + // if downstream flow depth below this and a downstream + // conduit offset exists + if ( z2 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + ycMin = MIN(*yN, *yC); + ycMax = MAX(*yN, *yC); + if ( y2 < ycMin ) flowClass = DN_CRITICAL; + else if ( y2 < ycMax ) + { + if ( ycMax - ycMin < FUDGE ) *fasnh = 0.0; + else *fasnh = (ycMax - y2) / (ycMax - ycMin); + } + } + } + } + + // --- case where no flow at either end of conduit + else if ( y1 <= FUDGE && y2 <= FUDGE ) flowClass = DRY; + + // --- case where downstream end of pipe is wet, upstream dry + else if ( y2 > FUDGE ) + { + // --- flow classification is UP_DRY if downstream head < + // invert of upstream end of conduit + if ( h2 < Node[n1].invertElev + Link[j].offset1 ) flowClass = UP_DRY; + + // --- otherwise, the downstream head will be >= upstream + // conduit invert creating a flow reversal and upstream end + // should be at critical depth, providing that an upstream + // offset exists (otherwise subcritical condition is maintained) + else if ( z1 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + flowClass = UP_CRITICAL; + } + } + + // --- case where upstream end of pipe is wet, downstream dry + else + { + // --- flow classification is DN_DRY if upstream head < + // invert of downstream end of conduit + if ( h1 < Node[n2].invertElev + Link[j].offset2 ) flowClass = DN_DRY; + + // --- otherwise flow at downstream end should be at critical depth + // providing that a downstream offset exists (otherwise + // subcritical condition is maintained) + else if ( z2 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + flowClass = DN_CRITICAL; + } + } + return flowClass; +} + +//============================================================================= + +void findSurfArea(int j, double q, double length, double* h1, double* h2, + double* y1, double* y2) +// +// Input: j = conduit link index +// q = current conduit flow (cfs) +// length = conduit length (ft) +// h1 = head at upstream end of conduit (ft) +// h2 = head at downstream end of conduit (ft) +// y1 = upstream flow depth (ft) +// y2 = downstream flow depth (ft) +// Output: updated values of h1, h2, y1, & y2; +// Purpose: assigns surface area of conduit to its up and downstream nodes. +// +{ + int n1, n2; // indexes of upstrm/downstrm nodes + double flowDepth1; // flow depth at upstrm end (ft) + double flowDepth2; // flow depth at downstrm end (ft) + double flowDepthMid; // flow depth at midpt. (ft) + double width1; // top width at upstrm end (ft) + double width2; // top width at downstrm end (ft) + double widthMid; // top width at midpt. (ft) + double surfArea1 = 0.0; // surface area at upstream node (ft2) + double surfArea2 = 0.0; // surface area st downstrm node (ft2) + double criticalDepth; // critical flow depth (ft) + double normalDepth; // normal flow depth (ft) + double fullDepth; // full depth (ft) //(5.1.013) + double fasnh = 1.0; // fraction between norm. & crit. depth //(5.1.013) + TXsect* xsect = &Link[j].xsect; // pointer to cross-section data + + // --- get node indexes & current flow depths + n1 = Link[j].node1; + n2 = Link[j].node2; + flowDepth1 = *y1; + flowDepth2 = *y2; + + normalDepth = (flowDepth1 + flowDepth2) / 2.0; + criticalDepth = normalDepth; + + // --- find conduit's flow classification + fullDepth = xsect->yFull; + if (flowDepth1 >= fullDepth && flowDepth2 >= fullDepth) + { + Link[j].flowClass = SUBCRITICAL; + } + else Link[j].flowClass = getFlowClass(j, q, *h1, *h2, *y1, *y2, + &criticalDepth, &normalDepth, &fasnh); + + // --- add conduit's surface area to its end nodes depending on flow class + switch ( Link[j].flowClass ) + { + case SUBCRITICAL: + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width1 = getWidth(xsect, flowDepth1); + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + surfArea1 = (width1 + widthMid) * length / 4.; + surfArea2 = (widthMid + width2) * length / 4. * fasnh; + break; + + case UP_CRITICAL: + flowDepth1 = criticalDepth; + if ( normalDepth < criticalDepth ) flowDepth1 = normalDepth; + flowDepth1 = MAX(flowDepth1, FUDGE); + *h1 = Node[n1].invertElev + Link[j].offset1 + flowDepth1; + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + surfArea2 = (widthMid + width2) * length * 0.5; + break; + + case DN_CRITICAL: + flowDepth2 = criticalDepth; + if ( normalDepth < criticalDepth ) flowDepth2 = normalDepth; + flowDepth2 = MAX(flowDepth2, FUDGE); + *h2 = Node[n2].invertElev + Link[j].offset2 + flowDepth2; + width1 = getWidth(xsect, flowDepth1); + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + widthMid = getWidth(xsect, flowDepthMid); + surfArea1 = (width1 + widthMid) * length * 0.5; + break; + + case UP_DRY: + flowDepth1 = FUDGE; + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width1 = getWidth(xsect, flowDepth1); + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + + // --- assign avg. surface area of downstream half of conduit + // to the downstream node + surfArea2 = (widthMid + width2) * length / 4.; + + // --- if there is no free-fall at upstream end, assign the + // upstream node the avg. surface area of the upstream half + if ( Link[j].offset1 <= 0.0 ) + { + surfArea1 = (width1 + widthMid) * length / 4.; + } + break; + + case DN_DRY: + flowDepth2 = FUDGE; + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width1 = getWidth(xsect, flowDepth1); + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + + // --- assign avg. surface area of upstream half of conduit + // to the upstream node + surfArea1 = (widthMid + width1) * length / 4.; + + // --- if there is no free-fall at downstream end, assign the + // downstream node the avg. surface area of the downstream half + if ( Link[j].offset2 <= 0.0 ) + { + surfArea2 = (width2 + widthMid) * length / 4.; + } + break; + + case DRY: + surfArea1 = FUDGE * length / 2.0; + surfArea2 = surfArea1; + break; + } + Link[j].surfArea1 = surfArea1; + Link[j].surfArea2 = surfArea2; + *y1 = flowDepth1; + *y2 = flowDepth2; +} + +//============================================================================= + +double findLocalLosses(int j, double a1, double a2, double aMid, double q) +// +// Input: j = link index +// a1 = upstream area (ft2) +// a2 = downstream area (ft2) +// aMid = midpoint area (ft2) +// q = flow rate (cfs) +// Output: returns local losses (ft/sec) +// Purpose: computes local losses term of momentum equation. +// +{ + double losses = 0.0; + q = fabs(q); + if ( a1 > FUDGE ) losses += Link[j].cLossInlet * (q/a1); + if ( a2 > FUDGE ) losses += Link[j].cLossOutlet * (q/a2); + if ( aMid > FUDGE ) losses += Link[j].cLossAvg * (q/aMid); + return losses; +} + +//============================================================================= + +double getSlotWidth(TXsect* xsect, double y) +{ + double yNorm = y / xsect->yFull; + + // --- return 0.0 if slot surcharge method not used + if (SurchargeMethod != SLOT || xsect_isOpen(xsect->type) || + yNorm < CrownCutoff) return 0.0; + + // --- for depth > 1.78 * pipe depth, slot width = 1% of max. width + if (yNorm > 1.78) return 0.01 * xsect->wMax; + + // --- otherwise use the Sjoberg formula + return xsect->wMax * 0.5423 * exp(-pow(yNorm, 2.4)); +} + +//============================================================================= + +double getWidth(TXsect* xsect, double y) +// +// Input: xsect = ptr. to conduit cross section +// y = flow depth (ft) +// Output: returns top width (ft) +// Purpose: computes top width of flow surface in conduit. +// +{ + double wSlot = getSlotWidth(xsect, y); + if (wSlot > 0.0) return wSlot; + if (y / xsect->yFull >= CrownCutoff && !xsect_isOpen(xsect->type)) + y = CrownCutoff * xsect->yFull; + return xsect_getWofY(xsect, y); +} + +//============================================================================= + +double getArea(TXsect* xsect, double y, double wSlot) +// +// Input: xsect = ptr. to conduit cross section +// y = flow depth (ft) +// Output: returns flow area (ft2) +// Purpose: computes area of flow cross-section in a conduit. +// +{ + if ( y >= xsect->yFull ) return xsect->aFull + (y - xsect->yFull) * wSlot; + return xsect_getAofY(xsect, y); +} + +//============================================================================= + +double getHydRad(TXsect* xsect, double y) +// +// Input: xsect = ptr. to conduit cross section +// y = flow depth (ft) +// Output: returns hydraulic radius (ft) +// Purpose: computes hydraulic radius of flow cross-section in a conduit. +// +{ + if (y >= xsect->yFull) return xsect->rFull; + return xsect_getRofY(xsect, y); +} + +//============================================================================= + +double checkNormalFlow(int j, double q, double y1, double y2, double a1, + double r1) +// +// Input: j = link index +// q = link flow found from dynamic wave equations (cfs) +// y1 = flow depth at upstream end (ft) +// y2 = flow depth at downstream end (ft) +// a1 = flow area at upstream end (ft2) +// r1 = hyd. radius at upstream end (ft) +// Output: returns modifed flow in link (cfs) +// Purpose: checks if flow in link should be replaced by normal flow. +// +{ + int check = FALSE; + int k = Link[j].subIndex; + int n1 = Link[j].node1; + int n2 = Link[j].node2; + int hasOutfall = (Node[n1].type == OUTFALL || Node[n2].type == OUTFALL); + double qNorm; + double f1; + + // --- check if water surface slope < conduit slope + if ( NormalFlowLtd == SLOPE || NormalFlowLtd == BOTH || hasOutfall ) + { + if ( y1 < y2 ) check = TRUE; + } + + // --- check if Fr >= 1.0 at upstream end of conduit + if ( !check && (NormalFlowLtd == FROUDE || NormalFlowLtd == BOTH) && + !hasOutfall ) + { + if ( y1 > FUDGE && y2 > FUDGE ) + { + f1 = link_getFroude(j, q/a1, y1); + if ( f1 >= 1.0 ) check = TRUE; + } + } + + // --- check if normal flow < dynamic flow + if ( check ) + { + qNorm = Conduit[k].beta * a1 * pow(r1, 2./3.); + if ( qNorm < q ) + { + Link[j].normalFlow = TRUE; + return qNorm; + } + } + return q; +} diff --git a/src/dynwave.c b/src/dynwave.c new file mode 100644 index 000000000..ac302e849 --- /dev/null +++ b/src/dynwave.c @@ -0,0 +1,908 @@ +//----------------------------------------------------------------------------- +// dynwave.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// M. Tryby (EPA) +// R. Dickinson (CDM) +// +// Dynamic wave flow routing functions. +// +// This module solves the dynamic wave flow routing equations using +// Picard Iterations (i.e., a method of successive approximations) +// to solve the explicit form of the continuity and momentum equations +// for conduits. +// +// Update History +// ============== +// Build 5.1.002: +// - Only non-ponded nodal surface area is saved for use in +// surcharge algorithm. +// Build 5.1.007: +// - Node losses added to node outflow variable instead of treated +// as a separate item when computing change in node flow volume. +// Build 5.1.008: +// - Module-specific constants moved here from project.c. +// - Support added for user-specified minimum variable time step. +// - Node crown elevations found here instead of in flowrout.c module. +// - OpenMP use to parallelize findLinkFlows() & findNodeDepths(). +// - Bug in finding complete list of capacity limited links fixed. +// Build 5.1.011: +// - Added test for failed memory allocation. +// - Fixed illegal array index bug for Ideal Pumps. +// Build 5.1.013: +// - Include omp.h protected against lack of compiler support for OpenMP. +// - SurchargeMethod option used to decide how node surcharging is handled. +// - Storage nodes allowed to pressurize if their surcharge depth > 0. +// - Minimum flow needed to compute a Courant time step modified. +// Build 5.1.014: +// - updateNodeFlows() modified to subtract conduit evap. and seepage losses +// from downstream node inflow instead of upstream node outflow. +// Build 5.1.015: +// - Roll back the 5.1.014 change for conduit losses in updateNodeFlows(). +// Build 5.2.0: +// - Support added for reporting most frequent non-converging links. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double MINTIMESTEP = 0.001; // min. time step (sec) +static const double OMEGA = 0.5; // under-relaxation parameter +static const double DEFAULT_SURFAREA = 12.566; // Min. nodal surface area (~4 ft diam.) +static const double DEFAULT_HEADTOL = 0.005; // Default head tolerance (ft) +static const double EXTRAN_CROWN_CUTOFF = 0.96; // crown cutoff for EXTRAN +static const double SLOT_CROWN_CUTOFF = 0.985257; // crown cutoff for SLOT +static const int DEFAULT_MAXTRIALS = 8; // Max. trials per time step + + +//----------------------------------------------------------------------------- +// Data Structures +//----------------------------------------------------------------------------- +typedef struct +{ + char converged; // TRUE if iterations for a node done + double newSurfArea; // current surface area (ft2) + double oldSurfArea; // previous surface area (ft2) + double sumdqdh; // sum of dqdh from adjoining links + double dYdT; // change in depth w.r.t. time (ft/sec) +} TXnode; + +//----------------------------------------------------------------------------- +// Shared Variables +//----------------------------------------------------------------------------- +static double VariableStep; // size of variable time step (sec) +static TXnode* Xnode; // extended nodal information + +static double Omega; // actual under-relaxation parameter +static int Steps; // number of Picard iterations + +//----------------------------------------------------------------------------- +// Function declarations +//----------------------------------------------------------------------------- +static void initRoutingStep(void); +static void initNodeStates(void); +static void findBypassedLinks(); +static void findLimitedLinks(); + +static void findLinkFlows(double dt); +static int isTrueConduit(int link); +static void findNonConduitFlow(int link, double dt); +static void findNonConduitSurfArea(int link); +static double getModPumpFlow(int link, double q, double dt); +static void updateNodeFlows(int link); +static void updateConvergenceStats(); + +static int findNodeDepths(double dt); +static void setNodeDepth(int node, double dt); +static double getFloodedDepth(int node, int canPond, double dV, double yNew, + double yMax, double dt); + +static double getVariableStep(double maxStep); +static double getLinkStep(double tMin, int *minLink); +static double getNodeStep(double tMin, int *minNode); + +//============================================================================= + +void dynwave_init() +// +// Input: none +// Output: none +// Purpose: initializes dynamic wave routing method. +// +{ + int i, j; + double z; + + VariableStep = 0.0; + Xnode = (TXnode *) calloc(Nobjects[NODE], sizeof(TXnode)); + if ( Xnode == NULL ) + { + report_writeErrorMsg(ERR_MEMORY, + " Not enough memory for dynamic wave routing."); + return; + } + + // --- initialize node surface areas & crown elev. + for (i = 0; i < Nobjects[NODE]; i++ ) + { + Xnode[i].newSurfArea = 0.0; + Xnode[i].oldSurfArea = 0.0; + Node[i].crownElev = Node[i].invertElev; + } + + // --- initialize links & update node crown elevations + for (i = 0; i < Nobjects[LINK]; i++) + { + j = Link[i].node1; + z = Node[j].invertElev + Link[i].offset1 + Link[i].xsect.yFull; + Node[j].crownElev = MAX(Node[j].crownElev, z); + j = Link[i].node2; + z = Node[j].invertElev + Link[i].offset2 + Link[i].xsect.yFull; + Node[j].crownElev = MAX(Node[j].crownElev, z); + Link[i].flowClass = DRY; + Link[i].dqdh = 0.0; + } + + // --- set crown cutoff for finding top width of closed conduits + if ( SurchargeMethod == SLOT ) CrownCutoff = SLOT_CROWN_CUTOFF; + else CrownCutoff = EXTRAN_CROWN_CUTOFF; +} + +//============================================================================= + +void dynwave_close() +// +// Input: none +// Output: none +// Purpose: frees memory allocated for dynamic wave routing method. +// +{ + FREE(Xnode); +} + +//============================================================================= + +void dynwave_validate() +// +// Input: none +// Output: none +// Purpose: adjusts dynamic wave routing options. +// +{ + if ( MinRouteStep > RouteStep ) MinRouteStep = RouteStep; + if ( MinRouteStep < MINTIMESTEP ) MinRouteStep = MINTIMESTEP; + if ( MinSurfArea == 0.0 ) MinSurfArea = DEFAULT_SURFAREA; + else MinSurfArea /= UCF(LENGTH) * UCF(LENGTH); + if ( HeadTol == 0.0 ) HeadTol = DEFAULT_HEADTOL; + else HeadTol /= UCF(LENGTH); + if ( MaxTrials == 0 ) MaxTrials = DEFAULT_MAXTRIALS; +} + +//============================================================================= + +double dynwave_getRoutingStep(double fixedStep) +// +// Input: fixedStep = user-supplied fixed time step (sec) +// Output: returns routing time step (sec) +// Purpose: computes variable routing time step if applicable. +// +{ + // --- use user-supplied fixed step if variable step option turned off + // or if its smaller than the min. allowable variable time step + if ( CourantFactor == 0.0 ) return fixedStep; + if ( fixedStep < MINTIMESTEP ) return fixedStep; + + // --- at start of simulation (when current variable step is zero) + // use the minimum allowable time step + if ( VariableStep == 0.0 ) + { + VariableStep = MinRouteStep; + } + + // --- otherwise compute variable step based on current flow solution + else VariableStep = getVariableStep(fixedStep); + + // --- adjust step to be a multiple of a millisecond + VariableStep = floor(1000.0 * VariableStep) / 1000.0; + return VariableStep; +} + +//============================================================================= + +int dynwave_execute(double tStep) +// +// Input: links = array of topo sorted links indexes +// tStep = time step (sec) +// Output: returns number of iterations used +// Purpose: routes flows through drainage network over current time step. +// +{ + int converged; + + // --- initialize + if ( ErrorCode ) return 0; + Steps = 0; + converged = FALSE; + Omega = OMEGA; + initRoutingStep(); + + // --- keep iterating until convergence + while ( Steps < MaxTrials ) + { + // --- execute a routing step & check for nodal convergence + initNodeStates(); + findLinkFlows(tStep); + converged = findNodeDepths(tStep); + Steps++; + if ( Steps > 1 ) + { + if ( converged ) break; + + // --- check if link calculations can be skipped in next step + findBypassedLinks(); + } + } + if ( !converged ) updateConvergenceStats(); + + // --- identify any capacity-limited conduits + findLimitedLinks(); + return Steps; +} + +//============================================================================= + +void updateConvergenceStats() +{ + int i; + NonConvergeCount++; + for (i = 0; i < Nobjects[NODE]; i++) + stats_updateConvergenceStats(i, Xnode[i].converged); +} + +//============================================================================= + +void initRoutingStep() +{ + int i; + for (i = 0; i < Nobjects[NODE]; i++) + { + Xnode[i].converged = FALSE; + Xnode[i].dYdT = 0.0; + } + for (i = 0; i < Nobjects[LINK]; i++) + { + Link[i].bypassed = FALSE; + Link[i].surfArea1 = 0.0; + Link[i].surfArea2 = 0.0; + } + + // --- a2 preserves conduit area from solution at last time step + for ( i = 0; i < Nlinks[CONDUIT]; i++) Conduit[i].a2 = Conduit[i].a1; +} + +//============================================================================= + +void initNodeStates() +// +// Input: none +// Output: none +// Purpose: initializes node's surface area, inflow & outflow +// +{ + int i; + + for (i = 0; i < Nobjects[NODE]; i++) + { + // --- initialize nodal surface area + if ( AllowPonding ) + { + Xnode[i].newSurfArea = node_getPondedArea(i, Node[i].newDepth); + } + else + { + Xnode[i].newSurfArea = node_getSurfArea(i, Node[i].newDepth); + } + + // --- initialize nodal inflow & outflow + Node[i].inflow = 0.0; + Node[i].outflow = Node[i].losses; + if ( Node[i].newLatFlow >= 0.0 ) + { + Node[i].inflow += Node[i].newLatFlow; + } + else + { + Node[i].outflow -= Node[i].newLatFlow; + } + Xnode[i].sumdqdh = 0.0; + } +} + +//============================================================================= + +void findBypassedLinks() +{ + int i; + for (i = 0; i < Nobjects[LINK]; i++) + { + if ( Xnode[Link[i].node1].converged && + Xnode[Link[i].node2].converged ) + Link[i].bypassed = TRUE; + else Link[i].bypassed = FALSE; + } +} + +//============================================================================= + +void findLimitedLinks() +// +// Input: none +// Output: none +// Purpose: determines if a conduit link is capacity limited. +// +{ + int j, n1, n2, k; + double h1, h2; + + for (j = 0; j < Nobjects[LINK]; j++) + { + // ---- check only non-dummy conduit links + if ( !isTrueConduit(j) ) continue; + + // --- check that upstream end is full + k = Link[j].subIndex; + Conduit[k].capacityLimited = FALSE; + if ( Conduit[k].a1 >= Link[j].xsect.aFull ) + { + // --- check if HGL slope > conduit slope + n1 = Link[j].node1; + n2 = Link[j].node2; + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + if ( (h1 - h2) > fabs(Conduit[k].slope) * Conduit[k].length ) + Conduit[k].capacityLimited = TRUE; + } + } +} + +//============================================================================= + +void findLinkFlows(double dt) +{ + int i; + + // --- find new flow in each non-dummy conduit +#pragma omp parallel num_threads(NumThreads) +{ + #pragma omp for + for ( i = 0; i < Nobjects[LINK]; i++) + { + if ( isTrueConduit(i) && !Link[i].bypassed ) + dwflow_findConduitFlow(i, Steps, Omega, dt); + } +} + + // --- update inflow/outflows for nodes attached to non-dummy conduits + for ( i = 0; i < Nobjects[LINK]; i++) + { + if ( isTrueConduit(i) ) updateNodeFlows(i); + } + + // --- find new flows for all dummy conduits, pumps & regulators + for ( i = 0; i < Nobjects[LINK]; i++) + { + if ( !isTrueConduit(i) ) + { + if ( !Link[i].bypassed ) findNonConduitFlow(i, dt); + updateNodeFlows(i); + } + } +} + +//============================================================================= + +int isTrueConduit(int j) +{ + return ( Link[j].type == CONDUIT && Link[j].xsect.type != DUMMY ); +} + +//============================================================================= + +void findNonConduitFlow(int i, double dt) +// +// Input: i = link index +// dt = time step (sec) +// Output: none +// Purpose: finds new flow in a non-conduit-type link +// +{ + double qLast; // previous link flow (cfs) + double qNew; // new link flow (cfs) + + // --- get link flow from last iteration + qLast = Link[i].newFlow; + Link[i].dqdh = 0.0; + + // --- get new inflow to link from its upstream node + // (link_getInflow returns 0 if flap gate closed or pump is offline) + qNew = link_getInflow(i); + if ( Link[i].type == PUMP ) qNew = getModPumpFlow(i, qNew, dt); + + // --- find surface area at each end of link + findNonConduitSurfArea(i); + + // --- apply under-relaxation with flow from previous iteration; + // --- do not allow flow to change direction without first being 0 + if ( Steps > 0 && Link[i].type != PUMP ) + { + qNew = (1.0 - Omega) * qLast + Omega * qNew; + if ( qNew * qLast < 0.0 ) qNew = 0.001 * SGN(qNew); + } + Link[i].newFlow = qNew; +} + +//============================================================================= + +double getModPumpFlow(int i, double q, double dt) +// +// Input: i = link index +// q = pump flow from pump curve (cfs) +// dt = time step (sec) +// Output: returns modified pump flow rate (cfs) +// Purpose: modifies pump curve pumping rate depending on amount of water +// available at pump's inlet node. +// +{ + int j = Link[i].node1; // pump's inlet node index + int k = Link[i].subIndex; // pump's index + double newNetInflow; // inflow - outflow rate (cfs) + double netFlowVolume; // inflow - outflow volume (ft3) + double y; // node depth (ft) + + if ( q == 0.0 ) return q; + + // --- case where inlet node is a storage node: + // prevent node volume from going negative + if ( Node[j].type == STORAGE ) return node_getMaxOutflow(j, q, dt); + + // --- case where inlet is a non-storage node + switch ( Pump[k].type ) + { + // --- for Type1 pump, a volume is computed for inlet node, + // so make sure it doesn't go negative + case TYPE1_PUMP: + return node_getMaxOutflow(j, q, dt); + + // --- for other types of pumps, if pumping rate would make depth + // at upstream node negative, then set pumping rate = inflow + case TYPE2_PUMP: + case TYPE4_PUMP: + case TYPE3_PUMP: + newNetInflow = Node[j].inflow - Node[j].outflow - q; + netFlowVolume = 0.5 * (Node[j].oldNetInflow + newNetInflow ) * dt; + y = Node[j].oldDepth + netFlowVolume / Xnode[j].newSurfArea; + if ( y <= 0.0 ) return Node[j].inflow; + } + return q; +} + +//============================================================================= + +void findNonConduitSurfArea(int i) +// +// Input: i = link index +// Output: none +// Purpose: finds the surface area contributed by a non-conduit +// link to its upstream and downstream nodes. +// +{ + if ( Link[i].type == ORIFICE ) + { + Link[i].surfArea1 = Orifice[Link[i].subIndex].surfArea / 2.; + } + + // --- no surface area for weirs to maintain SWMM 4 compatibility + else Link[i].surfArea1 = 0.0; + + Link[i].surfArea2 = Link[i].surfArea1; + if ( Link[i].flowClass == UP_CRITICAL || + Node[Link[i].node1].type == STORAGE ) Link[i].surfArea1 = 0.0; + if ( Link[i].flowClass == DN_CRITICAL || + Node[Link[i].node2].type == STORAGE ) Link[i].surfArea2 = 0.0; +} + +//============================================================================= + +void updateNodeFlows(int i) +// +// Input: i = link index +// q = link flow rate (cfs) +// Output: none +// Purpose: updates cumulative inflow & outflow at link's end nodes. +// +{ + int k; + int barrels = 1; + int n1 = Link[i].node1; + int n2 = Link[i].node2; + double q = Link[i].newFlow; + double uniformLossRate = 0.0; + + // --- compute any uniform seepage loss from a conduit + if ( Link[i].type == CONDUIT ) + { + k = Link[i].subIndex; + uniformLossRate = Conduit[k].evapLossRate + Conduit[k].seepLossRate; + barrels = Conduit[k].barrels; + uniformLossRate *= barrels; + } + + // --- update total inflow & outflow at upstream/downstream nodes + if ( q >= 0.0 ) + { + Node[n1].outflow += q + uniformLossRate; + Node[n2].inflow += q; + } + else + { + Node[n1].inflow -= q; + Node[n2].outflow -= q - uniformLossRate; + } + + // --- add surf. area contributions to upstream/downstream nodes + Xnode[Link[i].node1].newSurfArea += Link[i].surfArea1 * barrels; + Xnode[Link[i].node2].newSurfArea += Link[i].surfArea2 * barrels; + + // --- update summed value of dqdh at each end node + Xnode[Link[i].node1].sumdqdh += Link[i].dqdh; + if ( Link[i].type == PUMP ) + { + k = Link[i].subIndex; + if ( Pump[k].type != TYPE4_PUMP ) + { + Xnode[n2].sumdqdh += Link[i].dqdh; + } + } + else Xnode[n2].sumdqdh += Link[i].dqdh; +} + +//============================================================================= + +int findNodeDepths(double dt) +// +// Input: dt = time step (sec) +// Output: returns TRUE if depth change at all non-Outfall nodes is +// within the convergence tolerance and FALSE otherwise +// Purpose: finds new depth at all nodes and checks if convergence achieved. +// +{ + int i; + double yOld; // previous node depth (ft) + + // --- compute outfall depths based on flow in connecting link + for ( i = 0; i < Nobjects[LINK]; i++ ) link_setOutfallDepth(i); + + // --- compute new depth for all non-outfall nodes and determine if + // depth change from previous iteration is below tolerance +#pragma omp parallel num_threads(NumThreads) +{ + #pragma omp for private(yOld) + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + if ( Node[i].type == OUTFALL ) continue; + yOld = Node[i].newDepth; + setNodeDepth(i, dt); + Xnode[i].converged = TRUE; + if ( fabs(yOld - Node[i].newDepth) > HeadTol ) + { + Xnode[i].converged = FALSE; + } + } +} + + // --- return FALSE if any non-Outfall node failed to converge + for (i = 0; i < Nobjects[NODE]; i++) + { + if ( Node[i].type == OUTFALL ) continue; + if (Xnode[i].converged == FALSE) return FALSE; + } + return TRUE; +} + +//============================================================================= + +void setNodeDepth(int i, double dt) +// +// Input: i = node index +// dt = time step (sec) +// Output: none +// Purpose: sets depth at non-outfall node after current time step. +// +{ + int canPond; // TRUE if node can pond overflows + int isPonded; // TRUE if node is currently ponded + int isSurcharged = FALSE; // TRUE if node is surcharged + double dQ; // inflow minus outflow at node (cfs) + double dV; // change in node volume (ft3) + double dy; // change in node depth (ft) + double yMax; // max. depth at node (ft) + double yOld; // node depth at previous time step (ft) + double yLast; // previous node depth (ft) + double yNew; // new node depth (ft) + double yCrown; // depth to node crown (ft) + double surfArea; // node surface area (ft2) + double denom; // denominator term + double corr; // correction factor + double f; // relative surcharge depth + + // --- see if node can pond water above it + canPond = (AllowPonding && Node[i].pondedArea > 0.0); + isPonded = (canPond && Node[i].newDepth > Node[i].fullDepth); + + // --- initialize values + yCrown = Node[i].crownElev - Node[i].invertElev; + yOld = Node[i].oldDepth; + yLast = Node[i].newDepth; + Node[i].overflow = 0.0; + surfArea = Xnode[i].newSurfArea; + surfArea = MAX(surfArea, MinSurfArea); + + // --- determine average net flow volume into node over the time step + dQ = Node[i].inflow - Node[i].outflow; + dV = 0.5 * (Node[i].oldNetInflow + dQ) * dt; + + + // --- determine if node is EXTRAN surcharged + if (SurchargeMethod == EXTRAN) + { + // --- ponded nodes don't surcharge + if (isPonded) isSurcharged = FALSE; + + // --- closed storage units that are full are in surcharge + else if (Node[i].type == STORAGE) + { + isSurcharged = (Node[i].surDepth > 0.0 && + yLast > Node[i].fullDepth); + } + + // --- surcharge occurs when node depth exceeds top of its highest link + else isSurcharged = (yCrown > 0.0 && yLast > yCrown); + } + + // --- if node not surcharged, base depth change on surface area + if (!isSurcharged) + { + dy = dV / surfArea; + yNew = yOld + dy; + + // --- save non-ponded surface area for use in surcharge algorithm + if ( !isPonded ) Xnode[i].oldSurfArea = surfArea; + + // --- apply under-relaxation to new depth estimate + if ( Steps > 0 ) + { + yNew = (1.0 - Omega) * yLast + Omega * yNew; + } + + // --- don't allow a ponded node to drop much below full depth + if ( isPonded && yNew < Node[i].fullDepth ) + yNew = Node[i].fullDepth - FUDGE; + } + + // --- if node surcharged, base depth change on dqdh + // NOTE: depth change is w.r.t depth from previous + // iteration; also, do not apply under-relaxation. + else + { + // --- apply correction factor for upstream terminal nodes + corr = 1.0; + if ( Node[i].degree < 0 ) corr = 0.6; + + // --- allow surface area from last non-surcharged condition + // to influence dqdh if depth close to crown depth + denom = Xnode[i].sumdqdh; + if ( yLast < 1.25 * yCrown ) + { + f = (yLast - yCrown) / yCrown; + denom += (Xnode[i].oldSurfArea/dt - + Xnode[i].sumdqdh) * exp(-15.0 * f); + } + + // --- compute new estimate of node depth + if ( denom == 0.0 ) dy = 0.0; + else dy = corr * dQ / denom; + yNew = yLast + dy; + if ( yNew < yCrown ) yNew = yCrown - FUDGE; + + // --- don't allow a newly ponded node to rise much above full depth + if ( canPond && yNew > Node[i].fullDepth ) + yNew = Node[i].fullDepth + FUDGE; + } + + // --- depth cannot be negative + if ( yNew < 0 ) yNew = 0.0; + + // --- determine max. non-flooded depth + yMax = Node[i].fullDepth; + if ( canPond == FALSE ) yMax += Node[i].surDepth; + + // --- find flooded depth & volume + if ( yNew > yMax ) + { + yNew = getFloodedDepth(i, canPond, dV, yNew, yMax, dt); + } + else Node[i].newVolume = node_getVolume(i, yNew); + + // --- compute change in depth w.r.t. time + Xnode[i].dYdT = fabs(yNew - yOld) / dt; + + // --- save new depth for node + Node[i].newDepth = yNew; +} + +//============================================================================= + +double getFloodedDepth(int i, int canPond, double dV, double yNew, + double yMax, double dt) +// +// Input: i = node index +// canPond = TRUE if water can pond over node +// isPonded = TRUE if water is currently ponded +// dV = change in volume over time step (ft3) +// yNew = current depth at node (ft) +// yMax = max. depth at node before ponding (ft) +// dt = time step (sec) +// Output: returns depth at node when flooded (ft) +// Purpose: computes depth, volume and overflow for a flooded node. +// +{ + if ( canPond == FALSE ) + { + Node[i].overflow = dV / dt; + Node[i].newVolume = Node[i].fullVolume; + yNew = yMax; + } + else + { + Node[i].newVolume = MAX((Node[i].oldVolume+dV), Node[i].fullVolume); + Node[i].overflow = (Node[i].newVolume - + MAX(Node[i].oldVolume, Node[i].fullVolume)) / dt; + } + if ( Node[i].overflow < FUDGE ) Node[i].overflow = 0.0; + return yNew; + +} + +//============================================================================= + +double getVariableStep(double maxStep) +// +// Input: maxStep = user-supplied max. time step (sec) +// Output: returns time step (sec) +// Purpose: finds time step that satisfies stability criterion but +// is no greater than the user-supplied max. time step. +// +{ + int minLink = -1; // index of link w/ min. time step + int minNode = -1; // index of node w/ min. time step + double tMin; // allowable time step (sec) + double tMinLink; // allowable time step for links (sec) + double tMinNode; // allowable time step for nodes (sec) + + // --- find stable time step for links & then nodes + tMin = maxStep; + tMinLink = getLinkStep(tMin, &minLink); + tMinNode = getNodeStep(tMinLink, &minNode); + + // --- use smaller of the link and node time step + tMin = tMinLink; + if ( tMinNode < tMin ) + { + tMin = tMinNode ; + minLink = -1; + } + + // --- update count of times the minimum node or link was critical + stats_updateCriticalTimeCount(minNode, minLink); + + // --- don't let time step go below an absolute minimum + if ( tMin < MinRouteStep ) tMin = MinRouteStep; + return tMin; +} + +//============================================================================= + +double getLinkStep(double tMin, int *minLink) +// +// Input: tMin = critical time step found so far (sec) +// Output: minLink = index of link with critical time step; +// returns critical time step (sec) +// Purpose: finds critical time step for conduits based on Courant criterion. +// +{ + int i; // link index + int k; // conduit index + double q; // conduit flow (cfs) + double t; // time step (sec) + double tLink = tMin; // critical link time step (sec) + + // --- examine each conduit link + for ( i = 0; i < Nobjects[LINK]; i++ ) + { + if ( Link[i].type == CONDUIT ) + { + // --- skip conduits with negligible flow, area or Fr + k = Link[i].subIndex; + q = fabs(Link[i].newFlow) / Conduit[k].barrels; + if ( q <= FUDGE + || Conduit[k].a1 <= FUDGE + || Link[i].froude <= 0.01 + ) continue; + + // --- compute time step to satisfy Courant condition + t = Link[i].newVolume / Conduit[k].barrels / q; + t = t * Conduit[k].modLength / link_getLength(i); + t = t * Link[i].froude / (1.0 + Link[i].froude) * CourantFactor; + + // --- update critical link time step + if ( t < tLink ) + { + tLink = t; + *minLink = i; + } + } + } + return tLink; +} + +//============================================================================= + +double getNodeStep(double tMin, int *minNode) +// +// Input: tMin = critical time step found so far (sec) +// Output: minNode = index of node with critical time step; +// returns critical time step (sec) +// Purpose: finds critical time step for nodes based on max. allowable +// projected change in depth. +// +{ + int i; // node index + double maxDepth; // max. depth allowed at node (ft) + double dYdT; // change in depth per unit time (ft/sec) + double t1; // time needed to reach depth limit (sec) + double tNode = tMin; // critical node time step (sec) + + // --- find smallest time so that estimated change in nodal depth + // does not exceed safety factor * maxdepth + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + // --- see if node can be skipped + if ( Node[i].type == OUTFALL ) continue; + if ( Node[i].newDepth <= FUDGE) continue; + if ( Node[i].newDepth + FUDGE >= + Node[i].crownElev - Node[i].invertElev ) continue; + + // --- define max. allowable depth change using crown elevation + maxDepth = (Node[i].crownElev - Node[i].invertElev) * 0.25; + if ( maxDepth < FUDGE ) continue; + dYdT = Xnode[i].dYdT; + if (dYdT < FUDGE ) continue; + + // --- compute time to reach max. depth & compare with critical time + t1 = maxDepth / dYdT; + if ( t1 < tNode ) + { + tNode = t1; + *minNode = i; + } + } + return tNode; +} diff --git a/src/enums.h b/src/enums.h new file mode 100644 index 000000000..fc3cc2b4b --- /dev/null +++ b/src/enums.h @@ -0,0 +1,497 @@ +//----------------------------------------------------------------------------- +// enums.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Enumerated constants +// +// Update History +// ============== +// Build 5.1.004: +// - IGNORE_RDII for the ignore RDII option added. +// Build 5.1.007: +// - s_GWF for [GWF] input file section added. +// - s_ADJUST for [ADJUSTMENTS] input file section added. +// Build 5.1.008: +// - Enumerations for fullness state of a conduit added. +// - NUM_THREADS added for number of parallel threads option. +// - Runoff flow categories added to represent mass balance components. +// Build 5.1.010: +// - New ROADWAY_WEIR type of weir added. +// - Potential evapotranspiration (PET) added as a system output variable. +// Build 5.1.011: +// - s_EVENT added to InputSectionType enumeration. +// Build 5.1.013: +// - SURCHARGE_METHOD and RULE_STEP options added. +// - WEIR_CURVE added as a curve type. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for variable speed pumps. +// - Support added for analytical storage shapes. +//----------------------------------------------------------------------------- + +#ifndef ENUMS_H +#define ENUMS_H + + +//------------------------------------- +// Names of major object types +//------------------------------------- + enum ObjectType { + GAGE, // rain gage + SUBCATCH, // subcatchment + NODE, // conveyance system node + LINK, // conveyance system link + POLLUT, // pollutant + LANDUSE, // land use category + TIMEPATTERN, // dry weather flow time pattern + CURVE, // generic table of values + TSERIES, // generic time series of values + CONTROL, // conveyance system control rules + TRANSECT, // irregular channel cross-section + AQUIFER, // groundwater aquifer + UNITHYD, // RDII unit hydrograph + SNOWMELT, // snowmelt parameter set + SHAPE, // custom conduit shape + LID, // LID treatment units + STREET, // street cross section + INLET, // street inlet design + MAX_OBJ_TYPES}; + +//------------------------------------- +// Names of Node sub-types +//------------------------------------- + #define MAX_NODE_TYPES 5 + enum NodeType { + JUNCTION, + OUTFALL, + STORAGE, + DIVIDER}; + +//------------------------------------- +// Names of Link sub-types +//------------------------------------- + #define MAX_LINK_TYPES 5 + enum LinkType { + CONDUIT, + PUMP, + ORIFICE, + WEIR, + OUTLET}; + +//------------------------------------- +// File types +//------------------------------------- + enum FileType { + RAINFALL_FILE, // rainfall file + RUNOFF_FILE, // runoff file + HOTSTART_FILE, // hotstart file + RDII_FILE, // RDII file + INFLOWS_FILE, // inflows interface file + OUTFLOWS_FILE}; // outflows interface file + +//------------------------------------- +// File usage types +//------------------------------------- + enum FileUsageType { + NO_FILE, // no file usage + SCRATCH_FILE, // use temporary scratch file + USE_FILE, // use previously saved file + SAVE_FILE}; // save file currently in use + +//------------------------------------- +// Rain gage data types +//------------------------------------- + enum GageDataType { + RAIN_TSERIES, // rainfall from user-supplied time series + RAIN_FILE}; // rainfall from external file + +//------------------------------------- +// Cross section shape types +//------------------------------------- + enum XsectType { + DUMMY, // 0 + CIRCULAR, // 1 closed + FILLED_CIRCULAR, // 2 closed + RECT_CLOSED, // 3 closed + RECT_OPEN, // 4 + TRAPEZOIDAL, // 5 + TRIANGULAR, // 6 + PARABOLIC, // 7 + POWERFUNC, // 8 + RECT_TRIANG, // 9 + RECT_ROUND, // 10 + MOD_BASKET, // 11 + HORIZ_ELLIPSE, // 12 closed + VERT_ELLIPSE, // 13 closed + ARCH, // 14 closed + EGGSHAPED, // 15 closed + HORSESHOE, // 16 closed + GOTHIC, // 17 closed + CATENARY, // 18 closed + SEMIELLIPTICAL, // 19 closed + BASKETHANDLE, // 20 closed + SEMICIRCULAR, // 21 closed + IRREGULAR, // 22 + CUSTOM, // 23 closed + FORCE_MAIN, // 24 closed + STREET_XSECT}; // 25 + +//------------------------------------- +// Measurement units types +//------------------------------------- + enum UnitsType { + US, // US units + SI}; // SI (metric) units + + enum FlowUnitsType { + CFS, // cubic feet per second + GPM, // gallons per minute + MGD, // million gallons per day + CMS, // cubic meters per second + LPS, // liters per second + MLD}; // million liters per day + + enum ConcUnitsType { + MG, // Milligrams / L + UG, // Micrograms / L + COUNT}; // Counts / L + +//-------------------------------------- +// Quantities requiring unit conversions +//-------------------------------------- + enum ConversionType { + RAINFALL, + RAINDEPTH, + EVAPRATE, + LENGTH, + LANDAREA, + VOLUME, + WINDSPEED, + TEMPERATURE, + MASS, + GWFLOW, + FLOW}; // Flow must always be listed last + +//------------------------------------- +// Computed subcatchment quantities +//------------------------------------- + #define MAX_SUBCATCH_RESULTS 9 + enum SubcatchResultType { + SUBCATCH_RAINFALL, // rainfall intensity + SUBCATCH_SNOWDEPTH, // snow depth + SUBCATCH_EVAP, // evap loss + SUBCATCH_INFIL, // infil loss + SUBCATCH_RUNOFF, // runoff flow rate + SUBCATCH_GW_FLOW, // groundwater flow rate to node + SUBCATCH_GW_ELEV, // elevation of saturated gw table + SUBCATCH_SOIL_MOIST, // soil moisture + SUBCATCH_WASHOFF}; // pollutant washoff concentration + +//------------------------------------- +// Computed node quantities +//------------------------------------- + #define MAX_NODE_RESULTS 7 + enum NodeResultType { + NODE_DEPTH, // water depth above invert + NODE_HEAD, // hydraulic head + NODE_VOLUME, // volume stored & ponded + NODE_LATFLOW, // lateral inflow rate + NODE_INFLOW, // total inflow rate + NODE_OVERFLOW, // overflow rate + NODE_QUAL}; // concentration of each pollutant + +//------------------------------------- +// Computed link quantities +//------------------------------------- + #define MAX_LINK_RESULTS 6 + enum LinkResultType { + LINK_FLOW, // flow rate + LINK_DEPTH, // flow depth + LINK_VELOCITY, // flow velocity + LINK_VOLUME, // link volume + LINK_CAPACITY, // ratio of area to full area + LINK_QUAL}; // concentration of each pollutant + +//------------------------------------- +// System-wide quantities +//------------------------------------- +#define MAX_SYS_RESULTS 15 +enum SysFlowType { + SYS_TEMPERATURE, // air temperature + SYS_RAINFALL, // rainfall intensity + SYS_SNOWDEPTH, // snow depth + SYS_INFIL, // infil + SYS_RUNOFF, // runoff flow + SYS_DWFLOW, // dry weather inflow + SYS_GWFLOW, // ground water inflow + SYS_IIFLOW, // RDII inflow + SYS_EXFLOW, // external inflow + SYS_INFLOW, // total lateral inflow + SYS_FLOODING, // flooding outflow + SYS_OUTFLOW, // outfall outflow + SYS_STORAGE, // storage volume + SYS_EVAP, // evaporation + SYS_PET}; // potential ET + +//------------------------------------- +// Conduit flow classifications +//------------------------------------- + enum FlowClassType { + DRY, // dry conduit + UP_DRY, // upstream end is dry + DN_DRY, // downstream end is dry + SUBCRITICAL, // sub-critical flow + SUPCRITICAL, // super-critical flow + UP_CRITICAL, // free-fall at upstream end + DN_CRITICAL, // free-fall at downstream end + MAX_FLOW_CLASSES, // number of distinct flow classes + UP_FULL, // upstream end is full + DN_FULL, // downstream end is full + ALL_FULL}; // completely full + +//------------------------ +// Runoff flow categories +//------------------------ +enum RunoffFlowType { + RUNOFF_RAINFALL, // rainfall + RUNOFF_EVAP, // evaporation + RUNOFF_INFIL, // infiltration + RUNOFF_RUNOFF, // runoff + RUNOFF_DRAINS, // LID drain flow + RUNOFF_RUNON}; // runon from outfalls + +//------------------------------------- +// Surface pollutant loading categories +//------------------------------------- + enum LoadingType { + BUILDUP_LOAD, // pollutant buildup load + DEPOSITION_LOAD, // rainfall deposition load + SWEEPING_LOAD, // load removed by sweeping + BMP_REMOVAL_LOAD, // load removed by BMPs + INFIL_LOAD, // runon load removed by infiltration + RUNOFF_LOAD, // load removed by runoff + FINAL_LOAD}; // load remaining on surface + +//------------------------------------- +// Input data options +//------------------------------------- + enum RainfallType { + RAINFALL_INTENSITY, // rainfall expressed as intensity + RAINFALL_VOLUME, // rainfall expressed as volume + CUMULATIVE_RAINFALL}; // rainfall expressed as cumulative volume + + enum TempType { + NO_TEMP, // no temperature data supplied + TSERIES_TEMP, // temperatures come from time series + FILE_TEMP}; // temperatures come from file + +enum WindType { + MONTHLY_WIND, // wind speed varies by month + FILE_WIND}; // wind speed comes from file + + enum EvapType { + CONSTANT_EVAP, // constant evaporation rate + MONTHLY_EVAP, // evaporation rate varies by month + TIMESERIES_EVAP, // evaporation supplied by time series + TEMPERATURE_EVAP, // evaporation from daily temperature + FILE_EVAP, // evaporation comes from file + RECOVERY, // soil recovery pattern + DRYONLY}; // evap. allowed only in dry periods + + enum NormalizerType { + PER_AREA, // buildup is per unit of area + PER_CURB}; // buildup is per unit of curb length + + enum BuildupType { + NO_BUILDUP, // no buildup + POWER_BUILDUP, // power function buildup equation + EXPON_BUILDUP, // exponential function buildup equation + SATUR_BUILDUP, // saturation function buildup equation + EXTERNAL_BUILDUP}; // external time series buildup + + enum WashoffType { + NO_WASHOFF, // no washoff + EXPON_WASHOFF, // exponential washoff equation + RATING_WASHOFF, // rating curve washoff equation + EMC_WASHOFF}; // event mean concentration washoff + +enum SubAreaType { + IMPERV0, // impervious w/o depression storage + IMPERV1, // impervious w/ depression storage + PERV}; // pervious + + enum RunoffRoutingType { + TO_OUTLET, // perv & imperv runoff goes to outlet + TO_IMPERV, // perv runoff goes to imperv area + TO_PERV}; // imperv runoff goes to perv subarea + + enum RouteModelType { + NO_ROUTING, // no routing + SF, // steady flow model + KW, // kinematic wave model + EKW, // extended kin. wave model + DW}; // dynamic wave model + + enum ForceMainType { + H_W, // Hazen-Williams eqn. + D_W}; // Darcy-Weisbach eqn. + + enum OffsetType { + DEPTH_OFFSET, // offset measured as depth + ELEV_OFFSET}; // offset measured as elevation + + enum KinWaveMethodType { + NORMAL, // normal method + MODIFIED}; // modified method + +enum CompatibilityType { + SWMM5, // SWMM 5 weighting for area & hyd. radius + SWMM3, // SWMM 3 weighting + SWMM4}; // SWMM 4 weighting + + enum NormalFlowType { + SLOPE, // based on slope only + FROUDE, // based on Fr only + BOTH}; // based on slope & Fr + + enum InertialDampingType { + NO_DAMPING, // no inertial damping + PARTIAL_DAMPING, // partial damping + FULL_DAMPING}; // full damping + + enum SurchargeMethodType { + EXTRAN, // original EXTRAN method + SLOT}; // Preissmann slot method + + enum InflowType { + EXTERNAL_INFLOW, // user-supplied external inflow + DRY_WEATHER_INFLOW, // user-supplied dry weather inflow + WET_WEATHER_INFLOW, // computed runoff inflow + GROUNDWATER_INFLOW, // computed groundwater inflow + RDII_INFLOW, // computed I&I inflow + FLOW_INFLOW, // inflow parameter is flow + CONCEN_INFLOW, // inflow parameter is pollutant concen. + MASS_INFLOW}; // inflow parameter is pollutant mass + + enum PatternType { + MONTHLY_PATTERN, // DWF multipliers for each month + DAILY_PATTERN, // DWF multipliers for each day of week + HOURLY_PATTERN, // DWF multipliers for each hour of day + WEEKEND_PATTERN}; // hourly multipliers for week end days + + enum OutfallType { + FREE_OUTFALL, // critical depth outfall condition + NORMAL_OUTFALL, // normal flow depth outfall condition + FIXED_OUTFALL, // fixed depth outfall condition + TIDAL_OUTFALL, // variable tidal stage outfall condition + TIMESERIES_OUTFALL}; // variable time series outfall depth + + enum StorageType { + TABULAR, // area v. depth from table + FUNCTIONAL, // area v. depth from power function + CYLINDRICAL, // area v. depth from elliptical cylinder + CONICAL, // area v. depth from elliptical cone + PARABOLOID, // area v. depth from elliptical paraboloid + PYRAMIDAL}; // area v. depth from rectangular pyramid + + enum ReactorType { + CSTR, // completely mixed reactor + PLUG}; // plug flow reactor + + enum TreatmentType { + REMOVAL, // treatment stated as a removal + CONCEN}; // treatment stated as effluent concen. + + enum DividerType { + CUTOFF_DIVIDER, // diverted flow is excess of cutoff flow + TABULAR_DIVIDER, // table of diverted flow v. inflow + WEIR_DIVIDER, // diverted flow proportional to excess flow + OVERFLOW_DIVIDER}; // diverted flow is flow > full conduit flow + + enum PumpCurveType { + TYPE1_PUMP, // flow varies stepwise with wet well volume + TYPE2_PUMP, // flow varies stepwise with inlet depth + TYPE3_PUMP, // flow varies with head delivered + TYPE4_PUMP, // flow varies with inlet depth + TYPE5_PUMP, // variable speed version of TYPE3 pump + IDEAL_PUMP}; // outflow equals inflow + + enum OrificeType { + SIDE_ORIFICE, // side orifice + BOTTOM_ORIFICE}; // bottom orifice + + enum WeirType { + TRANSVERSE_WEIR, // transverse weir + SIDEFLOW_WEIR, // side flow weir + VNOTCH_WEIR, // V-notch (triangular) weir + TRAPEZOIDAL_WEIR, // trapezoidal weir + ROADWAY_WEIR}; // FHWA HDS-5 roadway weir + + enum CurveType { + STORAGE_CURVE, // surf. area v. depth for storage node + DIVERSION_CURVE, // diverted flow v. inflow for divider node + TIDAL_CURVE, // water elev. v. hour of day for outfall + RATING_CURVE, // flow rate v. head for outlet link + CONTROL_CURVE, // control setting v. controller variable + SHAPE_CURVE, // width v. depth for custom x-section + WEIR_CURVE, // discharge coeff. v. head for weir + PUMP1_CURVE, // flow v. wet well volume for pump + PUMP2_CURVE, // flow v. depth for pump (discrete) + PUMP3_CURVE, // flow v. head for pump (continuous) + PUMP4_CURVE, // flow v. depth for pump (continuous) + PUMP5_CURVE}; // variable speed version of TYPE3 pump + + enum NodeInletType { + NO_INLET, + BYPASS, + CAPTURE + }; + + enum InputSectionType { + s_TITLE, s_OPTION, s_FILE, s_RAINGAGE, + s_TEMP, s_EVAP, s_SUBCATCH, s_SUBAREA, + s_INFIL, s_AQUIFER, s_GROUNDWATER, s_SNOWMELT, + s_JUNCTION, s_OUTFALL, s_STORAGE, s_DIVIDER, + s_CONDUIT, s_PUMP, s_ORIFICE, s_WEIR, + s_OUTLET, s_XSECTION, s_TRANSECT, s_LOSSES, + s_CONTROL, s_POLLUTANT, s_LANDUSE, s_BUILDUP, + s_WASHOFF, s_COVERAGE, s_INFLOW, s_DWF, + s_PATTERN, s_RDII, s_UNITHYD, s_LOADING, + s_TREATMENT, s_CURVE, s_TIMESERIES, s_REPORT, + s_COORDINATE, s_VERTICES, s_POLYGON, s_LABEL, + s_SYMBOL, s_BACKDROP, s_TAG, s_PROFILE, + s_MAP, s_LID_CONTROL, s_LID_USAGE, s_GWF, + s_ADJUST, s_EVENT, s_STREET, s_INLET_USAGE, + s_INLET}; + + enum InputOptionType { + FLOW_UNITS, INFIL_MODEL, ROUTE_MODEL, + START_DATE, START_TIME, END_DATE, + END_TIME, REPORT_START_DATE, REPORT_START_TIME, + SWEEP_START, SWEEP_END, START_DRY_DAYS, + WET_STEP, DRY_STEP, ROUTE_STEP, RULE_STEP, + REPORT_STEP, ALLOW_PONDING, INERT_DAMPING, + SLOPE_WEIGHTING, VARIABLE_STEP, NORMAL_FLOW_LTD, + LENGTHENING_STEP, MIN_SURFAREA, COMPATIBILITY, + SKIP_STEADY_STATE, TEMPDIR, IGNORE_RAINFALL, + FORCE_MAIN_EQN, LINK_OFFSETS, MIN_SLOPE, + IGNORE_SNOWMELT, IGNORE_GWATER, IGNORE_ROUTING, + IGNORE_QUALITY, MAX_TRIALS, HEAD_TOL, + SYS_FLOW_TOL, LAT_FLOW_TOL, IGNORE_RDII, + MIN_ROUTE_STEP, NUM_THREADS, SURCHARGE_METHOD}; + +enum NoYesType { + NO, + YES}; + +enum NoneAllType { + NONE, + ALL, + SOME}; + + +#endif //ENUMS_H diff --git a/src/error.c b/src/error.c new file mode 100644 index 000000000..626950b6f --- /dev/null +++ b/src/error.c @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// error.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Error messages +// +// Update History +// ============== +// Build 5.1.008: +// - Text of Error 217 for control rules modified. +// Build 5.1.010: +// - Text of Error 318 for rainfall data files modified. +// Build 5.1.015: +// - Added new Error 140 for storage nodes. +// Build 5.2.0: +// - Re-designed error message system. +// - Added new Error 235 for invalid infiltration parameters. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "error.h" + +char ErrString[256]; + +char* error_getMsg(int errCode, char* msg) +{ + switch (errCode) + { + +#define ERR(code,string) case code: strcpy(msg, string); break; +#include "error.txt" +#undef ERR + + default: + strcpy(msg, ""); + } + return (msg); +}; + +int error_setInpError(int errcode, char* s) +{ + strcpy(ErrString, s); + return errcode; +} diff --git a/src/error.h b/src/error.h new file mode 100644 index 000000000..fedf0847e --- /dev/null +++ b/src/error.h @@ -0,0 +1,182 @@ +//----------------------------------------------------------------------------- +// error.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Error codes +// +//----------------------------------------------------------------------------- + +#ifndef ERROR_H +#define ERROR_H + +enum ErrorType { + +// ... Runtime Errors + ERR_NONE = 0, + ERR_MEMORY = 101, + ERR_KINWAVE = 103, + ERR_ODE_SOLVER = 105, + ERR_TIMESTEP = 107, + +// ... Subcatchment/Aquifer Errors + ERR_SUBCATCH_OUTLET = 108, + ERR_AQUIFER_PARAMS = 109, + ERR_GROUND_ELEV = 110, + +// ... Conduit/Pump Errors + ERR_LENGTH = 111, + ERR_ELEV_DROP = 112, + ERR_ROUGHNESS = 113, + ERR_BARRELS = 114, + ERR_SLOPE = 115, + ERR_NO_XSECT = 117, + ERR_XSECT = 119, + ERR_NO_CURVE = 121, + ERR_PUMP_LIMITS = 122, + +// ... Topology Errors + ERR_LOOP = 131, + ERR_MULTI_OUTLET = 133, + ERR_DUMMY_LINK = 134, + +// ... Node Errors + ERR_DIVIDER = 135, + ERR_DIVIDER_LINK = 136, + ERR_WEIR_DIVIDER = 137, + ERR_NODE_DEPTH = 138, + ERR_REGULATOR = 139, + ERR_STORAGE_VOLUME = 140, + ERR_OUTFALL = 141, + ERR_REGULATOR_SHAPE = 143, + ERR_NO_OUTLETS = 145, + +// ... RDII Errors + ERR_UNITHYD_TIMES = 151, + ERR_UNITHYD_RATIOS = 153, + ERR_RDII_AREA = 155, + +// ... Rain Gage Errors + ERR_RAIN_FILE_CONFLICT = 156, + ERR_RAIN_GAGE_FORMAT = 157, + ERR_RAIN_GAGE_TSERIES = 158, + ERR_RAIN_GAGE_INTERVAL = 159, + +// ... Treatment Function Error + ERR_CYCLIC_TREATMENT = 161, + +// ... Curve/Time Series Errors + ERR_CURVE_SEQUENCE = 171, + ERR_TIMESERIES_SEQUENCE = 173, + +// ... Snowmelt Errors + ERR_SNOWMELT_PARAMS = 181, + ERR_SNOWPACK_PARAMS = 182, + +// ... LID Errors + ERR_LID_TYPE = 183, + ERR_LID_LAYER = 184, + ERR_LID_PARAMS = 185, + ERR_LID_AREAS = 187, + ERR_LID_CAPTURE_AREA = 188, + +// ... Simulation Date/Time Errors + ERR_START_DATE = 191, + ERR_REPORT_DATE = 193, + ERR_REPORT_STEP = 195, + +// ... Input Parser Errors + ERR_INPUT = 200, + ERR_LINE_LENGTH = 201, + ERR_ITEMS = 203, + ERR_KEYWORD = 205, + ERR_DUP_NAME = 207, + ERR_NAME = 209, + ERR_NUMBER = 211, + ERR_DATETIME = 213, + ERR_RULE = 217, + ERR_TRANSECT_UNKNOWN = 219, + ERR_TRANSECT_SEQUENCE = 221, + ERR_TRANSECT_TOO_FEW = 223, + ERR_TRANSECT_TOO_MANY = 225, + ERR_TRANSECT_MANNING = 227, + ERR_TRANSECT_OVERBANK = 229, + ERR_TRANSECT_NO_DEPTH = 231, + ERR_MATH_EXPR = 233, + ERR_INFIL_PARAMS = 235, + +// ... File Name/Opening Errors + ERR_FILE_NAME = 301, + ERR_INP_FILE = 303, + ERR_RPT_FILE = 305, + ERR_OUT_FILE = 307, + ERR_OUT_SIZE = 308, + ERR_OUT_WRITE = 309, + ERR_OUT_READ = 311, + +// ... Rain File Errors + ERR_RAIN_FILE_SCRATCH = 313, + ERR_RAIN_FILE_OPEN = 315, + ERR_RAIN_FILE_DATA = 317, + ERR_RAIN_FILE_SEQUENCE = 318, + ERR_RAIN_FILE_FORMAT = 319, + ERR_RAIN_IFACE_FORMAT = 320, + ERR_RAIN_FILE_GAGE = 321, + +// ... Runoff File Errors + ERR_RUNOFF_FILE_OPEN = 323, + ERR_RUNOFF_FILE_FORMAT = 325, + ERR_RUNOFF_FILE_END = 327, + ERR_RUNOFF_FILE_READ = 329, + +// ... Hotstart File Errors + ERR_HOTSTART_FILE_OPEN = 331, + ERR_HOTSTART_FILE_FORMAT = 333, + ERR_HOTSTART_FILE_READ = 335, + +// ... Climate File Errors + ERR_NO_CLIMATE_FILE = 336, + ERR_CLIMATE_FILE_OPEN = 337, + ERR_CLIMATE_FILE_READ = 338, + ERR_CLIMATE_END_OF_FILE = 339, + +// ... RDII File Errors + ERR_RDII_FILE_SCRATCH = 341, + ERR_RDII_FILE_OPEN = 343, + ERR_RDII_FILE_FORMAT = 345, + +// ... Routing File Errors + ERR_ROUTING_FILE_OPEN = 351, + ERR_ROUTING_FILE_FORMAT = 353, + ERR_ROUTING_FILE_NOMATCH = 355, + ERR_ROUTING_FILE_NAMES = 357, + +// ... Time Series File Errors + ERR_TABLE_FILE_OPEN = 361, + ERR_TABLE_FILE_READ = 363, + +// ... Runtime Errors + ERR_SYSTEM = 500, + +// ... API Errors + ERR_API_NOT_OPEN = 501, + ERR_API_NOT_STARTED = 502, + ERR_API_NOT_ENDED = 503, + ERR_API_OBJECT_TYPE = 504, + ERR_API_OBJECT_INDEX = 505, + ERR_API_OBJECT_NAME = 506, + ERR_API_PROPERTY_TYPE = 507, + ERR_API_PROPERTY_VALUE = 508, + ERR_API_TIME_PERIOD = 509, + +// ... Additional Errors + MAXERRMSG = 1000 +}; + +char* error_getMsg(int i, char* msg); +int error_setInpError(int errcode, char* s); + +#endif //ERROR_H diff --git a/src/error.txt b/src/error.txt new file mode 100644 index 000000000..801d96b6a --- /dev/null +++ b/src/error.txt @@ -0,0 +1,135 @@ +// SWMM 5.2 Error Messages + +ERR(101,"\n ERROR 101: memory allocation error.") +ERR(103,"\n ERROR 103: cannot solve KW equations for Link %s.") +ERR(105,"\n ERROR 105: cannot open ODE solver.") +ERR(107,"\n ERROR 107: cannot compute a valid time step.") + +ERR(108,"\n ERROR 108: ambiguous outlet ID name for Subcatchment %s.") +ERR(109,"\n ERROR 109: invalid parameter values for Aquifer %s.") +ERR(110,"\n ERROR 110: ground elevation is below water table for Subcatchment %s.") + +ERR(111,"\n ERROR 111: invalid length for Conduit %s.") +ERR(112,"\n ERROR 112: elevation drop exceeds length for Conduit %s.") +ERR(113,"\n ERROR 113: invalid roughness for Conduit %s.") +ERR(114,"\n ERROR 114: invalid number of barrels for Conduit %s.") +ERR(115,"\n ERROR 115: adverse slope for Conduit %s.") +ERR(117,"\n ERROR 117: no cross section defined for Link %s.") +ERR(119,"\n ERROR 119: invalid cross section for Link %s.") +ERR(121,"\n ERROR 121: missing or invalid pump curve assigned to Pump %s.") +ERR(122,"\n ERROR 122: startup depth not higher than shutoff depth for Pump %s.") + +ERR(131,"\n ERROR 131: the following links form cyclic loops in the drainage system:") +ERR(133,"\n ERROR 133: Node %s has more than one outlet link.") +ERR(134,"\n ERROR 134: Node %s has illegal DUMMY link connections.") + +ERR(135,"\n ERROR 135: Divider %s does not have two outlet links.") +ERR(136,"\n ERROR 136: Divider %s has invalid diversion link.") +ERR(137,"\n ERROR 137: Weir Divider %s has invalid parameters.") +ERR(138,"\n ERROR 138: Node %s has initial depth greater than maximum depth.") +ERR(139,"\n ERROR 139: Regulator %s is the outlet of a non-storage node.") +ERR(140,"\n ERROR 140: Storage node %s has negative volume at full depth.") +ERR(141,"\n ERROR 141: Outfall %s has more than 1 inlet link or an outlet link.") +ERR(143,"\n ERROR 143: Regulator %s has invalid cross-section shape.") +ERR(145,"\n ERROR 145: Drainage system has no acceptable outlet nodes.") + +ERR(151,"\n ERROR 151: a Unit Hydrograph in set %s has invalid time base.") +ERR(153,"\n ERROR 153: a Unit Hydrograph in set %s has invalid response ratios.") +ERR(155,"\n ERROR 155: invalid sewer area for RDII at node %s.") + +ERR(156,"\n ERROR 156: ambiguous station ID for Rain Gage %s.") +ERR(157,"\n ERROR 157: inconsistent rainfall format for Rain Gage %s.") +ERR(158,"\n ERROR 158: time series for Rain Gage %s is also used by another object.") +ERR(159,"\n ERROR 159: recording interval greater than time series interval for Rain Gage %s.") + +ERR(161,"\n ERROR 161: cyclic dependency in treatment functions at node %s.") + +ERR(171,"\n ERROR 171: Curve %s has invalid or out of sequence data.") +ERR(173,"\n ERROR 173: Time Series %s has its data out of sequence.") + +ERR(181,"\n ERROR 181: invalid Snow Melt Climatology parameters.") +ERR(182,"\n ERROR 182: invalid parameters for Snow Pack %s.") + +ERR(183,"\n ERROR 183: no type specified for LID %s.") +ERR(184,"\n ERROR 184: missing layer for LID %s.") +ERR(185,"\n ERROR 185: invalid parameter value for LID %s.") +ERR(187,"\n ERROR 187: LID area exceeds total area for Subcatchment %s.") +ERR(188,"\n ERROR 188: LID capture area exceeds total impervious area for Subcatchment %s.") + +ERR(191,"\n ERROR 191: simulation start date comes after ending date.") +ERR(193,"\n ERROR 193: report start date comes after ending date.") +ERR(195,"\n ERROR 195: reporting time step or duration is less than routing time step.") + +ERR(200,"\n ERROR 200: one or more errors in input file.") +ERR(201,"\n ERROR 201: too many characters in input line ") +ERR(203,"\n ERROR 203: too few items ") +ERR(205,"\n ERROR 205: invalid keyword %s ") +ERR(207,"\n ERROR 207: duplicate ID name %s ") +ERR(209,"\n ERROR 209: undefined object %s ") +ERR(211,"\n ERROR 211: invalid number %s ") +ERR(213,"\n ERROR 213: invalid date/time %s ") +ERR(217,"\n ERROR 217: control rule clause invalid or out of sequence ") +ERR(219,"\n ERROR 219: data provided for unidentified transect ") +ERR(221,"\n ERROR 221: transect station out of sequence ") +ERR(223,"\n ERROR 223: Transect %s has too few stations.") +ERR(225,"\n ERROR 225: Transect %s has too many stations.") +ERR(227,"\n ERROR 227: Transect %s has no Manning's N.") +ERR(229,"\n ERROR 229: Transect %s has invalid overbank locations.") +ERR(231,"\n ERROR 231: Transect %s has no depth.") +ERR(233,"\n ERROR 233: invalid math expression ") +ERR(235,"\n ERROR 235: invalid infiltration parameters.") + +ERR(301,"\n ERROR 301: files share same names.") +ERR(303,"\n ERROR 303: cannot open input file.") +ERR(305,"\n ERROR 305: cannot open report file.") +ERR(307,"\n ERROR 307: cannot open binary results file.") +ERR(308,"\n ERROR 308: amount of output produced will exceed maximum file size.") + +ERR(309,"\n ERROR 309: error writing to binary results file.") +ERR(311,"\n ERROR 311: error reading from binary results file.") + +ERR(313,"\n ERROR 313: cannot open scratch rainfall interface file.") +ERR(315,"\n ERROR 315: cannot open rainfall interface file %s.") +ERR(317,"\n ERROR 317: cannot open rainfall data file %s.") +ERR(318,"\n ERROR 318: the following line is out of sequence in rainfall data file %s.") +ERR(319,"\n ERROR 319: unknown format for rainfall data file %s.") +ERR(320,"\n ERROR 320: invalid format for rainfall interface file.") +ERR(321,"\n ERROR 321: no data in rainfall interface file for gage %s.") + +ERR(323,"\n ERROR 323: cannot open runoff interface file %s.") +ERR(325,"\n ERROR 325: incompatible data found in runoff interface file.") +ERR(327,"\n ERROR 327: attempting to read beyond end of runoff interface file.") +ERR(329,"\n ERROR 329: error in reading from runoff interface file.") + +ERR(331,"\n ERROR 331: cannot open hot start interface file %s.") +ERR(333,"\n ERROR 333: incompatible data found in hot start interface file.") +ERR(335,"\n ERROR 335: error in reading from hot start interface file.") + +ERR(336,"\n ERROR 336: no climate file specified for evaporation and/or wind speed.") +ERR(337,"\n ERROR 337: cannot open climate file %s.") +ERR(338,"\n ERROR 338: error in reading from climate file %s.") +ERR(339,"\n ERROR 339: attempt to read beyond end of climate file %s.") + +ERR(341,"\n ERROR 341: cannot open scratch RDII interface file.") +ERR(343,"\n ERROR 343: cannot open RDII interface file %s.") +ERR(345,"\n ERROR 345: invalid format for RDII interface file.") + +ERR(351,"\n ERROR 351: cannot open routing interface file %s.") +ERR(353,"\n ERROR 353: invalid format for routing interface file %s.") +ERR(355,"\n ERROR 355: mis-matched names in routing interface file %s.") +ERR(357,"\n ERROR 357: inflows and outflows interface files have same name.") + +ERR(361,"\n ERROR 361: could not open external file used for Time Series %s.") +ERR(363,"\n ERROR 363: invalid data in external file used for Time Series %s.") + +// API Error Keys +ERR(500,"\n ERROR 500: System exception thrown.") +ERR(501,"\n API Error 501: project not opened.") +ERR(502,"\n API Error 502: simulation not started.") +ERR(503,"\n API Error 503: simulation not ended.") +ERR(504,"\n API Error 504: invalid object type.") +ERR(505,"\n API Error 505: invalid object index.") +ERR(506,"\n API Error 506: invalid object name.") +ERR(507,"\n API Error 507: invalid property type.") +ERR(508,"\n API Error 508: invalid property value.") +ERR(509,"\n API Error 509: invalid time period.") diff --git a/src/exfil.c b/src/exfil.c new file mode 100644 index 000000000..f1cb2adf6 --- /dev/null +++ b/src/exfil.c @@ -0,0 +1,252 @@ +//----------------------------------------------------------------------------- +// exfil.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Storage unit exfiltration functions. +// +// Update History +// ============== +// Build 5.1.008: +// - Monthly conductivity adjustment applied to exfiltration rate. +// Build 5.1.010: +// - New modified Green-Ampt infiltration option used. +// Build 5.1.011: +// - Fixed units conversion error for storage units with surface area curves. +// Build 5.2.0: +// - Support added for analytical storage shapes. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" +#include "infil.h" +#include "exfil.h" + +static int createStorageExfil(int k, double x[]); + +//============================================================================= + +int exfil_readStorageParams(int k, char* tok[], int ntoks, int n) +// +// Input: k = storage unit index +// tok[] = array of string tokens +// ntoks = number of tokens +// n = last token processed +// Output: returns an error code +// Purpose: reads a storage unit's exfiltration parameters from a +// tokenized line of input. +// +{ + int i; + double x[3]; //suction head, Ksat, IMDmax + + // --- read Ksat if it's the only remaining token + if ( ntoks == n+1 ) + { + if ( ! getDouble(tok[n], &x[1]) ) + return error_setInpError(ERR_NUMBER, tok[n]); + x[0] = 0.0; + x[2] = 0.0; + } + + // --- otherwise read Green-Ampt infiltration parameters from input tokens + else if ( ntoks < n + 3 ) return error_setInpError(ERR_ITEMS, ""); + else for (i = 0; i < 3; i++) + { + if ( ! getDouble(tok[n+i], &x[i]) ) + return error_setInpError(ERR_NUMBER, tok[n+i]); + } + + // --- no exfiltration if Ksat is 0 + if ( x[1] == 0.0 ) return 0; + + // --- create an exfiltration object + return createStorageExfil(k, x); +} + +//============================================================================= + +void exfil_initState(int k) +// +// Input: k = storage unit index +// Output: none +// Purpose: initializes the state of a storage unit's exfiltration object. +// +{ + int i; + double a, alast, d; + TTable* aCurve; + TExfil* exfil = Storage[k].exfil; + + // --- initialize exfiltration object + if ( exfil != NULL ) + { + // --- initialize the Green-Ampt infil. parameters + grnampt_initState(exfil->btmExfil); + grnampt_initState(exfil->bankExfil); + + switch (Storage[k].shape) + { + // --- shape given by a Storage Curve + case TABULAR: + i = Storage[k].aCurve; + exfil->btmArea = 0.0; + exfil->bankMinDepth = 0.0; + exfil->bankMaxDepth = 0.0; + exfil->bankMaxArea = 0.0; + if ( i >= 0 ) + { + // --- get bottom area + aCurve = &Curve[i]; + Storage[k].exfil->btmArea = table_lookupEx(aCurve, 0.0); + + // --- find min/max bank depths and max. bank area + table_getFirstEntry(aCurve, &d, &a); + alast = a; + while ( table_getNextEntry(aCurve, &d, &a) ) + { + if ( a < alast ) break; + else if ( a > alast ) + { + exfil->bankMaxArea = a; + exfil->bankMaxDepth = d; + } + else if ( exfil->bankMaxArea == 0.0 ) + exfil->bankMinDepth = d; + else break; + alast = a; + } + + // --- convert from user units to internal units + exfil->btmArea /= UCF(LENGTH) * UCF(LENGTH); + exfil->bankMaxArea /= UCF(LENGTH) * UCF(LENGTH); + exfil->bankMinDepth /= UCF(LENGTH); + exfil->bankMaxDepth /= UCF(LENGTH); + } + break; + + // --- functional storage shape curve + case FUNCTIONAL: + exfil->btmArea = Storage[k].a0; + if ( Storage[k].a2 == 0.0 ) + exfil->btmArea +=Storage[k].a1; + exfil->bankMinDepth = 0.0; + exfil->bankMaxDepth = BIG; + exfil->bankMaxArea = BIG; + break; + + // --- cylindrical, conical & prismatic shapes + case CYLINDRICAL: + case CONICAL: + case PYRAMIDAL: + exfil->btmArea = Storage[k].a0; + exfil->bankMinDepth = 0.0; + exfil->bankMaxDepth = BIG; + exfil->bankMaxArea = BIG; + break; + } + } +} + +//============================================================================= + +double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area) +// +// Input: exfil = ptr. to a storage exfiltration object +// tStep = time step (sec) +// depth = water depth (ft) +// area = surface area (ft2) +// Output: returns exfiltration rate out of storage unit (cfs) +// Purpose: computes rate of water exfiltrated from a storage node into +// the soil beneath it. +// +{ + double exfilRate = 0.0; + + // --- find infiltration through bottom of unit + if ( exfil->btmExfil->IMDmax == 0.0 ) + { + exfilRate = exfil->btmExfil->Ks * Adjust.hydconFactor; + } + else exfilRate = grnampt_getInfil(exfil->btmExfil, tStep, 0.0, depth, + MOD_GREEN_AMPT); + exfilRate *= exfil->btmArea; + + // --- find infiltration through sloped banks + if ( depth > exfil->bankMinDepth ) + { + // --- get area of banks + area = MIN(area, exfil->bankMaxArea) - exfil->btmArea; + if ( area > 0.0 ) + { + // --- if infil. rate not a function of depth + if ( exfil->btmExfil->IMDmax == 0.0 ) + { + exfilRate += area * exfil->btmExfil->Ks * Adjust.hydconFactor; + } + + // --- infil. rate depends on depth above bank + else + { + // --- case where water depth is above the point where the + // storage curve no longer has increasing area with depth + if ( depth > exfil->bankMaxDepth ) + { + depth = depth - exfil->bankMaxDepth + + (exfil->bankMaxDepth - exfil->bankMinDepth) / 2.0; + } + + // --- case where water depth is below top of bank + else depth = (depth - exfil->bankMinDepth) / 2.0; + + // --- use Green-Ampt function for bank infiltration + exfilRate += area * grnampt_getInfil(exfil->bankExfil, + tStep, 0.0, depth, MOD_GREEN_AMPT); + } + } + } + return exfilRate; +} + +//============================================================================= + +int createStorageExfil(int k, double x[]) +// +// Input: k = index of storage unit node +// x = array of Green-Ampt infiltration parameters +// Output: returns an error code. +// Purpose: creates an exfiltration object for a storage node. +// +// Note: the exfiltration object is freed in project.c. +// +{ + TExfil* exfil; + + // --- create an exfiltration object for the storage node + exfil = Storage[k].exfil; + if ( exfil == NULL ) + { + exfil = (TExfil *) malloc(sizeof(TExfil)); + if ( exfil == NULL ) return error_setInpError(ERR_MEMORY, ""); + Storage[k].exfil = exfil; + + // --- create Green-Ampt infiltration objects for the bottom & banks + exfil->btmExfil = NULL; + exfil->bankExfil = NULL; + exfil->btmExfil = (TGrnAmpt *) malloc(sizeof(TGrnAmpt)); + if ( exfil->btmExfil == NULL ) return error_setInpError(ERR_MEMORY, ""); + exfil->bankExfil = (TGrnAmpt *) malloc(sizeof(TGrnAmpt)); + if ( exfil->bankExfil == NULL ) return error_setInpError(ERR_MEMORY, ""); + } + + // --- initialize the Green-Ampt parameters + if ( !grnampt_setParams(exfil->btmExfil, x) ) + return error_setInpError(ERR_NUMBER, ""); + grnampt_setParams(exfil->bankExfil, x); + return 0; +} diff --git a/src/exfil.h b/src/exfil.h new file mode 100644 index 000000000..8da4da302 --- /dev/null +++ b/src/exfil.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// exfil.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Public interface for exfiltration functions. +//----------------------------------------------------------------------------- + +#ifndef EXFIL_H +#define EXFIL_H + +//---------------------------- +// EXFILTRATION OBJECT +//---------------------------- +typedef struct +{ + TGrnAmpt* btmExfil; + TGrnAmpt* bankExfil; + double btmArea; + double bankMinDepth; + double bankMaxDepth; + double bankMaxArea; +} TExfil; + +//----------------------------------------------------------------------------- +// Exfiltration Methods +//----------------------------------------------------------------------------- +int exfil_readStorageParams(int k, char* tok[], int ntoks, int n); +void exfil_initState(int k); +double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area); + +#endif diff --git a/src/findroot.c b/src/findroot.c new file mode 100644 index 000000000..d6056f658 --- /dev/null +++ b/src/findroot.c @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------------- +// findroot.c +// +// Finds solution of func(x) = 0 using either the Newton-Raphson +// method or Ridder's Method. +// Based on code from Numerical Recipes in C (Cambridge University +// Press, 1992). +// +// Date: 11/19/13 +// Author: L. Rossman +//----------------------------------------------------------------------------- + +#include +#include "findroot.h" + +#define SIGN(a,b) ((b) >= 0.0 ? fabs(a) : -fabs(a)) +#define MAXIT 60 + + +int findroot_Newton(double x1, double x2, double* rts, double xacc, + void (*func) (double x, double* f, double* df, void* p), + void* p) +// +// Using a combination of Newton-Raphson and bisection, find the root of a +// function func bracketed between x1 and x2. The root, returned in rts, +// will be refined until its accuracy is known within +/-xacc. func is a +// user-supplied routine, that returns both the function value and the first +// derivative of the function. p is a pointer to any auxilary data structure +// that func may require. It can be NULL if not needed. The function returns +// the number of function evaluations used or 0 if the maximum allowed +// iterations were exceeded. +// +// NOTES: +// 1. The calling program must insure that the signs of func(x1) and func(x2) +// are not the same, otherwise x1 and x2 do not bracket the root. +// 2. If func(x1) > func(x2) then the order of x1 and x2 should be +// switched in the call to Newton. +// +{ + int j, n = 0; + double df, dx, dxold, f, x; + double temp, xhi, xlo; + + // Initialize the "stepsize before last" and the last step. + x = *rts; + xlo = x1; + xhi = x2; + dxold = fabs(x2-x1); + dx = dxold; + func(x, &f, &df, p); + n++; + + // Loop over allowed iterations. + for (j=1; j<=MAXIT; j++) + { + // Bisect if Newton out of range or not decreasing fast enough. + if ( ( ( (x-xhi)*df-f)*((x-xlo)*df-f) >= 0.0 + || (fabs(2.0*f) > fabs(dxold*df) ) ) ) + { + dxold = dx; + dx = 0.5*(xhi-xlo); + x = xlo + dx; + if ( xlo == x ) break; + } + + // Newton step acceptable. Take it. + else + { + dxold = dx; + dx = f/df; + temp = x; + x -= dx; + if ( temp == x ) break; + } + + // Convergence criterion. + if ( fabs(dx) < xacc ) break; + + // Evaluate function. Maintain bracket on the root. + func(x, &f, &df, p); + n++; + if ( f < 0.0 ) xlo = x; + else xhi = x; + } + *rts = x; + if ( n <= MAXIT) return n; + else return 0; +}; + + +double findroot_Ridder(double x1, double x2, double xacc, + double (*func)(double, void* p), void* p) +{ + int j; + double ans, fhi, flo, fm, fnew, s, xhi, xlo, xm, xnew; + + flo = func(x1, p); + fhi = func(x2, p); + if ( flo == 0.0 ) return x1; + if ( fhi == 0.0 ) return x2; + ans = 0.5*(x1+x2); + if ( (flo > 0.0 && fhi < 0.0) || (flo < 0.0 && fhi > 0.0) ) + { + xlo = x1; + xhi = x2; + for (j=1; j<=MAXIT; j++) { + xm = 0.5*(xlo + xhi); + fm = func(xm, p); + s = sqrt( fm*fm - flo*fhi ); + if (s == 0.0) return ans; + xnew = xm + (xm-xlo)*( (flo >= fhi ? 1.0 : -1.0)*fm/s ); + if ( fabs(xnew - ans) <= xacc ) break; + ans = xnew; + fnew = func(ans, p); + if ( SIGN(fm, fnew) != fm) + { + xlo = xm; + flo = fm; + xhi = ans; + fhi = fnew; + } + else if ( SIGN(flo, fnew) != flo ) + { + xhi = ans; + fhi = fnew; + } + else if ( SIGN(fhi, fnew) != fhi) + { + xlo = ans; + flo = fnew; + } + else return ans; + if ( fabs(xhi - xlo) <= xacc ) return ans; + } + return ans; + } + return -1.e20; +} diff --git a/src/findroot.h b/src/findroot.h new file mode 100644 index 000000000..3b54c6456 --- /dev/null +++ b/src/findroot.h @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// findroot.h +// +// Header file for root finding method contained in findroot.c +// +// Last modified on 11/19/13. +//----------------------------------------------------------------------------- + +#ifndef FINDROOT_H +#define FINDROOT_H + +int findroot_Newton(double x1, double x2, double* rts, double xacc, + void (*func) (double x, double* f, double* df, void* p), + void* p); +double findroot_Ridder(double x1, double x2, double xacc, + double (*func)(double, void* p), void* p); + +#endif //FINDROOT_H diff --git a/src/flowrout.c b/src/flowrout.c new file mode 100644 index 000000000..9544dd459 --- /dev/null +++ b/src/flowrout.c @@ -0,0 +1,797 @@ +//----------------------------------------------------------------------------- +// flowrout.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// M. Tryby (EPA) +// +// Flow routing functions. +// +// Update History +// ============== +// Build 5.1.007: +// - updateStorageState() modified in response to node outflow being +// initialized with current evap & seepage losses in routing_execute(). +// Build 5.1.008: +// - Determination of node crown elevations moved to dynwave.c. +// - Support added for new way of recording conduit's fullness state. +// Build 5.1.012: +// - Overflow computed in updateStorageState() must be non-negative. +// - Terminal storage nodes now updated corectly. +// Build 5.1.014: +// - Arguments to function link_getLossRate changed. +// Build 5.2.0: +// - Correction made to updating state of terminal storage nodes. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double OMEGA = 0.55; // under-relaxation parameter +static const int MAXITER = 10; // max. iterations for storage updating +static const double STOPTOL = 0.005; // storage updating stopping tolerance + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// flowrout_init (called by routing_open) +// flowrout_close (called by routing_close) +// flowrout_getRoutingStep (called routing_getRoutingStep) +// flowrout_execute (called routing_execute) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static void initLinkDepths(void); +static void initNodeDepths(void); +static void initNodes(void); +static void initLinks(int routingModel); +static void validateTreeLayout(void); +static void validateGeneralLayout(void); +static void updateStorageState(int i, int j, int links[], double dt); +static double getStorageOutflow(int node, int j, int links[], double dt); +static double getLinkInflow(int link, double dt); +static void setNewNodeState(int node, double dt); +static void setNewLinkState(int link); +static void updateNodeDepth(int node, double y); +static int steadyflow_execute(int link, double* qin, double* qout, + double tStep); + + +//============================================================================= + +void flowrout_init(int routingModel) +// +// Input: routingModel = routing model code +// Output: none +// Purpose: initializes flow routing system. +// +{ + // --- initialize for dynamic wave routing + if ( routingModel == DW ) + { + // --- check for valid conveyance network layout + validateGeneralLayout(); + dynwave_init(); + + // --- initialize node & link depths if not using a hotstart file + if ( Fhotstart1.mode == NO_FILE ) + { + initNodeDepths(); + initLinkDepths(); + } + } + + // --- validate network layout for kinematic wave routing + else validateTreeLayout(); + + // --- initialize node & link volumes + initNodes(); + initLinks(routingModel); +} + +//============================================================================= + +void flowrout_close(int routingModel) +// +// Input: routingModel = routing method code +// Output: none +// Purpose: closes down routing method used. +// +{ + if ( routingModel == DW ) dynwave_close(); +} + +//============================================================================= + +double flowrout_getRoutingStep(int routingModel, double fixedStep) +// +// Input: routingModel = type of routing method used +// fixedStep = user-assigned max. routing step (sec) +// Output: returns adjusted value of routing time step (sec) +// Purpose: finds variable time step for dynamic wave routing. +// +{ + if ( routingModel == DW ) + { + return dynwave_getRoutingStep(fixedStep); + } + return fixedStep; +} + +//============================================================================= + +int flowrout_execute(int links[], int routingModel, double tStep) +// +// Input: links = array of link indexes in topo-sorted order (per routing model) +// routingModel = type of routing method used +// tStep = routing time step (sec) +// Output: returns number of computational steps taken +// Purpose: routes flow through conveyance network over current time step. +// +{ + int i, j; + int n1; // upstream node of link + double qin; // link inflow (cfs) + double qout; // link outflow (cfs) + double steps; // computational step count + + // --- set overflows to drain any ponded water + if ( ErrorCode ) return 0; + for (j = 0; j < Nobjects[NODE]; j++) + { + Node[j].updated = FALSE; + Node[j].overflow = 0.0; + if ( Node[j].type != STORAGE + && Node[j].newVolume > Node[j].fullVolume ) + { + Node[j].overflow = (Node[j].newVolume - Node[j].fullVolume)/tStep; + } + } + + // --- execute dynamic wave routing if called for + if ( routingModel == DW ) + { + return dynwave_execute(tStep); + } + + // --- otherwise examine each link, moving from upstream to downstream + steps = 0.0; + for (i = 0; i < Nobjects[LINK]; i++) + { + // --- see if upstream node is a storage unit whose state needs updating + j = links[i]; + n1 = Link[j].node1; + if ( Node[n1].type == STORAGE ) updateStorageState(n1, i, links, tStep); + + // --- retrieve inflow at upstream end of link + qin = getLinkInflow(j, tStep); + + // --- route flow through link + if ( routingModel == SF ) + steps += steadyflow_execute(j, &qin, &qout, tStep); + else + steps += kinwave_execute(j, &qin, &qout, tStep); + Link[j].newFlow = qout; + + // adjust outflow at upstream node and inflow at downstream node + Node[ Link[j].node1 ].outflow += qin; + Node[ Link[j].node2 ].inflow += qout; + } + if ( Nobjects[LINK] > 0 ) steps /= Nobjects[LINK]; + + // --- update state of each non-updated node and link + for ( j=0; j 2 ) + { + report_writeErrorMsg(ERR_DIVIDER, Node[j].ID); + } + break; + + // --- outfalls cannot have any outlet links + case OUTFALL: + if ( Node[j].degree > 0 ) + { + report_writeErrorMsg(ERR_OUTFALL, Node[j].ID); + } + break; + + // --- storage nodes can have multiple outlets + case STORAGE: break; + + // --- all other nodes allowed only one outlet link + default: + if ( Node[j].degree > 1 ) + { + report_writeErrorMsg(ERR_MULTI_OUTLET, Node[j].ID); + } + } + } + + // --- check links + for (j=0; j 1 ) + { + report_writeErrorMsg(ERR_DUMMY_LINK, Node[i].ID); + } + } + } + + // --- check each node to see if it qualifies as an outlet node + // (meaning that degree = 0) + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + // --- if node is of type Outfall, check that it has only 1 + // connecting link (which can either be an outflow or inflow link) + if ( Node[i].type == OUTFALL ) + { + if ( Node[i].degree + (int)Node[i].inflow > 1 ) + { + report_writeErrorMsg(ERR_OUTFALL, Node[i].ID); + } + else outletCount++; + } + } + if ( outletCount == 0 ) report_writeErrorMsg(ERR_NO_OUTLETS, ""); + + // --- reset node inflows back to zero + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + if ( Node[i].inflow == 0.0 ) Node[i].degree = -Node[i].degree; + Node[i].inflow = 0.0; + } +} + +//============================================================================= + +void initNodeDepths(void) +// +// Input: none +// Output: none +// Purpose: sets initial depth at nodes for Dynamic Wave flow routing. +// +{ + int i; // link or node index + int n; // node index + double y; // node water depth (ft) + + // --- use Node[].inflow as a temporary accumulator for depth in + // connecting links and Node[].outflow as a temporary counter + // for the number of connecting links + for (i = 0; i < Nobjects[NODE]; i++) + { + Node[i].inflow = 0.0; + Node[i].outflow = 0.0; + } + + // --- total up flow depths in all connecting links into nodes + for (i = 0; i < Nobjects[LINK]; i++) + { + if ( Link[i].newDepth > FUDGE ) y = Link[i].newDepth + Link[i].offset1; + else y = 0.0; + n = Link[i].node1; + Node[n].inflow += y; + Node[n].outflow += 1.0; + n = Link[i].node2; + Node[n].inflow += y; + Node[n].outflow += 1.0; + } + + // --- if no user-supplied depth then set initial depth at non-storage/ + // non-outfall nodes to average of depths in connecting links + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + if ( Node[i].type == OUTFALL ) continue; + if ( Node[i].type == STORAGE ) continue; + if ( Node[i].initDepth > 0.0 ) continue; + if ( Node[i].outflow > 0.0 ) + { + Node[i].newDepth = Node[i].inflow / Node[i].outflow; + } + } + + // --- compute initial depths at all outfall nodes + for ( i = 0; i < Nobjects[LINK]; i++ ) link_setOutfallDepth(i); +} + +//============================================================================= + +void initLinkDepths() +// +// Input: none +// Output: none +// Purpose: sets initial flow depths in conduits under Dyn. Wave routing. +// +{ + int i; // link index + double y, y1, y2; // depths (ft) + + // --- examine each link + for (i = 0; i < Nobjects[LINK]; i++) + { + // --- examine each conduit + if ( Link[i].type == CONDUIT ) + { + // --- skip conduits with user-assigned initial flows + // (their depths have already been set to normal depth) + if ( Link[i].q0 != 0.0 ) continue; + + // --- set depth to average of depths at end nodes + y1 = Node[Link[i].node1].newDepth - Link[i].offset1; + y1 = MAX(y1, 0.0); + y1 = MIN(y1, Link[i].xsect.yFull); + y2 = Node[Link[i].node2].newDepth - Link[i].offset2; + y2 = MAX(y2, 0.0); + y2 = MIN(y2, Link[i].xsect.yFull); + y = 0.5 * (y1 + y2); + y = MAX(y, FUDGE); + Link[i].newDepth = y; + } + } +} + +//============================================================================= + +void initNodes() +// +// Input: none +// Output: none +// Purpose: sets initial inflow/outflow and volume for each node +// +{ + int i; + + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + // --- initialize node inflow and outflow + Node[i].inflow = Node[i].newLatFlow; + Node[i].outflow = 0.0; + + // --- initialize node volume + Node[i].newVolume = 0.0; + if ( AllowPonding && + Node[i].pondedArea > 0.0 && + Node[i].newDepth > Node[i].fullDepth ) + { + Node[i].newVolume = Node[i].fullVolume + + (Node[i].newDepth - Node[i].fullDepth) * + Node[i].pondedArea; + } + else Node[i].newVolume = node_getVolume(i, Node[i].newDepth); + } + + // --- update nodal inflow/outflow at ends of each link + // (needed for Steady Flow & Kin. Wave routing) + for ( i = 0; i < Nobjects[LINK]; i++ ) + { + if ( Link[i].newFlow >= 0.0 ) + { + Node[Link[i].node1].outflow += Link[i].newFlow; + Node[Link[i].node2].inflow += Link[i].newFlow; + } + else + { + Node[Link[i].node1].inflow -= Link[i].newFlow; + Node[Link[i].node2].outflow -= Link[i].newFlow; + } + } +} + +//============================================================================= + +void initLinks(int routingModel) +// +// Input: none +// Output: none +// Purpose: sets initial upstream/downstream conditions in links. +// +{ + int i; // link index + int k; // conduit or pump index + + // --- examine each link + for ( i = 0; i < Nobjects[LINK]; i++ ) + { + if ( routingModel == SF) Link[i].newFlow = 0.0; + + // --- otherwise if link is a conduit + else if ( Link[i].type == CONDUIT ) + { + // --- assign initial flow to both ends of conduit + k = Link[i].subIndex; + Conduit[k].q1 = Link[i].newFlow / Conduit[k].barrels; + Conduit[k].q2 = Conduit[k].q1; + + // --- find areas based on initial flow depth + Conduit[k].a1 = xsect_getAofY(&Link[i].xsect, Link[i].newDepth); + Conduit[k].a2 = Conduit[k].a1; + + // --- compute initial volume from area + { + Link[i].newVolume = Conduit[k].a1 * link_getLength(i) * + Conduit[k].barrels; + } + Link[i].oldVolume = Link[i].newVolume; + } + } +} + +//============================================================================= + +double getLinkInflow(int j, double dt) +// +// Input: j = link index +// dt = routing time step (sec) +// Output: returns link inflow (cfs) +// Purpose: finds flow into upstream end of link at current time step under +// Steady or Kin. Wave routing. +// +{ + int n1 = Link[j].node1; + double q; + if ( Link[j].type == CONDUIT || + Link[j].type == PUMP || + Node[n1].type == STORAGE ) q = link_getInflow(j); + else q = 0.0; + return node_getMaxOutflow(n1, q, dt); +} + +//============================================================================= + +void updateStorageState(int i, int j, int links[], double dt) +// +// Input: i = index of storage node +// j = current position in links array +// links = array of topo-sorted link indexes +// dt = routing time step (sec) +// Output: none +// Purpose: updates depth and volume of a storage node using successive +// approximation with under-relaxation for Steady or Kin. Wave +// routing. +// +{ + int iter; // iteration counter + int stopped; // TRUE when iterations stop + double vFixed; // fixed terms of flow balance eqn. + double v2; // new volume estimate (ft3) + double d1; // initial value of storage depth (ft) + double d2; // updated value of storage depth (ft) + + // --- see if storage node needs updating + if ( Node[i].type != STORAGE ) return; + if ( Node[i].updated ) return; + + // --- compute terms of flow balance eqn. + // v2 = v1 + (inflow - outflow)*dt + // that do not depend on storage depth at end of time step + vFixed = Node[i].oldVolume + + 0.5 * (Node[i].oldNetInflow + Node[i].inflow - + Node[i].outflow) * dt; + d1 = Node[i].newDepth; + + // --- iterate finding outflow (which depends on depth) and subsequent + // new volume and depth until negligible depth change occurs + iter = 1; + stopped = FALSE; + while ( iter < MAXITER && !stopped ) + { + // --- find new volume from flow balance eqn. + v2 = vFixed - 0.5 * getStorageOutflow(i, j, links, dt) * dt; + + // --- limit volume to full volume if no ponding + // and compute overflow rate + v2 = MAX(0.0, v2); + Node[i].overflow = 0.0; + if ( v2 > Node[i].fullVolume ) + { + Node[i].overflow = (v2 - MAX(Node[i].oldVolume, + Node[i].fullVolume)) / dt; + if ( Node[i].overflow < FUDGE ) Node[i].overflow = 0.0; + if ( !AllowPonding || Node[i].pondedArea == 0.0 ) + v2 = Node[i].fullVolume; + } + + // --- update node's volume & depth + Node[i].newVolume = v2; + d2 = node_getDepth(i, v2); + Node[i].newDepth = d2; + + // --- use under-relaxation to estimate new depth value + // and stop if close enough to previous value + d2 = (1.0 - OMEGA)*d1 + OMEGA*d2; + if ( fabs(d2 - d1) <= STOPTOL ) stopped = TRUE; + + // --- update old depth with new value and continue to iterate + Node[i].newDepth = d2; + d1 = d2; + iter++; + } + + // --- mark node as being updated + Node[i].updated = TRUE; +} + +//============================================================================= + +double getStorageOutflow(int i, int j, int links[], double dt) +// +// Input: i = index of storage node +// j = current position in links array +// links = array of topo-sorted link indexes +// dt = routing time step (sec) +// Output: returns total outflow from storage node (cfs) +// Purpose: computes total flow released from a storage node. +// +{ + int k, m; + double outflow = 0.0; + + for (k = j; k < Nobjects[LINK]; k++) + { + m = links[k]; + if ( Link[m].node1 != i ) break; + outflow += getLinkInflow(m, dt); + } + return outflow; +} + +//============================================================================= + +void setNewNodeState(int j, double dt) +// +// Input: j = node index +// dt = time step (sec) +// Output: none +// Purpose: updates state of node after current time step +// for Steady Flow or Kinematic Wave flow routing. +// +{ + int canPond; // TRUE if ponding can occur at node + double newNetInflow; // inflow - outflow at node (cfs) + + // --- update terminal storage nodes + if ( Node[j].type == STORAGE ) + { + if ( Node[j].updated == FALSE ) + updateStorageState(j, Nobjects[LINK], NULL, dt); + } + + // --- update stored volume + newNetInflow = Node[j].inflow - Node[j].outflow - Node[j].losses; + Node[j].newVolume = Node[j].oldVolume + newNetInflow * dt; + if ( Node[j].newVolume < FUDGE ) Node[j].newVolume = 0.0; + + // --- determine any overflow lost from system + Node[j].overflow = 0.0; + canPond = (AllowPonding && Node[j].pondedArea > 0.0); + if ( Node[j].newVolume > Node[j].fullVolume ) + { + Node[j].overflow = (Node[j].newVolume - MAX(Node[j].oldVolume, + Node[j].fullVolume)) / dt; + if ( Node[j].overflow < FUDGE ) Node[j].overflow = 0.0; + if ( !canPond ) Node[j].newVolume = Node[j].fullVolume; + } + + // --- compute a depth from volume + // (depths at upstream nodes are subsequently adjusted in + // setNewLinkState to reflect depths in connected conduit) + Node[j].newDepth = node_getDepth(j, Node[j].newVolume); +} + +//============================================================================= + +void setNewLinkState(int j) +// +// Input: j = link index +// Output: none +// Purpose: updates state of link after current time step under +// Steady Flow or Kinematic Wave flow routing +// +{ + int k; + double a, y1, y2; + + Link[j].newDepth = 0.0; + Link[j].newVolume = 0.0; + + if ( Link[j].type == CONDUIT ) + { + // --- find avg. depth from entry/exit conditions + k = Link[j].subIndex; + a = 0.5 * (Conduit[k].a1 + Conduit[k].a2); + Link[j].newVolume = a * link_getLength(j) * Conduit[k].barrels; + y1 = xsect_getYofA(&Link[j].xsect, Conduit[k].a1); + y2 = xsect_getYofA(&Link[j].xsect, Conduit[k].a2); + Link[j].newDepth = 0.5 * (y1 + y2); + + // --- update depths at end nodes + updateNodeDepth(Link[j].node1, y1 + Link[j].offset1); + updateNodeDepth(Link[j].node2, y2 + Link[j].offset2); + + // --- check if capacity limited + if ( Conduit[k].a1 >= Link[j].xsect.aFull ) + { + Conduit[k].capacityLimited = TRUE; + Conduit[k].fullState = ALL_FULL; + } + else + { + Conduit[k].capacityLimited = FALSE; + Conduit[k].fullState = 0; + } + } +} + +//============================================================================= + +void updateNodeDepth(int i, double y) +// +// Input: i = node index +// y = flow depth (ft) +// Output: none +// Purpose: updates water depth at a node with a possibly higher value. +// +{ + // --- storage nodes were updated elsewhere + if ( Node[i].type == STORAGE ) return; + + // --- if non-outfall node is flooded, then use full depth + if ( Node[i].type != OUTFALL && Node[i].degree > 0 && + Node[i].overflow > 0.0 ) y = Node[i].fullDepth; + + // --- if current new depth below y + if ( Node[i].newDepth < y ) + { + // --- update new depth + Node[i].newDepth = y; + + // --- depth cannot exceed full depth (if value exists) + if ( Node[i].fullDepth > 0.0 && y > Node[i].fullDepth ) + { + Node[i].newDepth = Node[i].fullDepth; + } + } +} + +//============================================================================= + +int steadyflow_execute(int j, double* qin, double* qout, double tStep) +// +// Input: j = link index +// qin = inflow to link (cfs) +// tStep = time step (sec) +// Output: qin = adjusted inflow to link (limited by flow capacity) (cfs) +// qout = link's outflow (cfs) +// returns 1 if successful +// Purpose: performs steady flow routing through a single link. +// +{ + int k; + double s; + double q; + + // --- use Manning eqn. to compute flow area for conduits + if ( Link[j].type == CONDUIT ) + { + k = Link[j].subIndex; + q = (*qin) / Conduit[k].barrels; + if ( Link[j].xsect.type == DUMMY ) Conduit[k].a1 = 0.0; + else + { + // --- adjust flow for evap and infil losses + q -= link_getLossRate(j, q); + + // --- flow can't exceed full flow + if ( q > Link[j].qFull ) + { + q = Link[j].qFull; + Conduit[k].a1 = Link[j].xsect.aFull; + (*qin) = q * Conduit[k].barrels; + } + + // --- infer flow area from flow rate + else + { + s = q / Conduit[k].beta; + Conduit[k].a1 = xsect_getAofS(&Link[j].xsect, s); + } + } + Conduit[k].a2 = Conduit[k].a1; + + Conduit[k].q1Old = Conduit[k].q1; + Conduit[k].q2Old = Conduit[k].q2; + + Conduit[k].q1 = q; + Conduit[k].q2 = q; + (*qout) = q * Conduit[k].barrels; + } + else (*qout) = (*qin); + return 1; +} + +//============================================================================= diff --git a/src/forcmain.c b/src/forcmain.c new file mode 100644 index 000000000..a6314bbce --- /dev/null +++ b/src/forcmain.c @@ -0,0 +1,157 @@ +//----------------------------------------------------------------------------- +// forcemain.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Special Non-Manning Force Main functions +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double VISCOS = 1.1E-5; // Kinematic viscosity of water + // @ 20 deg C (sq ft/sec) + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// forcemain_getEquivN +// forcemain_getRoughFactor +// forcemain_getFricSlope + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static double forcemain_getFricFactor(double e, double hrad, double re); +static double forcemain_getReynolds(double v, double hrad); + +//============================================================================= + +double forcemain_getEquivN(int j, int k) +// +// Input: j = link index +// k = conduit index +// Output: returns an equivalent Manning's n for a force main +// Purpose: computes a Mannng's n that results in the same normal flow +// value for a force main flowing full under fully turbulent +// conditions using either the Hazen-Williams or Dary-Weisbach +// flow equations. +// +{ + TXsect xsect = Link[j].xsect; + double f; + double d = xsect.yFull; + switch ( ForceMainEqn ) + { + case H_W: + return 1.067 / xsect.rBot * pow(d/Conduit[k].slope, 0.04); + case D_W: + f = forcemain_getFricFactor(xsect.rBot, d/4.0, 1.0e12); + return sqrt(f/185.0) * pow(d, (1./6.)); + } + return Conduit[k].roughness; +} + +//============================================================================= + +double forcemain_getRoughFactor(int j, double lengthFactor) +// +// Input: j = link index +// lengthFactor = factor by which a pipe will be artifically lengthened +// Output: returns a roughness adjustment factor for a force main +// Purpose: computes an adjustment factor for a force main that compensates for +// any artificial lengthening the pipe may have received. +// +{ + TXsect xsect = Link[j].xsect; + double r; + switch ( ForceMainEqn ) + { + case H_W: + r = 1.318*xsect.rBot*pow(lengthFactor, 0.54); + return GRAVITY / pow(r, 1.852); + case D_W: + return 1.0/8.0/lengthFactor; + } + return 0.0; +} + +//============================================================================= + +double forcemain_getFricSlope(int j, double v, double hrad) +// +// Input: j = link index +// v = flow velocity (ft/sec) +// hrad = hydraulic radius (ft) +// Output: returns a force main pipe's friction slope +// Purpose: computes the headloss per unit length used in dynamic wave +// flow routing for a pressurized force main using either the +// Hazen-Williams or Darcy-Weisbach flow equations. +// Note: the pipe's roughness factor was saved in xsect.sBot in +// conduit_validate() in LINK.C. +// +{ + double re, f; + TXsect xsect = Link[j].xsect; + switch ( ForceMainEqn ) + { + case H_W: + return xsect.sBot * pow(v, 0.852) / pow(hrad, 1.1667); + case D_W: + re = forcemain_getReynolds(v, hrad); + f = forcemain_getFricFactor(xsect.rBot, hrad, re); + return f * xsect.sBot * v / hrad; + } + return 0.0; +} + +//============================================================================= + +double forcemain_getReynolds(double v, double hrad) +// +// Input: v = flow velocity (ft/sec) +// hrad = hydraulic radius (ft) +// Output: returns a flow's Reynolds Number +// Purpose: computes a flow's Reynolds Number +// +{ + return 4.0 * hrad * v / VISCOS; +} + +//============================================================================= + +double forcemain_getFricFactor(double e, double hrad, double re) +// +// Input: e = roughness height (ft) +// hrad = hydraulic radius (ft) +// re = Reynolds number +// Output: returns a Darcy-Weisbach friction factor +// Purpose: computes the Darcy-Weisbach friction factor for a force main +// using the Swamee and Jain approximation to the Colebrook-White +// equation. +// +{ + double f; + if ( re < 10.0 ) re = 10.0; + if ( re <= 2000.0 ) f = 64.0 / re; + else if ( re < 4000.0 ) + { + f = forcemain_getFricFactor(e, hrad, 4000.0); + f = 0.032 + (f - 0.032) * ( re - 2000.0) / 2000.0; + } + else + { + f = e/3.7/(4.0*hrad); + if ( re < 1.0e10 ) f += 5.74/pow(re, 0.9); + f = log10(f); + f = 0.25 / f / f; + } + return f; +} diff --git a/src/funcs.h b/src/funcs.h new file mode 100644 index 000000000..6f9782723 --- /dev/null +++ b/src/funcs.h @@ -0,0 +1,547 @@ +//----------------------------------------------------------------------------- +// funcs.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// M. Tryby (EPA) +// +// Global interfacing functions. +// +// Update History +// ============== +// Build 5.1.007: +// - climate_readAdjustments() added. +// Build 5.1.008: +// - Function list was re-ordered and blank lines added for readability. +// - Pollutant buildup/washoff functions for the new surfqual.c module added. +// - Several other functions added, re-named or have modified arguments. +// Build 5.1.010: +// - New roadway_getInflow() function added. +// Build 5.1.013: +// - Additional arguments added to function stats_updateSubcatchStats. +// Build 5.1.014: +// - Arguments to link_getLossRate function changed. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for reporting most frequent non-converging links. +// - Support added for named variables & math expressions in control rules. +// - Support added for tracking a gage's prior n-hour rainfall total. +// - Refactored external inflow code. +//----------------------------------------------------------------------------- + +#ifndef FUNCS_H +#define FUNCS_H + +//----------------------------------------------------------------------------- +// Project Methods +//----------------------------------------------------------------------------- +void project_open(const char *f1, const char *f2, const char *f3); +void project_close(void); + +void project_readInput(void); +int project_readOption(char* s1, char* s2); +void project_validate(void); +int project_init(void); + +int project_addObject(int type, char* id, int n); +int project_findObject(int type, const char* id); +char* project_findID(int type, char* id); + +double** project_createMatrix(int nrows, int ncols); +void project_freeMatrix(double** m); + +//----------------------------------------------------------------------------- +// Input Reader Methods +//----------------------------------------------------------------------------- +int input_countObjects(void); +int input_readData(void); + +//----------------------------------------------------------------------------- +// Report Writer Methods +//----------------------------------------------------------------------------- +int report_readOptions(char* tok[], int ntoks); + +void report_writeLine(const char* line); +void report_writeSysTime(void); +void report_writeLogo(void); +void report_writeTitle(void); +void report_writeOptions(void); +void report_writeReport(void); + +void report_writeRainStats(int gage, TRainStats* rainStats); +void report_writeRdiiStats(double totalRain, double totalRdii); + +void report_writeControlActionsHeading(void); +void report_writeControlAction(DateTime aDate, char* linkID, double value, + char* ruleID); + +void report_writeRunoffError(TRunoffTotals* totals, double area); +void report_writeLoadingError(TLoadingTotals* totals); +void report_writeGwaterError(TGwaterTotals* totals, double area); +void report_writeFlowError(TRoutingTotals* totals); +void report_writeQualError(TRoutingTotals* totals); + +void report_writeMaxStats(TMaxStats massBalErrs[], TMaxStats CourantCrit[], + int nMaxStats); +void report_writeMaxFlowTurns(TMaxStats flowTurns[], int nMaxStats); +void report_writeNonconvergedStats(TMaxStats maxNonconverged[], + int nMaxStats); +void report_writeTimeStepStats(TTimeStepStats* timeStepStats); + +void report_writeErrorMsg(int code, char* msg); +void report_writeErrorCode(void); +void report_writeInputErrorMsg(int k, int sect, char* line, long lineCount); +void report_writeWarningMsg(char* msg, char* id); +void report_writeTseriesErrorMsg(int code, TTable *tseries); + +void inputrpt_writeInput(void); +void statsrpt_writeReport(void); + +//----------------------------------------------------------------------------- +// Temperature/Evaporation Methods +//----------------------------------------------------------------------------- +int climate_readParams(char* tok[], int ntoks); +int climate_readEvapParams(char* tok[], int ntoks); +int climate_readAdjustments(char* tok[], int ntoks); +void climate_validate(void); +void climate_openFile(void); +void climate_initState(void); +void climate_setState(DateTime aDate); +DateTime climate_getNextEvapDate(void); + +//----------------------------------------------------------------------------- +// Rainfall Processing Methods +//----------------------------------------------------------------------------- +void rain_open(void); +void rain_close(void); + +//----------------------------------------------------------------------------- +// Snowmelt Processing Methods +//----------------------------------------------------------------------------- +int snow_readMeltParams(char* tok[], int ntoks); +int snow_createSnowpack(int subcacth, int snowIndex); + +void snow_validateSnowmelt(int snowIndex); +void snow_initSnowpack(int subcatch); +void snow_initSnowmelt(int snowIndex); + +void snow_getState(int subcatch, int subArea, double x[]); +void snow_setState(int subcatch, int subArea, double x[]); + +void snow_setMeltCoeffs(int snowIndex, double season); +void snow_plowSnow(int subcatch, double tStep); +double snow_getSnowMelt(int subcatch, double rainfall, double snowfall, + double tStep, double netPrecip[]); +double snow_getSnowCover(int subcatch); + +//----------------------------------------------------------------------------- +// Runoff Analyzer Methods +//----------------------------------------------------------------------------- +int runoff_open(void); +void runoff_execute(void); +void runoff_close(void); + +//----------------------------------------------------------------------------- +// Conveyance System Routing Methods +//----------------------------------------------------------------------------- +int routing_open(void); +double routing_getRoutingStep(int routingModel, double fixedStep); +void routing_execute(int routingModel, double routingStep); +void routing_close(int routingModel); + +//----------------------------------------------------------------------------- +// Output Filer Methods +//----------------------------------------------------------------------------- +int output_open(void); +void output_end(void); +void output_close(void); +void output_saveResults(double reportTime); +void output_updateAvgResults(void); +void output_readDateTime(long period, DateTime *aDate); +void output_readSubcatchResults(long period, int index); +void output_readNodeResults(int long, int index); +void output_readLinkResults(int long, int index); + +//----------------------------------------------------------------------------- +// Groundwater Methods +//----------------------------------------------------------------------------- +int gwater_readAquiferParams(int aquifer, char* tok[], int ntoks); +int gwater_readGroundwaterParams(char* tok[], int ntoks); +int gwater_readFlowExpression(char* tok[], int ntoks); +void gwater_deleteFlowExpression(int subcatch); + +void gwater_validateAquifer(int aquifer); +void gwater_validate(int subcatch); + +void gwater_initState(int subcatch); +void gwater_getState(int subcatch, double x[]); +void gwater_setState(int subcatch, double x[]); + +void gwater_getGroundwater(int subcatch, double evap, double infil, + double tStep); +double gwater_getVolume(int subcatch); + +//----------------------------------------------------------------------------- +// RDII Methods +//----------------------------------------------------------------------------- +int rdii_readRdiiInflow(char* tok[], int ntoks); +void rdii_deleteRdiiInflow(int node); +void rdii_initUnitHyd(int unitHyd); +int rdii_readUnitHydParams(char* tok[], int ntoks); +void rdii_openRdii(void); +void rdii_closeRdii(void); +int rdii_getNumRdiiFlows(DateTime aDate); +void rdii_getRdiiFlow(int index, int* node, double* q); + +//----------------------------------------------------------------------------- +// Landuse Methods +//----------------------------------------------------------------------------- +int landuse_readParams(int landuse, char* tok[], int ntoks); +int landuse_readPollutParams(int pollut, char* tok[], int ntoks); +int landuse_readBuildupParams(char* tok[], int ntoks); +int landuse_readWashoffParams(char* tok[], int ntoks); + +void landuse_getInitBuildup(TLandFactor* landFactor, double* initBuildup, + double area, double curb); +double landuse_getBuildup(int landuse, int pollut, double area, double curb, + double buildup, double tStep); + +double landuse_getWashoffLoad(int landuse, int p, double area, + TLandFactor landFactor[], double runoff, double vOutflow); +double landuse_getAvgBmpEffic(int j, int p); +double landuse_getCoPollutLoad(int p, double washoff[]); + +//----------------------------------------------------------------------------- +// Flow/Quality Routing Methods +//----------------------------------------------------------------------------- +void flowrout_init(int routingModel); +void flowrout_close(int routingModel); +double flowrout_getRoutingStep(int routingModel, double fixedStep); +int flowrout_execute(int links[], int routingModel, double tStep); + +void toposort_sortLinks(int links[]); +int kinwave_execute(int link, double* qin, double* qout, double tStep); + +void dynwave_validate(void); +void dynwave_init(void); +void dynwave_close(void); +double dynwave_getRoutingStep(double fixedStep); +int dynwave_execute(double tStep); +void dwflow_findConduitFlow(int j, int steps, double omega, double dt); + +void qualrout_init(void); +void qualrout_execute(double tStep); + +//----------------------------------------------------------------------------- +// Treatment Methods +//----------------------------------------------------------------------------- +int treatmnt_open(void); +void treatmnt_close(void); +int treatmnt_readExpression(char* tok[], int ntoks); +void treatmnt_delete(int node); +void treatmnt_treat(int node, double q, double v, double tStep); +void treatmnt_setInflow(double qIn, double wIn[]); + +//----------------------------------------------------------------------------- +// Mass Balance Methods +//----------------------------------------------------------------------------- +int massbal_open(void); +void massbal_close(void); +void massbal_report(void); + +void massbal_updateRunoffTotals(int type, double v); +void massbal_updateLoadingTotals(int type, int pollut, double w); +void massbal_updateGwaterTotals(double vInfil, double vUpperEvap, + double vLowerEvap, double vLowerPerc, double vGwater); +void massbal_updateRoutingTotals(double tStep); + + +void massbal_initTimeStepTotals(void); +void massbal_addInflowFlow(int type, double q); +void massbal_addInflowQual(int type, int pollut, double w); +void massbal_addOutflowFlow(double q, int isFlooded); +void massbal_addOutflowQual(int pollut, double mass, int isFlooded); +void massbal_addNodeLosses(double evapLoss, double infilLoss); +void massbal_addLinkLosses(double evapLoss, double infilLoss); +void massbal_addReactedMass(int pollut, double mass); +void massbal_addSeepageLoss(int pollut, double seepLoss); +void massbal_addToFinalStorage(int pollut, double mass); +double massbal_getStepFlowError(void); +double massbal_getRunoffError(void); +double massbal_getFlowError(void); + +//----------------------------------------------------------------------------- +// Simulation Statistics Methods +//----------------------------------------------------------------------------- +int stats_open(void); +void stats_close(void); +void stats_report(void); + +void stats_updateCriticalTimeCount(int node, int link); +void stats_updateFlowStats(double tStep, DateTime aDate); +void stats_updateTimeStepStats(double tStep, int trialsCount, int steadyState); + +void stats_updateSubcatchStats(int subcatch, double rainVol, + double runonVol, double evapVol, double infilVol, + double impervVol, double pervVol, double runoffVol, double runoff); +void stats_updateGwaterStats(int j, double infil, double evap, + double latFlow, double deepFlow, double theta, double waterTable, + double tStep); +void stats_updateMaxRunoff(void); +void stats_updateMaxNodeDepth(int node, double depth); +void stats_updateConvergenceStats(int node, int converged); + + +//----------------------------------------------------------------------------- +// Raingage Methods +//----------------------------------------------------------------------------- +int gage_readParams(int gage, char* tok[], int ntoks); +void gage_validate(int gage); +void gage_initState(int gage); +void gage_setState(int gage, DateTime aDate); +double gage_getPrecip(int gage, double *rainfall, double *snowfall); +void gage_setReportRainfall(int gage, DateTime aDate); +DateTime gage_getNextRainDate(int gage, DateTime aDate); +void gage_updatePastRain(int j, int tStep); +double gage_getPastRain(int gage, int hrs); + +//----------------------------------------------------------------------------- +// Subcatchment Methods +//----------------------------------------------------------------------------- +int subcatch_readParams(int subcatch, char* tok[], int ntoks); +int subcatch_readSubareaParams(char* tok[], int ntoks); +int subcatch_readLanduseParams(char* tok[], int ntoks); +int subcatch_readInitBuildup(char* tok[], int ntoks); + +void subcatch_validate(int subcatch); +void subcatch_initState(int subcatch); +void subcatch_setOldState(int subcatch); + +double subcatch_getFracPerv(int subcatch); +double subcatch_getStorage(int subcatch); +double subcatch_getDepth(int subcatch); + +void subcatch_getRunon(int subcatch); +void subcatch_addRunonFlow(int subcatch, double flow); +double subcatch_getRunoff(int subcatch, double tStep); + +double subcatch_getWtdOutflow(int subcatch, double wt); +void subcatch_getResults(int subcatch, double wt, float x[]); + +//----------------------------------------------------------------------------- +// Surface Pollutant Buildup/Washoff Methods +//----------------------------------------------------------------------------- +void surfqual_initState(int subcatch); +void surfqual_getWashoff(int subcatch, double runoff, double tStep); +void surfqual_getBuildup(int subcatch, double tStep); +void surfqual_sweepBuildup(int subcatch, DateTime aDate); +double surfqual_getWtdWashoff(int subcatch, int pollut, double wt); + +//----------------------------------------------------------------------------- +// Conveyance System Node Methods +//----------------------------------------------------------------------------- +int node_readParams(int node, int type, int subIndex, char* tok[], int ntoks); +void node_validate(int node); + +void node_initState(int node); +void node_initFlows(int node, double tStep); +void node_setOldHydState(int node); +void node_setOldQualState(int node); +void node_setOutletDepth(int node, double yNorm, double yCrit, double z); + +double node_getSurfArea(int node, double depth); +double node_getDepth(int node, double volume); +double node_getVolume(int node, double depth); +double node_getPondedArea(int node, double depth); + +double node_getOutflow(int node, int link); +double node_getLosses(int node, double tStep); +double node_getMaxOutflow(int node, double q, double tStep); +double node_getSystemOutflow(int node, int *isFlooded); +void node_getResults(int node, double wt, float x[]); + +//----------------------------------------------------------------------------- +// Conveyance System Inflow Methods +//----------------------------------------------------------------------------- +int inflow_readExtInflow(char* tok[], int ntoks); +int inflow_readDwfInflow(char* tok[], int ntoks); +int inflow_readDwfPattern(char* tok[], int ntoks); +int inflow_setExtInflow(int j, int param, int type, int tSeries, + int basePat, double cf, double baseline, double sf); + +void inflow_initDwfInflow(TDwfInflow* inflow); +void inflow_initDwfPattern(int pattern); + +double inflow_getExtInflow(TExtInflow* inflow, DateTime aDate); +double inflow_getDwfInflow(TDwfInflow* inflow, int m, int d, int h); + +void inflow_deleteExtInflows(int node); +void inflow_deleteDwfInflows(int node); + +//----------------------------------------------------------------------------- +// Routing Interface File Methods +//----------------------------------------------------------------------------- +int iface_readFileParams(char* tok[], int ntoks); +void iface_openRoutingFiles(void); +void iface_closeRoutingFiles(void); +int iface_getNumIfaceNodes(DateTime aDate); +int iface_getIfaceNode(int index); +double iface_getIfaceFlow(int index); +double iface_getIfaceQual(int index, int pollut); +void iface_saveOutletResults(DateTime reportDate, FILE* file); + +//----------------------------------------------------------------------------- +// Hot Start File Methods +//----------------------------------------------------------------------------- +int hotstart_open(void); +void hotstart_close(void); + +//----------------------------------------------------------------------------- +// Conveyance System Link Methods +//----------------------------------------------------------------------------- +int link_readParams(int link, int type, int subIndex, char* tok[], int ntoks); +int link_readXsectParams(char* tok[], int ntoks); +int link_readLossParams(char* tok[], int ntoks); + +void link_validate(int link); +void link_initState(int link); +void link_setOldHydState(int link); +void link_setOldQualState(int link); + +void link_setTargetSetting(int j); +void link_setSetting(int j, double tstep); +int link_setFlapGate(int link, int n1, int n2, double q); + +double link_getInflow(int link); +void link_setOutfallDepth(int link); +double link_getLength(int link); +double link_getYcrit(int link, double q); +double link_getYnorm(int link, double q); +double link_getVelocity(int link, double q, double y); +double link_getFroude(int link, double v, double y); +double link_getPower(int link); +double link_getLossRate(int link, double q); +char link_getFullState(double a1, double a2, double aFull); + +void link_getResults(int link, double wt, float x[]); + +//----------------------------------------------------------------------------- +// Link Cross-Section Methods +//----------------------------------------------------------------------------- +int xsect_isOpen(int type); +int xsect_setParams(TXsect *xsect, int type, double p[], double ucf); +void xsect_setIrregXsectParams(TXsect *xsect); +void xsect_setCustomXsectParams(TXsect *xsect); +void xsect_setStreetXsectParams(TXsect *xsect); +double xsect_getAmax(TXsect* xsect); + +double xsect_getSofA(TXsect* xsect, double area); +double xsect_getYofA(TXsect* xsect, double area); +double xsect_getRofA(TXsect* xsect, double area); +double xsect_getAofS(TXsect* xsect, double sFactor); +double xsect_getdSdA(TXsect* xsect, double area); +double xsect_getAofY(TXsect* xsect, double y); +double xsect_getRofY(TXsect* xsect, double y); +double xsect_getWofY(TXsect* xsect, double y); +double xsect_getYcrit(TXsect* xsect, double q); + +//----------------------------------------------------------------------------- +// Culvert/Roadway Methods +//----------------------------------------------------------------------------- +double culvert_getInflow(int link, double q, double h); +double roadway_getInflow(int link, double dir, double hcrest, double h1, + double h2); + +//----------------------------------------------------------------------------- +// Force Main Methods +//----------------------------------------------------------------------------- +double forcemain_getEquivN(int j, int k); +double forcemain_getRoughFactor(int j, double lengthFactor); +double forcemain_getFricSlope(int j, double v, double hrad); + +//----------------------------------------------------------------------------- +// Cross-Section Transect Methods +//----------------------------------------------------------------------------- +int transect_create(int n); +void transect_delete(void); +int transect_readParams(int* count, char* tok[], int ntoks); +void transect_validate(int j); +void transect_createStreetTransect(TStreet* street); + +//----------------------------------------------------------------------------- +// Street Cross-Section Methods +//----------------------------------------------------------------------------- +int street_create(int nStreets); +void street_delete(); +int street_readParams(char* tok[], int ntoks); +double street_getExtentFilled(int link); + +//----------------------------------------------------------------------------- +// Custom Shape Cross-Section Methods +//----------------------------------------------------------------------------- +int shape_validate(TShape *shape, TTable *curve); + +//----------------------------------------------------------------------------- +// Control Rule Methods +//----------------------------------------------------------------------------- +int controls_create(int n); +void controls_delete(void); +void controls_init(void); +void controls_addToCount(char* s); +int controls_addVariable(char* tok[], int ntoks); +int controls_addExpression(char* tok[], int ntoks); +int controls_addRuleClause(int rule, int keyword, char* Tok[], int nTokens); +int controls_evaluate(DateTime currentTime, DateTime elapsedTime, + double tStep); + +//----------------------------------------------------------------------------- +// Table & Time Series Methods +//----------------------------------------------------------------------------- +int table_readCurve(char* tok[], int ntoks); +int table_readTimeseries(char* tok[], int ntoks); + +int table_addEntry(TTable* table, double x, double y); +int table_getFirstEntry(TTable* table, double* x, double* y); +int table_getNextEntry(TTable* table, double* x, double* y); +void table_deleteEntries(TTable* table); + +void table_init(TTable* table); +int table_validate(TTable* table); + +double table_lookup(TTable* table, double x); +double table_lookupEx(TTable* table, double x); +double table_intervalLookup(TTable* table, double x); +double table_inverseLookup(TTable* table, double y); + +double table_getSlope(TTable *table, double x); +double table_getMaxY(TTable *table, double x); +double table_getStorageVolume(TTable* table, double x); +double table_getStorageDepth(TTable* table, double v); + +void table_tseriesInit(TTable *table); +double table_tseriesLookup(TTable* table, double t, char extend); + +//----------------------------------------------------------------------------- +// Utility Methods +//----------------------------------------------------------------------------- +double UCF(int quantity); // units conversion factor +int getInt(char *s, int *y); // get integer from string +int getFloat(char *s, float *y); // get float from string +int getDouble(char *s, double *y); // get double from string +char* getTempFileName(char *s); // get temporary file name +int findmatch(char *s, char *keyword[]); // search for matching keyword +int match(char *str, char *substr); // true if substr matches part of str +int strcomp(const char *s1, const char *s2); // case insensitive string compare +size_t sstrncpy(char *dest, const char *src, + size_t n); // safe string copy +size_t sstrcat(char* dest, const char* src, + size_t destsize); // safe string concatenation +void writecon(const char *s); // writes string to console +DateTime getDateTime(double elapsedMsec); // convert elapsed time to date +void getElapsedTime(DateTime aDate, // convert elapsed date + int* days, int* hrs, int* mins); +char* addAbsolutePath(char *fname); // add full path to a file name + +#endif //FUNCS_H diff --git a/src/gage.c b/src/gage.c new file mode 100644 index 000000000..031cc017c --- /dev/null +++ b/src/gage.c @@ -0,0 +1,705 @@ +//----------------------------------------------------------------------------- +// gage.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Rain gage functions. +// +// Update History +// ============== +// Build 5.1.007: +// - Support for monthly rainfall adjustments added. +// Build 5.1.013: +// - Validation no longer performed on unused gages. +// Build 5.2.0: +// - Support added for tracking a gage's prior n-hour rainfall total. +// - Support added for relative file names. +// - Support added for setting rainfall through API call. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +const double OneSecond = 1.1574074e-5; + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// gage_readParams (called by input_readLine) +// gage_validate (called by project_validate) +// gage_initState (called by project_init) +// gage_setState (called by runoff_execute & getRainfall in rdii.c) +// gage_getPrecip (called by subcatch_getRunoff) +// gage_getNextRainDate (called by runoff_getTimeStep) +// gage_updatePastRain (called by runoff_execute) +// gage_getPastRain (called by getRainValue in controls.c) +// gage_setReportRainfall (called by output_saveSubcatchResults) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int readGageSeriesFormat(char* tok[], int ntoks, double x[]); +static int readGageFileFormat(char* tok[], int ntoks, double x[]); +static int getFirstRainfall(int gage); +static int getNextRainfall(int gage); +static double convertRainfall(int gage, double rain); +static void initPastRain(int gage); + +//============================================================================= + +int gage_readParams(int j, char* tok[], int ntoks) +// +// Input: j = rain gage index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads rain gage parameters from a line of input data +// +// Data formats are: +// Name RainType RecdFreq SCF TIMESERIES SeriesName +// Name RainType RecdFreq SCF FILE FileName Station Units StartDate +// +{ + int k, err; + char *id; + char fname[MAXFNAME+1]; + char staID[MAXMSG+1]; + double x[7]; + + // --- check that gage exists + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(GAGE, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + + // --- assign default parameter values + x[0] = -1.0; // No time series index + x[1] = 1.0; // Rain type is volume + x[2] = 3600.0; // Recording freq. is 3600 sec + x[3] = 1.0; // Snow catch deficiency factor + x[4] = NO_DATE; // Default is no start/end date + x[5] = NO_DATE; + x[6] = 0.0; // US units + fname[0] = '\0'; + staID[0] = '\0'; + + if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); + k = findmatch(tok[4], GageDataWords); + if ( k == RAIN_TSERIES ) + { + err = readGageSeriesFormat(tok, ntoks, x); + } + else if ( k == RAIN_FILE ) + { + if ( ntoks < 8 ) return error_setInpError(ERR_ITEMS, ""); + sstrncpy(fname, tok[5], MAXFNAME); + sstrncpy(staID, tok[6], MAXMSG); + err = readGageFileFormat(tok, ntoks, x); + } + else return error_setInpError(ERR_KEYWORD, tok[4]); + + // --- save parameters to rain gage object + if ( err > 0 ) return err; + Gage[j].ID = id; + Gage[j].tSeries = (int)x[0]; + Gage[j].rainType = (int)x[1]; + Gage[j].rainInterval = (int)x[2]; + Gage[j].snowFactor = x[3]; + Gage[j].rainUnits = (int)x[6]; + if ( Gage[j].tSeries >= 0 ) Gage[j].dataSource = RAIN_TSERIES; + else Gage[j].dataSource = RAIN_FILE; + if ( Gage[j].dataSource == RAIN_FILE ) + { + sstrncpy(Gage[j].fname, addAbsolutePath(fname), MAXFNAME); + sstrncpy(Gage[j].staID, staID, MAXMSG); + Gage[j].startFileDate = x[4]; + Gage[j].endFileDate = x[5]; + } + Gage[j].unitsFactor = 1.0; + Gage[j].coGage = -1; + Gage[j].isUsed = FALSE; + return 0; +} + +//============================================================================= + +int readGageSeriesFormat(char* tok[], int ntoks, double x[]) +{ + int m, ts; + DateTime aTime; + + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + + // --- determine type of rain data + m = findmatch(tok[1], RainTypeWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + x[1] = (double)m; + + // --- get data time interval & convert to seconds + if ( getDouble(tok[2], &x[2]) ) x[2] = floor(x[2]*3600 + 0.5); + else if ( datetime_strToTime(tok[2], &aTime) ) + { + x[2] = floor(aTime*SECperDAY + 0.5); + } + else return error_setInpError(ERR_DATETIME, tok[2]); + if ( x[2] <= 0.0 ) return error_setInpError(ERR_DATETIME, tok[2]); + + // --- get snow catch deficiency factor + if ( !getDouble(tok[3], &x[3]) ) + return error_setInpError(ERR_DATETIME, tok[3]);; + + // --- get time series index + ts = project_findObject(TSERIES, tok[5]); + if ( ts < 0 ) return error_setInpError(ERR_NAME, tok[5]); + x[0] = (double)ts; + sstrncpy(tok[2], "", 0); + return 0; +} + +//============================================================================= + +int readGageFileFormat(char* tok[], int ntoks, double x[]) +{ + int m, u; + DateTime aDate; + DateTime aTime; + + // --- determine type of rain data + m = findmatch(tok[1], RainTypeWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + x[1] = (double)m; + + // --- get data time interval & convert to seconds + if ( getDouble(tok[2], &x[2]) ) x[2] *= 3600; + else if ( datetime_strToTime(tok[2], &aTime) ) + { + x[2] = floor(aTime*SECperDAY + 0.5); + } + else return error_setInpError(ERR_DATETIME, tok[2]); + if ( x[2] <= 0.0 ) return error_setInpError(ERR_DATETIME, tok[2]); + + // --- get snow catch deficiency factor + if ( !getDouble(tok[3], &x[3]) ) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- get rain depth units + u = findmatch(tok[7], RainUnitsWords); + if ( u < 0 ) return error_setInpError(ERR_KEYWORD, tok[7]); + x[6] = (double)u; + + // --- get start date (if present) + if ( ntoks > 8 && *tok[8] != '*') + { + if ( !datetime_strToDate(tok[8], &aDate) ) + return error_setInpError(ERR_DATETIME, tok[8]); + x[4] = (float) aDate; + } + return 0; +} + +//============================================================================= + +void gage_validate(int j) +// +// Input: j = rain gage index +// Output: none +// Purpose: checks for valid rain gage parameters +// +// NOTE: assumes that any time series used by a rain gage has been +// previously validated. +// +{ + int i, k; + int gageInterval; + + // --- for gage with time series data: + if ( Gage[j].dataSource == RAIN_TSERIES ) + { + // --- no validation for an unused gage + if ( !Gage[j].isUsed ) return; + + // --- see if gage uses same time series as another gage + k = Gage[j].tSeries; + for (i=0; i= 0 ) + { + report_writeErrorMsg(ERR_RAIN_GAGE_TSERIES, Gage[j].ID); + } + gageInterval = (int)(floor(Tseries[k].dxMin*SECperDAY + 0.5)); + if ( gageInterval > 0 && Gage[j].rainInterval > gageInterval ) + { + report_writeErrorMsg(ERR_RAIN_GAGE_INTERVAL, Gage[j].ID); + } + if ( Gage[j].rainInterval < gageInterval ) + { + report_writeWarningMsg(WARN09, Gage[j].ID); + } + if ( Gage[j].rainInterval < WetStep ) + { + report_writeWarningMsg(WARN01, Gage[j].ID); + WetStep = Gage[j].rainInterval; + } + } +} + +//============================================================================= + +void gage_initState(int j) +// +// Input: j = rain gage index +// Output: none +// Purpose: initializes state of rain gage. +// +{ + // --- initialize actual and reported rainfall + Gage[j].rainfall = 0.0; + Gage[j].apiRainfall = MISSING; + Gage[j].reportRainfall = 0.0; + if ( IgnoreRainfall ) return; + + // --- for gage with file data: + if ( Gage[j].dataSource == RAIN_FILE ) + { + // --- set current file position to start of period of record + Gage[j].currentFilePos = Gage[j].startFilePos; + + // --- assign units conversion factor + // (rain depths on interface file are in inches) + if ( UnitSystem == SI ) Gage[j].unitsFactor = MMperINCH; + } + + // --- get first & next rainfall values + if ( getFirstRainfall(j) ) + { + // --- find date at end of starting rain interval + Gage[j].endDate = datetime_addSeconds( + Gage[j].startDate, Gage[j].rainInterval); + + // --- if rainfall record begins after start of simulation, + if ( Gage[j].startDate > StartDateTime ) + { + // --- make next rainfall date the start of the rain record + Gage[j].nextDate = Gage[j].startDate; + Gage[j].nextRainfall = Gage[j].rainfall; + + // --- make start of current rain interval the simulation start + Gage[j].startDate = StartDateTime; + Gage[j].endDate = Gage[j].nextDate; + Gage[j].rainfall = 0.0; + } + + // --- otherwise find next recorded rainfall + else if ( !getNextRainfall(j) ) Gage[j].nextDate = NO_DATE; + } + else Gage[j].startDate = NO_DATE; + initPastRain(j); +} + +//============================================================================= + +void gage_setState(int j, DateTime t) +// +// Input: j = rain gage index +// t = a calendar date/time +// Output: none +// Purpose: updates state of rain gage for specified date. +// +{ + // --- return if gage not used by any subcatchment + if ( Gage[j].isUsed == FALSE ) return; + + // --- set rainfall to zero if disabled + if ( IgnoreRainfall ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- use rainfall from co-gage (gage with lower index that uses + // same rainfall time series or file) if it exists + if ( Gage[j].coGage >= 0) + { + Gage[j].rainfall = Gage[Gage[j].coGage].rainfall; + return; + } + + // --- use rainfall supplied by API function call + // (where constant ZERO (1.e-10) is used for 0 rainfall) + if (Gage[j].apiRainfall != MISSING) + { + Gage[j].rainfall = Gage[j].apiRainfall; + return; + } + + // --- otherwise march through rainfall record until date t is bracketed + t += OneSecond; + for (;;) + { + // --- no rainfall if no interval start date + if ( Gage[j].startDate == NO_DATE ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- no rainfall if time is before interval start date + if ( t < Gage[j].startDate ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- use current rainfall if time is before interval end date + if ( t < Gage[j].endDate ) + { + return; + } + + // --- no rainfall if t >= interval end date & no next interval exists + if ( Gage[j].nextDate == NO_DATE ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- no rainfall if t > interval end date & < next interval date + if ( t < Gage[j].nextDate ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- otherwise update next rainfall interval date + Gage[j].startDate = Gage[j].nextDate; + Gage[j].endDate = datetime_addSeconds(Gage[j].startDate, + Gage[j].rainInterval); + Gage[j].rainfall = Gage[j].nextRainfall; + if ( !getNextRainfall(j) ) Gage[j].nextDate = NO_DATE; + } +} + +//============================================================================= + +void initPastRain(int j) +{ + // --- initialize past hourly rain accumulation + int i; + for (i = 0; i <= MAXPASTRAIN; i++) + Gage[j].pastRain[i] = 0.0; + Gage[j].pastInterval = 0; +} + +//============================================================================= + +void gage_updatePastRain(int j, int tStep) +// +// Input: j = rain gage index +// tStep = current runoff time step (sec) +// Output: none +// Purpose: updates past MAXPASTRAIN hourly rain totals. +// +// Note: pastRain[0] is past rain volume prior to 1 hour, +// pastRain[n] is past rain volume after n hours, +// pastInterval is time since last hour was reached. +{ + int i, t; + double r; + + // --- current rainfall intensity (in/sec or mm/sec) + r = Gage[j].rainfall / 3600.; + + // --- process each hourly interval of current time step + while (tStep > 0) + { + // --- time for most recent rainfall interval to reach 1 hr + t = 3600 - Gage[j].pastInterval; + + // --- remaining time step is greater than this time + if (tStep > t) + { + // --- add current rain to most recent interval + Gage[j].pastRain[0] += t * r; + + // --- shift all prior hourly rain amounts by 1 hour + for (i = MAXPASTRAIN; i > 0; i-- ) + Gage[j].pastRain[i] = Gage[j].pastRain[i-1]; + + // --- begin a new most recent interval + Gage[j].pastInterval = 0; + Gage[j].pastRain[0] = 0.0; + tStep -= t; + } + // --- time to reach 1 hr in most recent interval is greater + // than remaining time step so update most recent interval + else + { + Gage[j].pastRain[0] += tStep * r; + Gage[j].pastInterval += tStep; + tStep = 0; + } + } +} + +//============================================================================= + +double gage_getPastRain(int j, int n) +// +// Input: j = rain gage index +// n = number of hours prior to current date +// Output: cumulative rain volume (inches or mm) in last n hours +// Purpose: retrieves rainfall total over some previous number of hours. +// +{ + int i; + double result = 0.0; + if (n < 1 || n > MAXPASTRAIN) return 0.0; + for (i = 1; i <= n; i++) + result += Gage[j].pastRain[i]; + return result; +} + +//============================================================================= + +DateTime gage_getNextRainDate(int j, DateTime aDate) +// +// Input: j = rain gage index +// aDate = calendar date/time +// Output: next date with rainfall occurring +// Purpose: finds the next date from specified date when rainfall occurs. +// +{ + if ( Gage[j].isUsed == FALSE ) return aDate; + aDate += OneSecond; + if ( aDate < Gage[j].startDate ) return Gage[j].startDate; + if ( aDate < Gage[j].endDate ) return Gage[j].endDate; + return Gage[j].nextDate; +} + +//============================================================================= + +double gage_getPrecip(int j, double *rainfall, double *snowfall) +// +// Input: j = rain gage index +// Output: rainfall = rainfall rate (ft/sec) +// snowfall = snow fall rate (ft/sec) +// returns total precipitation (ft/sec) +// Purpose: determines whether gage's recorded rainfall is rain or snow. +// +{ + *rainfall = 0.0; + *snowfall = 0.0; + if ( !IgnoreSnowmelt && Temp.ta <= Snow.snotmp ) + { + *snowfall = Gage[j].rainfall * Gage[j].snowFactor / UCF(RAINFALL); + } + else *rainfall = Gage[j].rainfall / UCF(RAINFALL); + return (*rainfall) + (*snowfall); +} + +//============================================================================= + +void gage_setReportRainfall(int j, DateTime reportDate) +// +// Input: j = rain gage index +// reportDate = date/time value of current reporting time +// Output: none +// Purpose: sets the rainfall value reported at the current reporting time. +// +{ + double result; + + // --- use value from co-gage if it exists + if ( Gage[j].coGage >= 0) + { + Gage[j].reportRainfall = Gage[Gage[j].coGage].reportRainfall; + return; + } + + // --- rainfall set by API call + if (Gage[j].apiRainfall != MISSING) + { + Gage[j].reportRainfall = Gage[j].apiRainfall; + return; + } + + // --- otherwise increase reporting time by 1 second to avoid + // roundoff problems + reportDate += OneSecond; + + // --- use current rainfall if report date/time is before end + // of current rain interval + if ( reportDate < Gage[j].endDate ) result = Gage[j].rainfall; + + // --- use 0.0 if report date/time is before start of next rain interval + else if ( reportDate < Gage[j].nextDate ) result = 0.0; + + // --- otherwise report date/time falls right on end of current rain + // interval and start of next interval so use next interval's rainfall + else result = Gage[j].nextRainfall; + Gage[j].reportRainfall = result; +} + +//============================================================================= + +int getFirstRainfall(int j) +// +// Input: j = rain gage index +// Output: returns TRUE if successful +// Purpose: positions rainfall record to date with first rainfall. +// +{ + int k; // time series index + float vFirst; // first rain volume (ft or m) + double rFirst; // first rain intensity (in/hr or mm/hr) + + // --- assign default values to date & rainfall + Gage[j].startDate = NO_DATE; + Gage[j].rainfall = 0.0; + + // --- initialize internal cumulative rainfall value + Gage[j].rainAccum = 0; + + // --- use rain interface file if applicable + if ( Gage[j].dataSource == RAIN_FILE ) + { + if ( Frain.file && Gage[j].endFilePos > Gage[j].startFilePos ) + { + // --- retrieve 1st date & rainfall volume from file + fseek(Frain.file, Gage[j].startFilePos, SEEK_SET); + fread(&Gage[j].startDate, sizeof(DateTime), 1, Frain.file); + fread(&vFirst, sizeof(float), 1, Frain.file); + Gage[j].currentFilePos = ftell(Frain.file); + + // --- convert rainfall to intensity + Gage[j].rainfall = convertRainfall(j, (double)vFirst); + return 1; + } + return 0; + } + + // --- otherwise access user-supplied rainfall time series + else + { + k = Gage[j].tSeries; + if ( k >= 0 ) + { + // --- retrieve first rainfall value from time series + if ( table_getFirstEntry(&Tseries[k], &Gage[j].startDate, + &rFirst) ) + { + // --- convert rainfall to intensity + Gage[j].rainfall = convertRainfall(j, rFirst); + return 1; + } + } + return 0; + } +} + +//============================================================================= + +int getNextRainfall(int j) +// +// Input: j = rain gage index +// Output: returns 1 if successful; 0 if not +// Purpose: positions rainfall record to date with next non-zero rainfall +// while updating the gage's next rain intensity value. +// +// Note: zero rainfall values explicitly entered into a rain file or +// time series are skipped over so that a proper accounting of +// wet and dry periods can be maintained. +// +{ + int k; // time series index + float vNext; // next rain volume (ft or m) + double rNext; // next rain intensity (in/hr or mm/hr) + + Gage[j].nextRainfall = 0.0; + do + { + if ( Gage[j].dataSource == RAIN_FILE ) + { + if ( Frain.file && Gage[j].currentFilePos < Gage[j].endFilePos ) + { + fseek(Frain.file, Gage[j].currentFilePos, SEEK_SET); + fread(&Gage[j].nextDate, sizeof(DateTime), 1, Frain.file); + fread(&vNext, sizeof(float), 1, Frain.file); + Gage[j].currentFilePos = ftell(Frain.file); + rNext = convertRainfall(j, (double)vNext); + } + else return 0; + } + + else + { + k = Gage[j].tSeries; + if ( k >= 0 ) + { + if ( !table_getNextEntry(&Tseries[k], + &Gage[j].nextDate, &rNext) ) return 0; + rNext = convertRainfall(j, rNext); + } + else return 0; + } + } while (rNext == 0.0); + Gage[j].nextRainfall = rNext; + return 1; +} + +//============================================================================= + +double convertRainfall(int j, double r) +// +// Input: j = rain gage index +// r = rainfall value (user units) +// Output: returns rainfall intensity (user units) +// Purpose: converts rainfall value to an intensity (depth per hour). +// +{ + double r1; + switch ( Gage[j].rainType ) + { + case RAINFALL_INTENSITY: + r1 = r; + break; + + case RAINFALL_VOLUME: + r1 = r / Gage[j].rainInterval * 3600.0; + break; + + case CUMULATIVE_RAINFALL: + if ( r < Gage[j].rainAccum ) + r1 = r / Gage[j].rainInterval * 3600.0; + else r1 = (r - Gage[j].rainAccum) / Gage[j].rainInterval * 3600.0; + Gage[j].rainAccum = r; + break; + + default: r1 = r; + } + return r1 * Gage[j].unitsFactor * Adjust.rainFactor; +} + +//============================================================================= diff --git a/src/globals.h b/src/globals.h new file mode 100644 index 000000000..70f2431aa --- /dev/null +++ b/src/globals.h @@ -0,0 +1,172 @@ +//----------------------------------------------------------------------------- +// globals.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Global Variables +// +// Update History +// ============== +// Build 5.1.004: +// - Ignore RDII option added. +// Build 5.1.007: +// - Monthly climate variable adjustments added. +// Build 5.1.008: +// - Number of parallel threads for dynamic wave routing added. +// - Minimum dynamic wave routing variable time step added. +// Build 5.1.011: +// - Changed WarningCode to Warnings (# warnings issued) +// - Added error message text as a variable. +// - Added elapsed simulation time (in decimal days) variable. +// - Added variables associated with detailed routing events. +// Build 5.1.012: +// - InSteadyState variable made local to routing_execute in routing.c. +// Build 5.1.013: +// - CrownCutoff and RuleStep added as analysis option variables. +// Build 5.1.015: +// - Fixes bug in summary statistics when Report Start date > Start Date. +// Build 5.2.0: +// - Support for relative file names added. +//----------------------------------------------------------------------------- + +#ifndef GLOBALS_H +#define GLOBALS_H + + +EXTERN TFile + Finp, // Input file + Fout, // Output file + Frpt, // Report file + Fclimate, // Climate file + Frain, // Rainfall file + Frunoff, // Runoff file + Frdii, // RDII inflow file + Fhotstart1, // Hot start input file + Fhotstart2, // Hot start output file + Finflows, // Inflows routing file + Foutflows; // Outflows routing file + +EXTERN long + Nperiods, // Number of reporting periods + TotalStepCount, // Total routing steps used + ReportStepCount, // Reporting routing steps used + NonConvergeCount; // Number of non-converging steps + +EXTERN char + Msg[MAXMSG+1], // Text of output message + ErrorMsg[MAXMSG+1], // Text of error message + Title[MAXTITLE][MAXMSG+1],// Project title + TempDir[MAXFNAME+1], // Temporary file directory + InpDir[MAXFNAME+1]; // Input file directory + +EXTERN TRptFlags + RptFlags; // Reporting options + +EXTERN int + Nobjects[MAX_OBJ_TYPES], // Number of each object type + Nnodes[MAX_NODE_TYPES], // Number of each node sub-type + Nlinks[MAX_LINK_TYPES], // Number of each link sub-type + UnitSystem, // Unit system + FlowUnits, // Flow units + InfilModel, // Infiltration method + RouteModel, // Flow routing method + ForceMainEqn, // Flow equation for force mains + LinkOffsets, // Link offset convention + SurchargeMethod, // EXTRAN or SLOT method + AllowPonding, // Allow water to pond at nodes + InertDamping, // Degree of inertial damping + NormalFlowLtd, // Normal flow limited + SlopeWeighting, // Use slope weighting + Compatibility, // SWMM 5/3/4 compatibility + SkipSteadyState, // Skip over steady state periods + IgnoreRainfall, // Ignore rainfall/runoff + IgnoreRDII, // Ignore RDII + IgnoreSnowmelt, // Ignore snowmelt + IgnoreGwater, // Ignore groundwater + IgnoreRouting, // Ignore flow routing + IgnoreQuality, // Ignore water quality + ErrorCode, // Error code number + Warnings, // Number of warning messages + WetStep, // Runoff wet time step (sec) + DryStep, // Runoff dry time step (sec) + ReportStep, // Reporting time step (sec) + RuleStep, // Rule evaluation time step (sec) + SweepStart, // Day of year when sweeping starts + SweepEnd, // Day of year when sweeping ends + MaxTrials, // Max. trials for DW routing + NumThreads, // Number of parallel threads used + NumEvents; // Number of detailed events + +EXTERN double + RouteStep, // Routing time step (sec) + MinRouteStep, // Minimum variable time step (sec) + LengtheningStep, // Time step for lengthening (sec) + StartDryDays, // Antecedent dry days + CourantFactor, // Courant time step factor + MinSurfArea, // Minimum nodal surface area + MinSlope, // Minimum conduit slope + RunoffError, // Runoff continuity error + GwaterError, // Groundwater continuity error + FlowError, // Flow routing error + QualError, // Quality routing error + HeadTol, // DW routing head tolerance (ft) + SysFlowTol, // Tolerance for steady system flow + LatFlowTol, // Tolerance for steady nodal inflow + CrownCutoff; // Fractional pipe crown cutoff + +EXTERN DateTime + StartDate, // Starting date + StartTime, // Starting time + StartDateTime, // Starting Date+Time + EndDate, // Ending date + EndTime, // Ending time + EndDateTime, // Ending Date+Time + ReportStartDate, // Report start date + ReportStartTime, // Report start time + ReportStart; // Report start Date+Time + +EXTERN double + ReportTime, // Current reporting time (msec) + OldRunoffTime, // Previous runoff time (msec) + NewRunoffTime, // Current runoff time (msec) + OldRoutingTime, // Previous routing time (msec) + NewRoutingTime, // Current routing time (msec) + TotalDuration, // Simulation duration (msec) + ElapsedTime; // Current elapsed time (days) + +EXTERN TTemp Temp; // Temperature data +EXTERN TEvap Evap; // Evaporation data +EXTERN TWind Wind; // Wind speed data +EXTERN TSnow Snow; // Snow melt data +EXTERN TAdjust Adjust; // Climate adjustments + +EXTERN TSnowmelt* Snowmelt; // Array of snow melt objects +EXTERN TGage* Gage; // Array of rain gages +EXTERN TSubcatch* Subcatch; // Array of subcatchments +EXTERN TAquifer* Aquifer; // Array of groundwater aquifers +EXTERN TUnitHyd* UnitHyd; // Array of unit hydrographs +EXTERN TNode* Node; // Array of nodes +EXTERN TOutfall* Outfall; // Array of outfall nodes +EXTERN TDivider* Divider; // Array of divider nodes +EXTERN TStorage* Storage; // Array of storage nodes +EXTERN TLink* Link; // Array of links +EXTERN TConduit* Conduit; // Array of conduit links +EXTERN TPump* Pump; // Array of pump links +EXTERN TOrifice* Orifice; // Array of orifice links +EXTERN TWeir* Weir; // Array of weir links +EXTERN TOutlet* Outlet; // Array of outlet device links +EXTERN TPollut* Pollut; // Array of pollutants +EXTERN TLanduse* Landuse; // Array of landuses +EXTERN TPattern* Pattern; // Array of time patterns +EXTERN TTable* Curve; // Array of curve tables +EXTERN TTable* Tseries; // Array of time series tables +EXTERN TTransect* Transect; // Array of transect data +EXTERN TStreet* Street; // Array of defined Street cross-sections +EXTERN TShape* Shape; // Array of custom conduit shapes +EXTERN TEvent* Event; // Array of routing events + + +#endif //GLOBALS_H diff --git a/src/gwater.c b/src/gwater.c new file mode 100644 index 000000000..48db9de17 --- /dev/null +++ b/src/gwater.c @@ -0,0 +1,872 @@ +//----------------------------------------------------------------------------- +// gwater.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Groundwater functions. +// +// Update History +// ============== +// Build 5.1.007: +// - User-supplied function for deep GW seepage flow added. +// - New variable names for use in user-supplied GW flow equations added. +// Build 5.1.008: +// - More variable names for user-supplied GW flow equations added. +// - Subcatchment area made into a shared variable. +// - Evaporation loss initialized to 0. +// - Support for collecting GW statistics added. +// Build 5.1.010: +// - Unsaturated hydraulic conductivity added to GW flow equation variables. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" +#include "odesolve.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double GWTOL = 0.0001; // ODE solver tolerance +static const double XTOL = 0.001; // tolerance for moisture & depth + +enum GWstates {THETA, // moisture content of upper GW zone + LOWERDEPTH}; // depth of lower saturated GW zone + +enum GWvariables { + gwvHGW, // water table height (ft) + gwvHSW, // surface water height (ft) + gwvHCB, // channel bottom height (ft) + gwvHGS, // ground surface height (ft) + gwvKS, // sat. hyd. condutivity (ft/s) + gwvK, // unsat. hyd. conductivity (ft/s) + gwvTHETA, // upper zone moisture content + gwvPHI, // soil porosity + gwvFI, // surface infiltration (ft/s) + gwvFU, // uper zone percolation rate (ft/s) + gwvA, // subcatchment area (ft2) + gwvMAX}; + +// Names of GW variables that can be used in GW outflow expression +static char* GWVarWords[] = {"HGW", "HSW", "HCB", "HGS", "KS", "K", + "THETA", "PHI", "FI", "FU", "A", NULL}; + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +// NOTE: all flux rates are in ft/sec, all depths are in ft. +static double Area; // subcatchment area (ft2) +static double Infil; // infiltration rate from surface +static double MaxEvap; // max. evaporation rate +static double AvailEvap; // available evaporation rate +static double UpperEvap; // evaporation rate from upper GW zone +static double LowerEvap; // evaporation rate from lower GW zone +static double UpperPerc; // percolation rate from upper to lower zone +static double LowerLoss; // loss rate from lower GW zone +static double GWFlow; // flow rate from lower zone to conveyance node +static double MaxUpperPerc; // upper limit on UpperPerc +static double MaxGWFlowPos; // upper limit on GWFlow when its positve +static double MaxGWFlowNeg; // upper limit on GWFlow when its negative +static double FracPerv; // fraction of surface that is pervious +static double TotalDepth; // total depth of GW aquifer +static double Theta; // moisture content of upper zone +static double HydCon; // unsaturated hydraulic conductivity (ft/s) +static double Hgw; // ht. of saturated zone +static double Hstar; // ht. from aquifer bottom to node invert +static double Hsw; // ht. from aquifer bottom to water surface +static double Tstep; // current time step (sec) +static TAquifer A; // aquifer being analyzed +static TGroundwater* GW; // groundwater object being analyzed +static MathExpr* LatFlowExpr; // user-supplied lateral GW flow expression +static MathExpr* DeepFlowExpr; // user-supplied deep GW flow expression + +//----------------------------------------------------------------------------- +// External Functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// gwater_readAquiferParams (called by input_readLine) +// gwater_readGroundwaterParams (called by input_readLine) +// gwater_readFlowExpression (called by input_readLine) +// gwater_deleteFlowExpression (called by deleteObjects in project.c) +// gwater_validateAquifer (called by swmm_open) +// gwater_validate (called by subcatch_validate) +// gwater_initState (called by subcatch_initState) +// gwater_getVolume (called by massbal_open & massbal_getGwaterError) +// gwater_getGroundwater (called by getSubareaRunoff in subcatch.c) +// gwater_getState (called by saveRunoff in hotstart.c) +// gwater_setState (called by readRunoff in hotstart.c) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static void getDxDt(double t, double* x, double* dxdt); +static void getFluxes(double upperVolume, double lowerDepth); +static void getEvapRates(double theta, double upperDepth); +static double getUpperPerc(double theta, double upperDepth); +static double getGWFlow(double lowerDepth); +static void updateMassBal(double area, double tStep); + +// Used to process custom GW outflow equations +static int getVariableIndex(char* s); +static double getVariableValue(int varIndex); + +//============================================================================= + +int gwater_readAquiferParams(int j, char* tok[], int ntoks) +// +// Input: j = aquifer index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error message +// Purpose: reads aquifer parameter values from line of input data +// +// Data line contains following parameters: +// ID, porosity, wiltingPoint, fieldCapacity, conductivity, +// conductSlope, tensionSlope, upperEvapFraction, lowerEvapDepth, +// gwRecession, bottomElev, waterTableElev, upperMoisture +// (evapPattern) +// +{ + int i, p; + double x[12]; + char *id; + + // --- check that aquifer exists + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(AQUIFER, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + + // --- read remaining tokens as numbers + for (i = 0; i < 11; i++) x[i] = 0.0; + for (i = 1; i < 13; i++) + { + if ( ! getDouble(tok[i], &x[i-1]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + + // --- read upper evap pattern if present + p = -1; + if ( ntoks > 13 ) + { + p = project_findObject(TIMEPATTERN, tok[13]); + if ( p < 0 ) return error_setInpError(ERR_NAME, tok[13]); + } + + // --- assign parameters to aquifer object + Aquifer[j].ID = id; + Aquifer[j].porosity = x[0]; + Aquifer[j].wiltingPoint = x[1]; + Aquifer[j].fieldCapacity = x[2]; + Aquifer[j].conductivity = x[3] / UCF(RAINFALL); + Aquifer[j].conductSlope = x[4]; + Aquifer[j].tensionSlope = x[5] / UCF(LENGTH); + Aquifer[j].upperEvapFrac = x[6]; + Aquifer[j].lowerEvapDepth = x[7] / UCF(LENGTH); + Aquifer[j].lowerLossCoeff = x[8] / UCF(RAINFALL); + Aquifer[j].bottomElev = x[9] / UCF(LENGTH); + Aquifer[j].waterTableElev = x[10] / UCF(LENGTH); + Aquifer[j].upperMoisture = x[11]; + Aquifer[j].upperEvapPat = p; + return 0; +} + +//============================================================================= + +int gwater_readGroundwaterParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads groundwater inflow parameters for a subcatchment from +// a line of input data. +// +// Data format is: +// subcatch aquifer node surfElev a1 b1 a2 b2 a3 fixedDepth + +// (nodeElev bottomElev waterTableElev upperMoisture ) +// +{ + int i, j, k, m, n; + double x[11]; + TGroundwater* gw; + + // --- check that specified subcatchment, aquifer & node exist + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + j = project_findObject(SUBCATCH, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- check for enough tokens + if ( ntoks < 11 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that specified aquifer and node exists + k = project_findObject(AQUIFER, tok[1]); + if ( k < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n = project_findObject(NODE, tok[2]); + if ( n < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // -- read in the flow parameters + for ( i = 0; i < 7; i++ ) + { + if ( ! getDouble(tok[i+3], &x[i]) ) + return error_setInpError(ERR_NUMBER, tok[i+3]); + } + + // --- read in optional depth parameters + for ( i = 7; i < 11; i++) + { + x[i] = MISSING; + m = i + 3; + if ( ntoks > m && *tok[m] != '*' ) + { + if (! getDouble(tok[m], &x[i]) ) + return error_setInpError(ERR_NUMBER, tok[m]); + if ( i < 10 ) x[i] /= UCF(LENGTH); + } + } + + // --- create a groundwater flow object + if ( !Subcatch[j].groundwater ) + { + gw = (TGroundwater *) malloc(sizeof(TGroundwater)); + if ( !gw ) return error_setInpError(ERR_MEMORY, ""); + Subcatch[j].groundwater = gw; + } + else gw = Subcatch[j].groundwater; + + // --- populate the groundwater flow object with its parameters + gw->aquifer = k; + gw->node = n; + gw->surfElev = x[0] / UCF(LENGTH); + gw->a1 = x[1]; + gw->b1 = x[2]; + gw->a2 = x[3]; + gw->b2 = x[4]; + gw->a3 = x[5]; + gw->fixedDepth = x[6] / UCF(LENGTH); + gw->nodeElev = x[7]; //already converted to ft. + gw->bottomElev = x[8]; + gw->waterTableElev = x[9]; + gw->upperMoisture = x[10]; + return 0; +} + +//============================================================================= + +int gwater_readFlowExpression(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads mathematical expression for lateral or deep groundwater +// flow for a subcatchment from a line of input data. +// +// Format is: subcatch LATERAL/DEEP +// where subcatch is the ID of the subcatchment, LATERAL is for lateral +// GW flow, DEEP is for deep GW flow and is any well-formed math +// expression. +// +{ + int i, j, k; + char exprStr[MAXLINE+1]; + MathExpr* expr; + + // --- return if too few tokens + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that subcatchment exists + j = project_findObject(SUBCATCH, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- check if expression is for lateral or deep GW flow + k = 1; + if ( match(tok[1], "LAT") ) k = 1; + else if ( match(tok[1], "DEEP") ) k = 2; + else return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- concatenate remaining tokens into a single string + sstrncpy(exprStr, tok[2], MAXLINE); + for ( i = 3; i < ntoks; i++) + { + sstrcat(exprStr, " ", MAXLINE+1); + sstrcat(exprStr, tok[i], MAXLINE+1); + } + + // --- delete any previous flow eqn. + if ( k == 1 ) mathexpr_delete(Subcatch[j].gwLatFlowExpr); + else mathexpr_delete(Subcatch[j].gwDeepFlowExpr); + + // --- create a parsed expression tree from the string expr + // (getVariableIndex is the function that converts a GW + // variable's name into an index number) + expr = mathexpr_create(exprStr, getVariableIndex); + if ( expr == NULL ) return error_setInpError(ERR_MATH_EXPR, ""); + + // --- save expression tree with the subcatchment + if ( k == 1 ) Subcatch[j].gwLatFlowExpr = expr; + else Subcatch[j].gwDeepFlowExpr = expr; + return 0; +} + +//============================================================================= + +void gwater_deleteFlowExpression(int j) +// +// Input: j = subcatchment index +// Output: none +// Purpose: deletes a subcatchment's custom groundwater flow expressions. +// +{ + mathexpr_delete(Subcatch[j].gwLatFlowExpr); + mathexpr_delete(Subcatch[j].gwDeepFlowExpr); +} + +//============================================================================= + +void gwater_validateAquifer(int j) +// +// Input: j = aquifer index +// Output: none +// Purpose: validates groundwater aquifer properties . +// +{ + int p; + + if ( Aquifer[j].porosity <= 0.0 + || Aquifer[j].fieldCapacity >= Aquifer[j].porosity + || Aquifer[j].wiltingPoint >= Aquifer[j].fieldCapacity + || Aquifer[j].conductivity <= 0.0 + || Aquifer[j].conductSlope < 0.0 + || Aquifer[j].tensionSlope < 0.0 + || Aquifer[j].upperEvapFrac < 0.0 + || Aquifer[j].lowerEvapDepth < 0.0 + || Aquifer[j].waterTableElev < Aquifer[j].bottomElev + || Aquifer[j].upperMoisture > Aquifer[j].porosity + || Aquifer[j].upperMoisture < Aquifer[j].wiltingPoint ) + report_writeErrorMsg(ERR_AQUIFER_PARAMS, Aquifer[j].ID); + + p = Aquifer[j].upperEvapPat; + if ( p >= 0 && Pattern[p].type != MONTHLY_PATTERN ) + { + report_writeErrorMsg(ERR_AQUIFER_PARAMS, Aquifer[j].ID); + } +} + +//============================================================================= + +void gwater_validate(int j) +{ + TAquifer a; // Aquifer data structure + TGroundwater* gw; // Groundwater data structure + + gw = Subcatch[j].groundwater; + if ( gw ) + { + a = Aquifer[gw->aquifer]; + + // ... use aquifer values for missing groundwater parameters + if ( gw->bottomElev == MISSING ) gw->bottomElev = a.bottomElev; + if ( gw->waterTableElev == MISSING ) gw->waterTableElev = a.waterTableElev; + if ( gw->upperMoisture == MISSING ) gw->upperMoisture = a.upperMoisture; + + // ... ground elevation can't be below water table elevation + if ( gw->surfElev < gw->waterTableElev ) + report_writeErrorMsg(ERR_GROUND_ELEV, Subcatch[j].ID); + } +} + +//============================================================================= + +void gwater_initState(int j) +// +// Input: j = subcatchment index +// Output: none +// Purpose: initializes state of subcatchment's groundwater. +// +{ + TAquifer a; // Aquifer data structure + TGroundwater* gw; // Groundwater data structure + + gw = Subcatch[j].groundwater; + if ( gw ) + { + a = Aquifer[gw->aquifer]; + + // ... initial moisture content + gw->theta = gw->upperMoisture; + if ( gw->theta >= a.porosity ) + { + gw->theta = a.porosity - XTOL; + } + + // ... initial depth of lower (saturated) zone + gw->lowerDepth = gw->waterTableElev - gw->bottomElev; + if ( gw->lowerDepth >= gw->surfElev - gw->bottomElev ) + { + gw->lowerDepth = gw->surfElev - gw->bottomElev - XTOL; + } + + // ... initial lateral groundwater outflow + gw->oldFlow = 0.0; + gw->newFlow = 0.0; + gw->evapLoss = 0.0; + + // ... initial available infiltration volume into upper zone + gw->maxInfilVol = (gw->surfElev - gw->waterTableElev) * + (a.porosity - gw->theta) / + subcatch_getFracPerv(j); + } +} + +//============================================================================= + +void gwater_getState(int j, double x[]) +// +// Input: j = subcatchment index +// Output: x[] = array of groundwater state variables +// Purpose: retrieves state of subcatchment's groundwater. +// +{ + TGroundwater* gw = Subcatch[j].groundwater; + x[0] = gw->theta; + x[1] = gw->bottomElev + gw->lowerDepth; + x[2] = gw->newFlow; + x[3] = gw->maxInfilVol; +} + +//============================================================================= + +void gwater_setState(int j, double x[]) +// +// Input: j = subcatchment index +// x[] = array of groundwater state variables +// Purpose: assigns values to a subcatchment's groundwater state. +// +{ + TGroundwater* gw = Subcatch[j].groundwater; + if ( gw == NULL ) return; + gw->theta = x[0]; + gw->lowerDepth = x[1] - gw->bottomElev; + gw->oldFlow = x[2]; + if ( x[3] != MISSING ) gw->maxInfilVol = x[3]; +} + +//============================================================================= + +double gwater_getVolume(int j) +// +// Input: j = subcatchment index +// Output: returns total volume of groundwater in ft/ft2 +// Purpose: finds volume of groundwater stored in upper & lower zones +// +{ + TAquifer a; + TGroundwater* gw; + double upperDepth; + gw = Subcatch[j].groundwater; + if ( gw == NULL ) return 0.0; + a = Aquifer[gw->aquifer]; + upperDepth = gw->surfElev - gw->bottomElev - gw->lowerDepth; + return (upperDepth * gw->theta) + (gw->lowerDepth * a.porosity); +} + +//============================================================================= + +void gwater_getGroundwater(int j, double evap, double infil, double tStep) +// +// Purpose: computes groundwater flow from subcatchment during current time step. +// Input: j = subcatchment index +// evap = pervious surface evaporation volume consumed (ft3) +// infil = surface infiltration volume (ft3) +// tStep = time step (sec) +// Output: none +// +{ + int n; // node exchanging groundwater + double x[2]; // upper moisture content & lower depth + double vUpper; // upper vol. available for percolation + double nodeFlow; // max. possible GW flow from node + + // --- save subcatchment's groundwater and aquifer objects to + // shared variables + GW = Subcatch[j].groundwater; + if ( GW == NULL ) return; + LatFlowExpr = Subcatch[j].gwLatFlowExpr; + DeepFlowExpr = Subcatch[j].gwDeepFlowExpr; + A = Aquifer[GW->aquifer]; + + // --- get fraction of total area that is pervious + FracPerv = subcatch_getFracPerv(j); + if ( FracPerv <= 0.0 ) return; + Area = Subcatch[j].area; + + // --- convert infiltration volume (ft3) to equivalent rate + // over entire GW (subcatchment) area + infil = infil / Area / tStep; + Infil = infil; + Tstep = tStep; + + // --- convert pervious surface evaporation already exerted (ft3) + // to equivalent rate over entire GW (subcatchment) area + evap = evap / Area / tStep; + + // --- convert max. surface evap rate (ft/sec) to a rate + // that applies to GW evap (GW evap can only occur + // through the pervious land surface area) + MaxEvap = Evap.rate * FracPerv; + + // --- available subsurface evaporation is difference between max. + // rate and pervious surface evap already exerted + AvailEvap = MAX((MaxEvap - evap), 0.0); + + // --- save total depth & outlet node properties to shared variables + TotalDepth = GW->surfElev - GW->bottomElev; + if ( TotalDepth <= 0.0 ) return; + n = GW->node; + + // --- establish min. water table height above aquifer bottom at which + // GW flow can occur (override node's invert if a value was provided + // in the GW object) + if ( GW->nodeElev != MISSING ) Hstar = GW->nodeElev - GW->bottomElev; + else Hstar = Node[n].invertElev - GW->bottomElev; + + // --- establish surface water height (relative to aquifer bottom) + // for drainage system node connected to the GW aquifer + if ( GW->fixedDepth > 0.0 ) + { + Hsw = GW->fixedDepth + Node[n].invertElev - GW->bottomElev; + } + else Hsw = Node[n].newDepth + Node[n].invertElev - GW->bottomElev; + + // --- store state variables (upper zone moisture content, lower zone + // depth) in work vector x + x[THETA] = GW->theta; + x[LOWERDEPTH] = GW->lowerDepth; + + // --- set limit on percolation rate from upper to lower GW zone + vUpper = (TotalDepth - x[LOWERDEPTH]) * (x[THETA] - A.fieldCapacity); + vUpper = MAX(0.0, vUpper); + MaxUpperPerc = vUpper / tStep; + + // --- set limit on GW flow out of aquifer based on volume of lower zone + MaxGWFlowPos = x[LOWERDEPTH]*A.porosity / tStep; + + // --- set limit on GW flow into aquifer from drainage system node + // based on min. of capacity of upper zone and drainage system + // inflow to the node + MaxGWFlowNeg = (TotalDepth - x[LOWERDEPTH]) * (A.porosity - x[THETA]) + / tStep; + nodeFlow = (Node[n].inflow + Node[n].newVolume/tStep) / Area; + MaxGWFlowNeg = -MIN(MaxGWFlowNeg, nodeFlow); + + // --- integrate eqns. for d(Theta)/dt and d(LowerDepth)/dt + // NOTE: ODE solver must have been initialized previously + odesolve_integrate(x, 2, 0, tStep, GWTOL, tStep, getDxDt); + + // --- keep state variables within allowable bounds + x[THETA] = MAX(x[THETA], A.wiltingPoint); + if ( x[THETA] >= A.porosity ) + { + x[THETA] = A.porosity - XTOL; + x[LOWERDEPTH] = TotalDepth - XTOL; + } + x[LOWERDEPTH] = MAX(x[LOWERDEPTH], 0.0); + if ( x[LOWERDEPTH] >= TotalDepth ) + { + x[LOWERDEPTH] = TotalDepth - XTOL; + } + + // --- save new values of state values + GW->theta = x[THETA]; + GW->lowerDepth = x[LOWERDEPTH]; + getFluxes(GW->theta, GW->lowerDepth); + GW->oldFlow = GW->newFlow; + GW->newFlow = GWFlow; + GW->evapLoss = UpperEvap + LowerEvap; + + //--- find max. infiltration volume (as depth over + // the pervious portion of the subcatchment) + // that upper zone can support in next time step + GW->maxInfilVol = (TotalDepth - x[LOWERDEPTH]) * + (A.porosity - x[THETA]) / FracPerv; + + // --- update GW mass balance + updateMassBal(Area, tStep); + + // --- update GW statistics + stats_updateGwaterStats(j, infil, GW->evapLoss, GWFlow, LowerLoss, + GW->theta, GW->lowerDepth + GW->bottomElev, tStep); +} + +//============================================================================= + +void updateMassBal(double area, double tStep) +// +// Input: area = subcatchment area (ft2) +// tStep = time step (sec) +// Output: none +// Purpose: updates GW mass balance with volumes of water fluxes. +// +{ + double vInfil; // infiltration volume + double vUpperEvap; // upper zone evap. volume + double vLowerEvap; // lower zone evap. volume + double vLowerPerc; // lower zone deep perc. volume + double vGwater; // volume of exchanged groundwater + double ft2sec = area * tStep; + + vInfil = Infil * ft2sec; + vUpperEvap = UpperEvap * ft2sec; + vLowerEvap = LowerEvap * ft2sec; + vLowerPerc = LowerLoss * ft2sec; + vGwater = 0.5 * (GW->oldFlow + GW->newFlow) * ft2sec; + massbal_updateGwaterTotals(vInfil, vUpperEvap, vLowerEvap, vLowerPerc, + vGwater); +} + +//============================================================================= + +void getFluxes(double theta, double lowerDepth) +// +// Input: upperVolume = vol. depth of upper zone (ft) +// upperDepth = depth of upper zone (ft) +// Output: none +// Purpose: computes water fluxes into/out of upper/lower GW zones. +// +{ + double upperDepth; + + // --- find upper zone depth + lowerDepth = MAX(lowerDepth, 0.0); + lowerDepth = MIN(lowerDepth, TotalDepth); + upperDepth = TotalDepth - lowerDepth; + + // --- save lower depth and theta to global variables + Hgw = lowerDepth; + Theta = theta; + + // --- find evaporation rate from both zones + getEvapRates(theta, upperDepth); + + // --- find percolation rate from upper to lower zone + UpperPerc = getUpperPerc(theta, upperDepth); + UpperPerc = MIN(UpperPerc, MaxUpperPerc); + + // --- find loss rate to deep GW + if ( DeepFlowExpr != NULL ) + LowerLoss = mathexpr_eval(DeepFlowExpr, getVariableValue) / + UCF(RAINFALL); + else + LowerLoss = A.lowerLossCoeff * lowerDepth / TotalDepth; + LowerLoss = MIN(LowerLoss, lowerDepth/Tstep); + + // --- find GW flow rate from lower zone to drainage system node + GWFlow = getGWFlow(lowerDepth); + if ( LatFlowExpr != NULL ) + { + GWFlow += mathexpr_eval(LatFlowExpr, getVariableValue) / UCF(GWFLOW); + } + if ( GWFlow >= 0.0 ) GWFlow = MIN(GWFlow, MaxGWFlowPos); + else GWFlow = MAX(GWFlow, MaxGWFlowNeg); +} + +//============================================================================= + +void getDxDt(double t, double* x, double* dxdt) +// +// Input: t = current time (not used) +// x = array of state variables +// Output: dxdt = array of time derivatives of state variables +// Purpose: computes time derivatives of upper moisture content +// and lower depth. +// +{ + double qUpper; // inflow - outflow for upper zone (ft/sec) + double qLower; // inflow - outflow for lower zone (ft/sec) + double denom; + + getFluxes(x[THETA], x[LOWERDEPTH]); + qUpper = Infil - UpperEvap - UpperPerc; + qLower = UpperPerc - LowerLoss - LowerEvap - GWFlow; + + // --- d(upper zone moisture)/dt = (net upper zone flow) / + // (upper zone depth) + denom = TotalDepth - x[LOWERDEPTH]; + if (denom > 0.0) + dxdt[THETA] = qUpper / denom; + else + dxdt[THETA] = 0.0; + + // --- d(lower zone depth)/dt = (net lower zone flow) / + // (upper zone moisture deficit) + denom = A.porosity - x[THETA]; + if (denom > 0.0) + dxdt[LOWERDEPTH] = qLower / denom; + else + dxdt[LOWERDEPTH] = 0.0; +} + +//============================================================================= + +void getEvapRates(double theta, double upperDepth) +// +// Input: theta = moisture content of upper zone +// upperDepth = depth of upper zone (ft) +// Output: none +// Purpose: computes evapotranspiration out of upper & lower zones. +// +{ + int p, month; + double f; + double lowerFrac, upperFrac; + + // --- no GW evaporation when infiltration is occurring + UpperEvap = 0.0; + LowerEvap = 0.0; + if ( Infil > 0.0 ) return; + + // --- get monthly-adjusted upper zone evap fraction + upperFrac = A.upperEvapFrac; + f = 1.0; + p = A.upperEvapPat; + if ( p >= 0 ) + { + month = datetime_monthOfYear(getDateTime(NewRunoffTime)); + f = Pattern[p].factor[month-1]; + } + upperFrac *= f; + + // --- upper zone evaporation requires that soil moisture + // be above the wilting point + if ( theta > A.wiltingPoint ) + { + // --- actual evap is upper zone fraction applied to max. potential + // rate, limited by the available rate after any surface evap + UpperEvap = upperFrac * MaxEvap; + UpperEvap = MIN(UpperEvap, AvailEvap); + } + + // --- check if lower zone evaporation is possible + if ( A.lowerEvapDepth > 0.0 ) + { + // --- find the fraction of the lower evaporation depth that + // extends into the saturated lower zone + lowerFrac = (A.lowerEvapDepth - upperDepth) / A.lowerEvapDepth; + lowerFrac = MAX(0.0, lowerFrac); + lowerFrac = MIN(lowerFrac, 1.0); + + // --- make the lower zone evap rate proportional to this fraction + // and the evap not used in the upper zone + LowerEvap = lowerFrac * (1.0 - upperFrac) * MaxEvap; + LowerEvap = MIN(LowerEvap, (AvailEvap - UpperEvap)); + } +} + +//============================================================================= + +double getUpperPerc(double theta, double upperDepth) +// +// Input: theta = moisture content of upper zone +// upperDepth = depth of upper zone (ft) +// Output: returns percolation rate (ft/sec) +// Purpose: finds percolation rate from upper to lower zone. +// +{ + double delta; // unfilled water content of upper zone + double dhdz; // avg. change in head with depth + double hydcon; // unsaturated hydraulic conductivity + + // --- no perc. from upper zone if no depth or moisture content too low + if ( upperDepth <= 0.0 || theta <= A.fieldCapacity ) return 0.0; + + // --- compute hyd. conductivity as function of moisture content + delta = theta - A.porosity; + hydcon = A.conductivity * exp(delta * A.conductSlope); + + // --- compute integral of dh/dz term + delta = theta - A.fieldCapacity; + dhdz = 1.0 + A.tensionSlope * 2.0 * delta / upperDepth; + + // --- compute upper zone percolation rate + HydCon = hydcon; + return hydcon * dhdz; +} + +//============================================================================= + +double getGWFlow(double lowerDepth) +// +// Input: lowerDepth = depth of lower zone (ft) +// Output: returns groundwater flow rate (ft/sec) +// Purpose: finds groundwater outflow from lower saturated zone. +// +{ + double q, t1, t2, t3; + + // --- water table must be above Hstar for flow to occur + if ( lowerDepth <= Hstar ) return 0.0; + + // --- compute groundwater component of flow + if ( GW->b1 == 0.0 ) t1 = GW->a1; + else t1 = GW->a1 * pow( (lowerDepth - Hstar)*UCF(LENGTH), GW->b1); + + // --- compute surface water component of flow + if ( GW->b2 == 0.0 ) t2 = GW->a2; + else if (Hsw > Hstar) + { + t2 = GW->a2 * pow( (Hsw - Hstar)*UCF(LENGTH), GW->b2); + } + else t2 = 0.0; + + // --- compute groundwater/surface water interaction term + t3 = GW->a3 * lowerDepth * Hsw * UCF(LENGTH) * UCF(LENGTH); + + // --- compute total groundwater flow + q = (t1 - t2 + t3) / UCF(GWFLOW); + if ( q < 0.0 && GW->a3 != 0.0 ) q = 0.0; + return q; +} + +//============================================================================= + +int getVariableIndex(char* s) +// +// Input: s = name of a groundwater variable +// Output: returns index of groundwater variable +// Purpose: finds position of GW variable in list of GW variable names. +// +{ + int k; + + k = findmatch(s, GWVarWords); + if ( k >= 0 ) return k; + return -1; +} + +//============================================================================= + +double getVariableValue(int varIndex) +// +// Input: varIndex = index of a GW variable +// Output: returns current value of GW variable +// Purpose: finds current value of a GW variable. +// +{ + switch (varIndex) + { + case gwvHGW: return Hgw * UCF(LENGTH); + case gwvHSW: return Hsw * UCF(LENGTH); + case gwvHCB: return Hstar * UCF(LENGTH); + case gwvHGS: return TotalDepth * UCF(LENGTH); + case gwvKS: return A.conductivity * UCF(RAINFALL); + case gwvK: return HydCon * UCF(RAINFALL); + case gwvTHETA:return Theta; + case gwvPHI: return A.porosity; + case gwvFI: return Infil * UCF(RAINFALL); + case gwvFU: return UpperPerc * UCF(RAINFALL); + case gwvA: return Area * UCF(LANDAREA); + default: return 0.0; + } +} diff --git a/src/hash.c b/src/hash.c new file mode 100644 index 000000000..4a73e6ccd --- /dev/null +++ b/src/hash.c @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------------- +// hash.c +// +// Implementation of a simple Hash Table for string storage & retrieval +// CASE INSENSITIVE +// +// Written by L. Rossman +// Last Updated on 6/19/03 +// +// The hash table data structure (HTable) is defined in "hash.h". +// Interface Functions: +// HTcreate() - creates a hash table +// HTinsert() - inserts a string & its index value into a hash table +// HTfind() - retrieves the index value of a string from a table +// HTfree() - frees a hash table +//----------------------------------------------------------------------------- + +#include +#include +#include "hash.h" +#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) + +/* Case-insensitive comparison of strings s1 and s2 */ +int samestr(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); + return(0); +} /* End of samestr */ + +/* Use Fletcher's checksum to compute 2-byte hash of string */ +unsigned int hash(const char *str) +{ + unsigned int sum1= 0, check1; + unsigned long sum2= 0L; + while( '\0' != *str ) + { + sum1 += UCHAR(*str); + str++; + if ( 255 <= sum1 ) sum1 -= 255; + sum2 += sum1; + } + check1= sum2; + check1 %= 255; + check1= 255 - (sum1+check1) % 255; + sum1= 255 - (sum1+check1) % 255; + return( ( ( check1 << 8 ) | sum1 ) % HTMAXSIZE); +} + +HTtable *HTcreate() +{ + int i; + HTtable *ht = (HTtable *) calloc(HTMAXSIZE, sizeof(HTtable)); + if (ht != NULL) for (i=0; i= HTMAXSIZE ) return(0); + entry = (struct HTentry *) malloc(sizeof(struct HTentry)); + if (entry == NULL) return(0); + entry->key = key; + entry->data = data; + entry->next = ht[i]; + ht[i] = entry; + return(1); +} + +int HTfind(HTtable *ht, const char *key) +{ + unsigned int i = hash(key); + struct HTentry *entry; + if ( i >= HTMAXSIZE ) return(NOTFOUND); + entry = ht[i]; + while (entry != NULL) + { + if ( samestr(entry->key,key) ) return(entry->data); + entry = entry->next; + } + return(NOTFOUND); +} + +char *HTfindKey(HTtable *ht, const char *key) +{ + unsigned int i = hash(key); + struct HTentry *entry; + if ( i >= HTMAXSIZE ) return(NULL); + entry = ht[i]; + while (entry != NULL) + { + if ( samestr(entry->key,key) ) return(entry->key); + entry = entry->next; + } + return(NULL); +} + +void HTfree(HTtable *ht) +{ + struct HTentry *entry, + *nextentry; + int i; + for (i=0; inext; + free(entry); + entry = nextentry; + } + } + free(ht); +} diff --git a/src/hash.h b/src/hash.h new file mode 100644 index 000000000..ba42be00e --- /dev/null +++ b/src/hash.h @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// hash.h +// +// Header file for Hash Table module hash.c. +//----------------------------------------------------------------------------- + +#ifndef HASH_H +#define HASH_H + + +#define HTMAXSIZE 1999 +#define NOTFOUND -1 + +struct HTentry +{ + char *key; + int data; + struct HTentry *next; +}; + +typedef struct HTentry *HTtable; + +HTtable* HTcreate(void); +int HTinsert(HTtable *, char *, int); +int HTfind(HTtable *, const char *); +char* HTfindKey(HTtable *, const char *); +void HTfree(HTtable *); + + +#endif //HASH_H diff --git a/src/headers.h b/src/headers.h new file mode 100644 index 000000000..87139b060 --- /dev/null +++ b/src/headers.h @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------------- +// headers.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Header files included in most SWMM5 modules. +// +// DO NOT CHANGE THE ORDER OF THE #INCLUDE STATEMENTS +//----------------------------------------------------------------------------- +#include "macros.h" +#include "objects.h" +#define EXTERN extern +#include "globals.h" +#include "funcs.h" +#include "error.h" +#include "text.h" +#include "keywords.h" diff --git a/src/hotstart.c b/src/hotstart.c new file mode 100644 index 000000000..69f7abcc9 --- /dev/null +++ b/src/hotstart.c @@ -0,0 +1,544 @@ +//----------------------------------------------------------------------------- +// hotstart.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Hot Start file functions. +// +// A SWMM hot start file contains the state of a SWMM project after +// a simulation has been run, allowing it to be used to initialize +// a subsequent simulation that picks up where the previous run ended. +// +// An abridged version of the hot start file (version 2) is available +// that contains only variables that appear in the binary output file +// (groundwater upper moisture and water table elevation, node depth, +// lateral inflow, and quality, and link flow, depth, setting and quality). +// +// When reading a previously saved hot start file checks are made to +// insure the the current SWMM project has the same number of major +// components (subcatchments, land uses, nodes, links, and pollutants) +// and unit system as does the hot start file. No test is made to +// insure that these components are of the same sub-type and maintain +// the same order as when the hot start file was created. +// +// Update History +// ============== +// Build 5.1.008: +// - Storage node hydraulic residence time (HRT) was added to the file. +// - Link control settings are now applied when reading a hot start file. +// - Runoff read from file assigned to newRunoff property instead of oldRunoff. +// - Array indexing bug when reading snowpack state from file fixed. +// Build 5.1.011: +// - Link control setting bug when reading a hot start file fixed. +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Local Variables +//----------------------------------------------------------------------------- +static int fileVersion; + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// hotstart_open (called by swmm_start in swmm5.c) +// hotstart_close (called by swmm_end in swmm5.c) + +//----------------------------------------------------------------------------- +// Function declarations +//----------------------------------------------------------------------------- +static int openHotstartFile1(void); +static int openHotstartFile2(void); +static void readRunoff(void); +static void saveRunoff(void); +static void readRouting(void); +static void saveRouting(void); +static int readFloat(float *x, FILE* f); +static int readDouble(double* x, FILE* f); + +//============================================================================= + +int hotstart_open() +{ + // --- open hot start files + if ( !openHotstartFile1() ) return FALSE; //input hot start file + if ( !openHotstartFile2() ) return FALSE; //output hot start file + return TRUE; +} + +//============================================================================= + +void hotstart_close() +{ + if ( Fhotstart2.file ) + { + saveRunoff(); + saveRouting(); + fclose(Fhotstart2.file); + } +} + +//============================================================================= + +int openHotstartFile1() +// +// Input: none +// Output: none +// Purpose: opens a previously saved routing hotstart file. +// +{ + int nSubcatch; + int nNodes; + int nLinks; + int nPollut; + int nLandUses; + int flowUnits; + char fStamp[] = "SWMM5-HOTSTART"; + char fileStamp[] = "SWMM5-HOTSTART"; + char fStampx[] = "SWMM5-HOTSTARTx"; + char fileStamp2[] = "SWMM5-HOTSTART2"; + char fileStamp3[] = "SWMM5-HOTSTART3"; + char fileStamp4[] = "SWMM5-HOTSTART4"; + + // --- try to open the file + if ( Fhotstart1.mode != USE_FILE ) return TRUE; + if ( (Fhotstart1.file = fopen(Fhotstart1.name, "r+b")) == NULL) + { + report_writeErrorMsg(ERR_HOTSTART_FILE_OPEN, Fhotstart1.name); + return FALSE; + } + + // --- check that file contains proper header records + fread(fStampx, sizeof(char), strlen(fileStamp2), Fhotstart1.file); + if ( strcmp(fStampx, fileStamp4) == 0 ) fileVersion = 4; + else if ( strcmp(fStampx, fileStamp3) == 0 ) fileVersion = 3; + else if ( strcmp(fStampx, fileStamp2) == 0 ) fileVersion = 2; + else + { + rewind(Fhotstart1.file); + fread(fStamp, sizeof(char), strlen(fileStamp), Fhotstart1.file); + if ( strcmp(fStamp, fileStamp) != 0 ) + { + report_writeErrorMsg(ERR_HOTSTART_FILE_FORMAT, ""); + return FALSE; + } + fileVersion = 1; + } + + nSubcatch = -1; + nNodes = -1; + nLinks = -1; + nPollut = -1; + nLandUses = -1; + flowUnits = -1; + if ( fileVersion >= 2 ) + { + fread(&nSubcatch, sizeof(int), 1, Fhotstart1.file); + } + else nSubcatch = Nobjects[SUBCATCH]; + if ( fileVersion >= 3 ) + { + fread(&nLandUses, sizeof(int), 1, Fhotstart1.file); + } + else nLandUses = Nobjects[LANDUSE]; + fread(&nNodes, sizeof(int), 1, Fhotstart1.file); + fread(&nLinks, sizeof(int), 1, Fhotstart1.file); + fread(&nPollut, sizeof(int), 1, Fhotstart1.file); + fread(&flowUnits, sizeof(int), 1, Fhotstart1.file); + if ( nSubcatch != Nobjects[SUBCATCH] + || nLandUses != Nobjects[LANDUSE] + || nNodes != Nobjects[NODE] + || nLinks != Nobjects[LINK] + || nPollut != Nobjects[POLLUT] + || flowUnits != FlowUnits ) + { + report_writeErrorMsg(ERR_HOTSTART_FILE_FORMAT, ""); + return FALSE; + } + + // --- read contents of the file and close it + if ( fileVersion >= 3 ) readRunoff(); + readRouting(); + fclose(Fhotstart1.file); + if ( ErrorCode ) return FALSE; + else return TRUE; +} + +//============================================================================= + +int openHotstartFile2() +// +// Input: none +// Output: none +// Purpose: opens a new routing hotstart file to save results to. +// +{ + int nSubcatch; + int nLandUses; + int nNodes; + int nLinks; + int nPollut; + int flowUnits; + char fileStamp[] = "SWMM5-HOTSTART4"; + + // --- try to open file + if ( Fhotstart2.mode != SAVE_FILE ) return TRUE; + if ( (Fhotstart2.file = fopen(Fhotstart2.name, "w+b")) == NULL) + { + report_writeErrorMsg(ERR_HOTSTART_FILE_OPEN, Fhotstart2.name); + return FALSE; + } + + // --- write file stamp & number of objects to file + nSubcatch = Nobjects[SUBCATCH]; + nLandUses = Nobjects[LANDUSE]; + nNodes = Nobjects[NODE]; + nLinks = Nobjects[LINK]; + nPollut = Nobjects[POLLUT]; + flowUnits = FlowUnits; + fwrite(fileStamp, sizeof(char), strlen(fileStamp), Fhotstart2.file); + fwrite(&nSubcatch, sizeof(int), 1, Fhotstart2.file); + fwrite(&nLandUses, sizeof(int), 1, Fhotstart2.file); + fwrite(&nNodes, sizeof(int), 1, Fhotstart2.file); + fwrite(&nLinks, sizeof(int), 1, Fhotstart2.file); + fwrite(&nPollut, sizeof(int), 1, Fhotstart2.file); + fwrite(&flowUnits, sizeof(int), 1, Fhotstart2.file); + return TRUE; +} + +//============================================================================= + +void saveRouting() +// +// Input: none +// Output: none +// Purpose: saves current state of all nodes and links to hotstart file. +// +{ + int i, j; + float x[3]; + + for (i = 0; i < Nobjects[NODE]; i++) + { + x[0] = (float)Node[i].newDepth; + x[1] = (float)Node[i].newLatFlow; + fwrite(x, sizeof(float), 2, Fhotstart2.file); + + if ( Node[i].type == STORAGE ) + { + j = Node[i].subIndex; + x[0] = (float)Storage[j].hrt; + fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); + } + + for (j = 0; j < Nobjects[POLLUT]; j++) + { + x[0] = (float)Node[i].newQual[j]; + fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); + } + } + for (i = 0; i < Nobjects[LINK]; i++) + { + x[0] = (float)Link[i].newFlow; + x[1] = (float)Link[i].newDepth; + x[2] = (float)Link[i].setting; + fwrite(x, sizeof(float), 3, Fhotstart2.file); + for (j = 0; j < Nobjects[POLLUT]; j++) + { + x[0] = (float)Link[i].newQual[j]; + fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); + } + } +} + +//============================================================================= + +void readRouting() +// +// Input: none +// Output: none +// Purpose: reads initial state of all nodes, links and groundwater objects +// from hotstart file. +// +{ + int i, j; + float x; + double xgw[4]; + FILE* f = Fhotstart1.file; + + // --- for file format 2, assign GW moisture content and lower depth + if ( fileVersion == 2 ) + { + // --- flow and available upper zone volume not used + xgw[2] = 0.0; + xgw[3] = MISSING; + for (i = 0; i < Nobjects[SUBCATCH]; i++) + { + // --- read moisture content and water table elevation as floats + if ( !readFloat(&x, f) ) return; + xgw[0] = x; + if ( !readFloat(&x, f) ) return; + xgw[1] = x; + + // --- set GW state + if ( Subcatch[i].groundwater != NULL ) gwater_setState(i, xgw); + } + } + + // --- read node states + for (i = 0; i < Nobjects[NODE]; i++) + { + if ( !readFloat(&x, f) ) return; + Node[i].newDepth = x; + if ( !readFloat(&x, f) ) return; + Node[i].newLatFlow = x; + + if ( fileVersion >= 4 && Node[i].type == STORAGE ) + { + if ( !readFloat(&x, f) ) return; + j = Node[i].subIndex; + Storage[j].hrt = x; + } + + for (j = 0; j < Nobjects[POLLUT]; j++) + { + if ( !readFloat(&x, f) ) return; + Node[i].newQual[j] = x; + } + + // --- read in zeros here for backwards compatibility + if ( fileVersion <= 2 ) + { + for (j = 0; j < Nobjects[POLLUT]; j++) + { + if ( !readFloat(&x, f) ) return; + } + } + } + + // --- read link states + for (i = 0; i < Nobjects[LINK]; i++) + { + if ( !readFloat(&x, f) ) return; + Link[i].newFlow = x; + if ( !readFloat(&x, f) ) return; + Link[i].newDepth = x; + if ( !readFloat(&x, f) ) return; + Link[i].setting = x; + + // --- set link's target setting to saved setting + Link[i].targetSetting = x; + link_setTargetSetting(i); + link_setSetting(i, 0.0); + + for (j = 0; j < Nobjects[POLLUT]; j++) + { + if ( !readFloat(&x, f) ) return; + Link[i].newQual[j] = x; + } + + } +} + +//============================================================================= + +void saveRunoff(void) +// +// Input: none +// Output: none +// Purpose: saves current state of all subcatchments to hotstart file. +// +{ + int i, j, k; + double x[6]; + FILE* f = Fhotstart2.file; + + for (i = 0; i < Nobjects[SUBCATCH]; i++) + { + // Ponded depths for each sub-area & total runoff (4 elements) + for (j = 0; j < 3; j++) x[j] = Subcatch[i].subArea[j].depth; + x[3] = Subcatch[i].newRunoff; + fwrite(x, sizeof(double), 4, f); + + // Infiltration state (max. of 6 elements) + for (j=0; j<6; j++) x[j] = 0.0; + infil_getState(i, x); + fwrite(x, sizeof(double), 6, f); + + // Groundwater state (4 elements) + if ( Subcatch[i].groundwater != NULL ) + { + gwater_getState(i, x); + fwrite(x, sizeof(double), 4, f); + } + + // Snowpack state (5 elements for each of 3 snow surfaces) + if ( Subcatch[i].snowpack != NULL ) + { + for (j=0; j<3; j++) + { + snow_getState(i, j, x); + fwrite(x, sizeof(double), 5, f); + } + } + + // Water quality + if ( Nobjects[POLLUT] > 0 ) + { + // Runoff quality + for (j=0; j 0 ) + { + // Runoff quality + for (j=0; j +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Imported variables +//----------------------------------------------------------------------------- +extern double Qcf[]; // flow units conversion factors + // (see swmm5.c) + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +static int IfaceFlowUnits; // flow units for routing interface file +static int IfaceStep; // interface file time step (sec) +static int NumIfacePolluts; // number of pollutants in interface file +static int* IfacePolluts; // indexes of interface file pollutants +static int NumIfaceNodes; // number of nodes on interface file +static int* IfaceNodes; // indexes of nodes on interface file +static double** OldIfaceValues; // interface flows & WQ at previous time +static double** NewIfaceValues; // interface flows & WQ at next time +static double IfaceFrac; // fraction of interface file time step +static DateTime OldIfaceDate; // previous date of interface values +static DateTime NewIfaceDate; // next date of interface values + +//----------------------------------------------------------------------------- +// External Functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// iface_readFileParams (called by input_readLine) +// iface_openRoutingFiles (called by routing_open) +// iface_closeRoutingFiles (called by routing_close) +// iface_getNumIfaceNodes (called by addIfaceInflows in routing.c) +// iface_getIfaceNode (called by addIfaceInflows in routing.c) +// iface_getIfaceFlow (called by addIfaceInflows in routing.c) +// iface_getIfaceQual (called by addIfaceInflows in routing.c) +// iface_saveOutletResults (called by output_saveResults) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static void openFileForOutput(void); +static void openFileForInput(void); +static int getIfaceFilePolluts(void); +static int getIfaceFileNodes(void); +static void setOldIfaceValues(void); +static void readNewIfaceValues(void); +static int isOutletNode(int node); + +//============================================================================= + +int iface_readFileParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads interface file information from a line of input data. +// +// Data format is: +// USE/SAVE FileType FileName +// +{ + char k; + int j; + char fname[MAXFNAME+1]; + + // --- determine file disposition and type + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + k = (char)findmatch(tok[0], FileModeWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); + j = findmatch(tok[1], FileTypeWords); + if ( j < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + if ( ntoks < 3 ) return 0; + sstrncpy(fname, tok[2], MAXFNAME); + + // --- process file name + switch ( j ) + { + case RAINFALL_FILE: + Frain.mode = k; + sstrncpy(Frain.name, addAbsolutePath(fname), MAXFNAME); + break; + + case RUNOFF_FILE: + Frunoff.mode = k; + sstrncpy(Frunoff.name, addAbsolutePath(fname), MAXFNAME); + break; + + case HOTSTART_FILE: + if ( k == USE_FILE ) + { + Fhotstart1.mode = k; + sstrncpy(Fhotstart1.name, addAbsolutePath(fname), MAXFNAME); + } + else if ( k == SAVE_FILE ) + { + Fhotstart2.mode = k; + sstrncpy(Fhotstart2.name, addAbsolutePath(fname), MAXFNAME); + } + break; + + case RDII_FILE: + Frdii.mode = k; + sstrncpy(Frdii.name, fname, MAXFNAME); + break; + + case INFLOWS_FILE: + if ( k != USE_FILE ) return error_setInpError(ERR_ITEMS, ""); + Finflows.mode = k; + sstrncpy(Finflows.name, addAbsolutePath(fname), MAXFNAME); + break; + + case OUTFLOWS_FILE: + if ( k != SAVE_FILE ) return error_setInpError(ERR_ITEMS, ""); + Foutflows.mode = k; + sstrncpy(Foutflows.name, addAbsolutePath(fname), MAXFNAME); + break; + } + return 0; +} + +//============================================================================= + +void iface_openRoutingFiles() +// +// Input: none +// Output: none +// Purpose: opens routing interface files. +// +{ + // --- initialize shared variables + NumIfacePolluts = 0; + IfacePolluts = NULL; + NumIfaceNodes = 0; + IfaceNodes = NULL; + OldIfaceValues = NULL; + NewIfaceValues = NULL; + + // --- check that inflows & outflows files are not the same + if ( Foutflows.mode != NO_FILE && Finflows.mode != NO_FILE ) + { + if ( strcomp(Foutflows.name, Finflows.name) ) + { + report_writeErrorMsg(ERR_ROUTING_FILE_NAMES, ""); + return; + } + } + + // --- open the file for reading or writing + if ( Foutflows.mode == SAVE_FILE ) openFileForOutput(); + if ( Finflows.mode == USE_FILE ) openFileForInput(); +} + +//============================================================================= + +void iface_closeRoutingFiles() +// +// Input: none +// Output: none +// Purpose: closes routing interface files. +// +{ + FREE(IfacePolluts); + FREE(IfaceNodes); + if ( OldIfaceValues != NULL ) project_freeMatrix(OldIfaceValues); + if ( NewIfaceValues != NULL ) project_freeMatrix(NewIfaceValues); + if ( Finflows.file ) fclose(Finflows.file); + if ( Foutflows.file ) fclose(Foutflows.file); +} + +//============================================================================= + +int iface_getNumIfaceNodes(DateTime currentDate) +// +// Input: currentDate = current date/time +// Output: returns number of interface nodes if data exists or +// 0 otherwise +// Purpose: reads inflow data from interface file at current date. +// +{ + // --- return 0 if file begins after current date + if ( OldIfaceDate > currentDate ) return 0; + + // --- keep updating new interface values until current date bracketed + while ( NewIfaceDate < currentDate && NewIfaceDate != NO_DATE ) + { + setOldIfaceValues(); + readNewIfaceValues(); + } + + // --- return 0 if no data available + if ( NewIfaceDate == NO_DATE ) return 0; + + // --- find fraction current date is bewteen old & new interface dates + IfaceFrac = (currentDate - OldIfaceDate) / (NewIfaceDate - OldIfaceDate); + IfaceFrac = MAX(0.0, IfaceFrac); + IfaceFrac = MIN(IfaceFrac, 1.0); + + // --- return number of interface nodes + return NumIfaceNodes; +} + +//============================================================================= + +int iface_getIfaceNode(int index) +// +// Input: index = interface file node index +// Output: returns project node index +// Purpose: finds index of project node associated with interface node index +// +{ + if ( index >= 0 && index < NumIfaceNodes ) return IfaceNodes[index]; + else return -1; +} + +//============================================================================= + +double iface_getIfaceFlow(int index) +// +// Input: index = interface file node index +// Output: returns inflow to node +// Purpose: finds interface flow for particular node index. +// +{ + double q1, q2; + + if ( index >= 0 && index < NumIfaceNodes ) + { + // --- interpolate flow between old and new values + q1 = OldIfaceValues[index][0]; + q2 = NewIfaceValues[index][0]; + return (1.0 - IfaceFrac)*q1 + IfaceFrac*q2; + } + else return 0.0; +} + +//============================================================================= + +double iface_getIfaceQual(int index, int pollut) +// +// Input: index = index of node on interface file +// pollut = index of pollutant on interface file +// Output: returns inflow pollutant concentration +// Purpose: finds interface concentration for particular node index & pollutant. +// +{ + int j; + double c1, c2; + + if ( index >= 0 && index < NumIfaceNodes ) + { + // --- find index of pollut on interface file + j = IfacePolluts[pollut]; + if ( j < 0 ) return 0.0; + + // --- interpolate flow between old and new values + // (remember that 1st col. of values matrix is for flow) + c1 = OldIfaceValues[index][j+1]; + c2 = NewIfaceValues[index][j+1]; + return (1.0 - IfaceFrac)*c1 + IfaceFrac*c2; + } + else return 0.0; +} + +//============================================================================= + +void iface_saveOutletResults(DateTime reportDate, FILE* file) +// +// Input: reportDate = reporting date/time +// file = ptr. to interface file +// Output: none +// Purpose: saves system outflows to routing interface file. +// +{ + int i, p, yr, mon, day, hr, min, sec; + char theDate[26]; + datetime_decodeDate(reportDate, &yr, &mon, &day); + datetime_decodeTime(reportDate, &hr, &min, &sec); + snprintf(theDate, 26, " %04d %02d %02d %02d %02d %02d ", + yr, mon, day, hr, min, sec); + for (i=0; i 0 ) + { + report_writeErrorMsg(err, Finflows.name); + return; + } + + // --- match nodes in file with those in project + err = getIfaceFileNodes(); + if ( err > 0 ) + { + report_writeErrorMsg(err, Finflows.name); + return; + } + + // --- create matrices for old & new interface flows & WQ values + OldIfaceValues = project_createMatrix(NumIfaceNodes, + 1+NumIfacePolluts); + NewIfaceValues = project_createMatrix(NumIfaceNodes, + 1+NumIfacePolluts); + if ( OldIfaceValues == NULL || NewIfaceValues == NULL ) + { + report_writeErrorMsg(ERR_MEMORY, ""); + return; + } + + // --- read in new interface flows & WQ values + readNewIfaceValues(); + OldIfaceDate = NewIfaceDate; +} + +//============================================================================= + +int getIfaceFilePolluts() +// +// Input: none +// Output: returns an error code +// Purpose: reads names of pollutants saved on the inflows interface file. +// +{ + int i, j; + char line[MAXLINE+1]; // line from inflows interface file + char s1[MAXLINE+1]; // general string variable + char s2[MAXLINE+1]; + + // --- read number of pollutants (minus FLOW) + fgets(line, MAXLINE, Finflows.file); + NumIfacePolluts = -1; + if (sscanf(line, "%d", &NumIfacePolluts)) + NumIfacePolluts--; + if ( NumIfacePolluts < 0 ) return ERR_ROUTING_FILE_FORMAT; + + // --- read flow units + fgets(line, MAXLINE, Finflows.file); + if (sscanf(line, "%s %s", s1, s2) < 2) + return ERR_ROUTING_FILE_FORMAT; + if ( !strcomp(s1, "FLOW") ) + return ERR_ROUTING_FILE_FORMAT; + IfaceFlowUnits = findmatch(s2, FlowUnitWords); + if ( IfaceFlowUnits < 0 ) + return ERR_ROUTING_FILE_FORMAT; + + // --- allocate memory for pollutant index array + if ( Nobjects[POLLUT] > 0 ) + { + IfacePolluts = (int *) calloc(Nobjects[POLLUT], sizeof(int)); + if ( !IfacePolluts ) return ERR_MEMORY; + for (i=0; i 0 && Nobjects[POLLUT] > 0 ) + { + // --- check each pollutant name on file with project's pollutants + for (i=0; i +#include +#include "headers.h" +#include "infil.h" + +//----------------------------------------------------------------------------- +// Local Variables +//----------------------------------------------------------------------------- +typedef union TInfil { + THorton horton; + TGrnAmpt grnAmpt; + TCurveNum curveNum; +} TInfil; +TInfil *Infil; + +static double Fumax; // saturated water volume in upper soil zone (ft) +static double InfilFactor; + +//----------------------------------------------------------------------------- +// External Functions (declared in infil.h) +//----------------------------------------------------------------------------- +// infil_create (called by createObjects in project.c) +// infil_delete (called by deleteObjects in project.c) +// infil_readParams (called by input_readLine) +// infil_initState (called by subcatch_initState) +// infil_getState (called by writeRunoffFile in hotstart.c) +// infil_setState (called by readRunoffFile in hotstart.c) +// infil_getInfil (called by getSubareaRunoff in subcatch.c) + +// Called locally and by storage node methods in node.c +// grnampt_setParams +// grnampt_initState +// grnampt_getInfil + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int horton_setParams(THorton *infil, double p[]); +static void horton_initState(THorton *infil); +static void horton_getState(THorton *infil, double x[]); +static void horton_setState(THorton *infil, double x[]); +static double horton_getInfil(THorton *infil, double tstep, double irate, + double depth); +static double modHorton_getInfil(THorton *infil, double tstep, double irate, + double depth); + +static void grnampt_getState(TGrnAmpt *infil, double x[]); +static void grnampt_setState(TGrnAmpt *infil, double x[]); +static double grnampt_getUnsatInfil(TGrnAmpt *infil, double tstep, + double irate, double depth, int modelType); +static double grnampt_getSatInfil(TGrnAmpt *infil, double tstep, + double irate, double depth); +static double grnampt_getF2(double f1, double c1, double ks, double ts); + +static int curvenum_setParams(TCurveNum *infil, double p[]); +static void curvenum_initState(TCurveNum *infil); +static void curvenum_getState(TCurveNum *infil, double x[]); +static void curvenum_setState(TCurveNum *infil, double x[]); +static double curvenum_getInfil(TCurveNum *infil, double tstep, double irate, + double depth); + +//============================================================================= + +void infil_create(int n) +// +// Purpose: creates an array of infiltration objects. +// Input: n = number of subcatchments +// Output: none +// +{ + Infil = (TInfil *) calloc(n, sizeof(TInfil)); + if (Infil == NULL) ErrorCode = ERR_MEMORY; + InfilFactor = 1.0; + return; +} + +//============================================================================= + +void infil_delete() +// +// Purpose: deletes infiltration objects associated with subcatchments +// Input: none +// Output: none +// +{ + FREE(Infil); +} + +//============================================================================= + +int infil_readParams(int m, char* tok[], int ntoks) +// +// Input: m = default infiltration model +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: sets infiltration parameters from a line of input data. +// +// Format of data line is: +// subcatch p1 p2 ... (infilMethod) +{ + int i, j, n, status; + double x[5]; + + // --- check that subcatchment exists + j = project_findObject(SUBCATCH, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- check for infiltration method keyword is last token + i = findmatch(tok[ntoks-1], InfilModelWords); + if ( i >= 0 ) + { + m = i; + --ntoks; + } + + // --- number of input tokens depends on infiltration model m + if ( m == HORTON ) n = 5; + else if ( m == MOD_HORTON ) n = 5; + else if ( m == GREEN_AMPT ) n = 4; + else if ( m == MOD_GREEN_AMPT ) n = 4; + else if ( m == CURVE_NUMBER ) n = 4; + else return 0; + + if ( ntoks < n ) return error_setInpError(ERR_ITEMS, ""); + + // --- parse numerical values from tokens + for (i = 0; i < 5; i++) x[i] = 0.0; + for (i = 1; i < n; i++) + { + if (!getDouble(tok[i], &x[i - 1])) + return error_setInpError(ERR_NUMBER, tok[i]); + } + + // --- special case for Horton infil. - last parameter is optional + if ( (m == HORTON || m == MOD_HORTON) && ntoks > n ) + { + if ( ! getDouble(tok[n], &x[n-1]) ) + return error_setInpError(ERR_NUMBER, tok[n]); + } + + // --- assign parameter values to infil, infilModel object + Subcatch[j].infil = j; + Subcatch[j].infilModel = m; + switch (m) + { + case HORTON: + case MOD_HORTON: status = horton_setParams(&Infil[j].horton, x); + break; + case GREEN_AMPT: + case MOD_GREEN_AMPT: + status = grnampt_setParams(&Infil[j].grnAmpt, x); + break; + case CURVE_NUMBER: status = curvenum_setParams(&Infil[j].curveNum, x); + break; + default: status = TRUE; + } + if ( !status ) return error_setInpError(ERR_INFIL_PARAMS, ""); + return 0; +} + +//============================================================================= + +void infil_initState(int j) +// +// Input: j = subcatchment index +// Output: none +// Purpose: initializes state of infiltration for a subcatchment. +// +{ + switch (Subcatch[j].infilModel) + { + case HORTON: + case MOD_HORTON: horton_initState(&Infil[j].horton); break; + case GREEN_AMPT: + case MOD_GREEN_AMPT: + grnampt_initState(&Infil[j].grnAmpt); break; + case CURVE_NUMBER: curvenum_initState(&Infil[j].curveNum); break; + } +} + +//============================================================================= + +void infil_getState(int j, double x[]) +// +// Input: j = subcatchment index +// Output: x = subcatchment's infiltration state +// Purpose: retrieves the current infiltration state for a subcatchment. +// +{ + switch (Subcatch[j].infilModel) + { + case HORTON: + case MOD_HORTON: horton_getState(&Infil[j].horton, x); break; + case GREEN_AMPT: + case MOD_GREEN_AMPT: + grnampt_getState(&Infil[j].grnAmpt, x); break; + case CURVE_NUMBER: curvenum_getState(&Infil[j].curveNum, x); break; + } +} + +//============================================================================= + +void infil_setState(int j, double x[]) +// +// Input: j = subcatchment index +// m = infiltration method code +// Output: none +// Purpose: sets the current infiltration state for a subcatchment. +// +{ + switch (Subcatch[j].infilModel) + { + case HORTON: + case MOD_HORTON: horton_setState(&Infil[j].horton, x); break; + case GREEN_AMPT: + case MOD_GREEN_AMPT: + grnampt_setState(&Infil[j].grnAmpt, x); break; + case CURVE_NUMBER: curvenum_setState(&Infil[j].curveNum, x); break; + } +} + +//============================================================================= + +void infil_setInfilFactor(int j) +// +// Input: j = subcatchment index +// Output: none +// Purpose: assigns a value to the infiltration adjustment factor. +{ + int m; + int p; + + // ... set factor to the global conductivity adjustment factor + InfilFactor = Adjust.hydconFactor; + + // ... override global factor with subcatchment's adjustment if assigned + if (j >= 0) + { + p = Subcatch[j].infilPattern; + if (p >= 0 && Pattern[p].type == MONTHLY_PATTERN) + { + m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; + InfilFactor = Pattern[p].factor[m]; + } + } +} + +//============================================================================= + +double infil_getInfil(int j, double tstep, double rainfall, + double runon, double depth) +// +// Input: j = subcatchment index +// tstep = runoff time step (sec) +// rainfall = rainfall rate (ft/sec) +// runon = runon rate from other sub-areas or subcatchments (ft/sec) +// depth = depth of surface water on subcatchment (ft) +// Output: returns infiltration rate (ft/sec) +// Purpose: computes infiltration rate depending on infiltration method. +// +{ + switch (Subcatch[j].infilModel) + { + case HORTON: + return horton_getInfil(&Infil[j].horton, tstep, rainfall+runon, depth); + + case MOD_HORTON: + return modHorton_getInfil(&Infil[j].horton, tstep, rainfall+runon, + depth); + + case GREEN_AMPT: + case MOD_GREEN_AMPT: + return grnampt_getInfil(&Infil[j].grnAmpt, tstep, rainfall+runon, depth, + Subcatch[j].infilModel); + + case CURVE_NUMBER: + depth += runon * tstep; + return curvenum_getInfil(&Infil[j].curveNum, tstep, rainfall, depth); + + default: + return 0.0; + } +} + +//============================================================================= + +int horton_setParams(THorton *infil, double p[]) +// +// Input: infil = ptr. to Horton infiltration object +// p[] = array of parameter values +// Output: returns TRUE if parameters are valid, FALSE otherwise +// Purpose: assigns Horton infiltration parameters to a subcatchment. +// +{ + int k; + for (k = 0; k < 5; k++) if ( p[k] < 0.0 ) return FALSE; + + // --- max. & min. infil rates (ft/sec) + infil->f0 = p[0] / UCF(RAINFALL); + infil->fmin = p[1] / UCF(RAINFALL); + + // --- convert decay const. to 1/sec + infil->decay = p[2] / 3600.; + + // --- convert drying time (days) to a regeneration const. (1/sec) + // assuming that former is time to reach 98% dry along an + // exponential drying curve + if (p[3] == 0.0 ) p[3] = TINY; + infil->regen = -log(1.0-0.98) / p[3] / SECperDAY; + + // --- optional max. infil. capacity (ft) (p[4] = 0 if no value supplied) + infil->Fmax = p[4] / UCF(RAINDEPTH); + if ( infil->f0 < infil->fmin ) return FALSE; + return TRUE; +} + +//============================================================================= + +void horton_initState(THorton *infil) +// +// Input: infil = ptr. to Horton infiltration object +// Output: none +// Purpose: initializes time on Horton infiltration curve for a subcatchment. +// +{ + infil->tp = 0.0; + infil->Fe = 0.0; +} + +//============================================================================= + +void horton_getState(THorton *infil, double x[]) +{ + x[0] = infil->tp; + x[1] = infil->Fe; +} + +void horton_setState(THorton *infil, double x[]) +{ + infil->tp = x[0]; + infil->Fe = x[1]; +} + +//============================================================================= + +double horton_getInfil(THorton *infil, double tstep, double irate, double depth) +// +// Input: infil = ptr. to Horton infiltration object +// tstep = runoff time step (sec), +// irate = net "rainfall" rate (ft/sec), +// = rainfall + snowmelt + runon - evaporation +// depth = depth of ponded water (ft). +// Output: returns infiltration rate (ft/sec) +// Purpose: computes Horton infiltration for a subcatchment. +// +{ + // --- assign local variables + int iter; + double fa, fp = 0.0; + double Fp, F1, t1, tlim, ex, kt; + double FF, FF1, r; + double f0 = infil->f0 * InfilFactor; + double fmin = infil->fmin * InfilFactor; + double Fmax = infil->Fmax; + double tp = infil->tp; + double df = f0 - fmin; + double kd = infil->decay; + double kr = infil->regen * Evap.recoveryFactor; + + // --- special cases of no infil. or constant infil + if ( df < 0.0 || kd < 0.0 || kr < 0.0 ) return 0.0; + if ( df == 0.0 || kd == 0.0 ) + { + fp = f0; + fa = irate + depth / tstep; + if ( fp > fa ) fp = fa; + return MAX(0.0, fp); + } + + // --- compute water available for infiltration + fa = irate + depth / tstep; + + // --- case where there is water to infiltrate + if ( fa > ZERO ) + { + // --- compute average infil. rate over time step + t1 = tp + tstep; // future cumul. time + tlim = 16.0 / kd; // for tp >= tlim, f = fmin + if ( tp >= tlim ) + { + Fp = fmin * tp + df / kd; + F1 = Fp + fmin * tstep; + } + else + { + Fp = fmin * tp + df / kd * (1.0 - exp(-kd * tp)); + F1 = fmin * t1 + df / kd * (1.0 - exp(-kd * t1)); + } + fp = (F1 - Fp) / tstep; + fp = MAX(fp, fmin); + + // --- limit infil rate to available infil + if ( fp > fa ) fp = fa; + + // --- if fp on flat portion of curve then increase tp by tstep + if ( t1 > tlim ) tp = t1; + + // --- if infil < available capacity then increase tp by tstep + else if ( fp < fa ) tp = t1; + + // --- if infil limited by available capacity then + // solve F(tp) - F1 = 0 using Newton-Raphson method + else + { + F1 = Fp + fp * tstep; + tp = tp + tstep / 2.0; + for ( iter=1; iter<=20; iter++ ) + { + kt = MIN( 60.0, kd*tp ); + ex = exp(-kt); + FF = fmin * tp + df / kd * (1.0 - ex) - F1; + FF1 = fmin + df * ex; + r = FF / FF1; + tp = tp - r; + if ( fabs(r) <= 0.001 * tstep ) break; + } + } + + // --- limit cumulative infiltration to Fmax + if ( Fmax > 0.0 ) + { + if ( infil->Fe + fp * tstep > Fmax ) + fp = (Fmax - infil->Fe) / tstep; + fp = MAX(fp, 0.0); + infil->Fe += fp * tstep; + } + } + + // --- case where infil. capacity is regenerating; update tp. + else if (kr > 0.0) + { + r = exp(-kr * tstep); + tp = 1.0 - exp(-kd * tp); + tp = -log(1.0 - r*tp) / kd; + + // reduction in cumulative infiltration + if ( Fmax > 0.0 ) + { + infil->Fe = fmin*tp + (df/kd)*(1.0 - exp(-kd*tp)); + } + } + infil->tp = tp; + return fp; +} + +//============================================================================= + +double modHorton_getInfil(THorton *infil, double tstep, double irate, + double depth) +// +// Input: infil = ptr. to Horton infiltration object +// tstep = runoff time step (sec), +// irate = net "rainfall" rate (ft/sec), +// = rainfall + snowmelt + runon +// depth = depth of ponded water (ft). +// Output: returns infiltration rate (ft/sec) +// Purpose: computes modified Horton infiltration for a subcatchment. +// +{ + // --- assign local variables + double f = 0.0; + double fp, fa; + double f0 = infil->f0 * InfilFactor; + double fmin = infil->fmin * InfilFactor; + double df = f0 - fmin; + double kd = infil->decay; + double kr = infil->regen * Evap.recoveryFactor; + + // --- special cases of no or constant infiltration + if ( df < 0.0 || kd < 0.0 || kr < 0.0 ) return 0.0; + if ( df == 0.0 || kd == 0.0 ) + { + fp = f0; + fa = irate + depth / tstep; + if ( fp > fa ) fp = fa; + return MAX(0.0, fp); + } + + // --- compute water available for infiltration + fa = irate + depth / tstep; + + // --- case where there is water to infiltrate + if ( fa > ZERO ) + { + // --- saturated condition + if ( infil->Fmax > 0.0 && infil->Fe >= infil->Fmax ) return 0.0; + + // --- potential infiltration + fp = f0 - kd * infil->Fe; + fp = MAX(fp, fmin); + + // --- actual infiltration + f = MIN(fa, fp); + + // --- new cumulative infiltration minus seepage + infil->Fe += MAX((f - fmin), 0.0) * tstep; + if ( infil->Fmax > 0.0 ) infil->Fe = MAX(infil->Fe, infil->Fmax); + } + + // --- reduce cumulative infiltration for dry condition + else if (kr > 0.0) + { + infil->Fe *= exp(-kr * tstep); + infil->Fe = MAX(infil->Fe, 0.0); + } + return f; +} + +//============================================================================= + +void grnampt_getParams(int j, double p[]) +// +// Input: j = subcatchment index +// p[] = array of parameter values +// Output: none +// Purpose: retrieves Green-Ampt infiltration parameters for a subcatchment. +// +{ + p[0] = Infil[j].grnAmpt.S * UCF(RAINDEPTH); // Capillary suction head (ft) + p[1] = Infil[j].grnAmpt.Ks * UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) + p[2] = Infil[j].grnAmpt.IMDmax; // Max. init. moisture deficit +} + +//============================================================================= + +int grnampt_setParams(TGrnAmpt *infil, double p[]) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// p[] = array of parameter values +// Output: returns TRUE if parameters are valid, FALSE otherwise +// Purpose: assigns Green-Ampt infiltration parameters to a subcatchment. +// +{ + double ksat; // sat. hyd. conductivity in in/hr + + if ( p[0] < 0.0 || p[1] <= 0.0 || p[2] < 0.0 || p[2] > 1.0) return FALSE; + infil->S = p[0] / UCF(RAINDEPTH); // Capillary suction head (ft) + infil->Ks = p[1] / UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) + infil->IMDmax = p[2]; // Max. init. moisture deficit + + // --- find depth of upper soil zone (ft) using Mein's eqn. + ksat = infil->Ks * 12. * 3600.; + infil->Lu = 4.0 * sqrt(ksat) / 12.; + return TRUE; +} + +//============================================================================= + +void grnampt_initState(TGrnAmpt *infil) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// Output: none +// Purpose: initializes state of Green-Ampt infiltration for a subcatchment. +// +{ + if (infil == NULL) return; + infil->IMD = infil->IMDmax; + infil->Fu = 0.0; + infil->F = 0.0; + infil->Sat = FALSE; + infil->T = 0.0; +} + +void grnampt_getState(TGrnAmpt *infil, double x[]) +{ + x[0] = infil->IMD; + x[1] = infil->F; + x[2] = infil->Fu; + x[3] = infil->Sat; + x[4] = infil->T; +} + +void grnampt_setState(TGrnAmpt *infil, double x[]) +{ + infil->IMD = x[0]; + infil->F = x[1]; + infil->Fu = x[2]; + infil->Sat = (char)x[3]; + infil->T = x[4]; +} + +//============================================================================= + +double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, + double depth, int modelType) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// tstep = time step (sec), +// irate = net "rainfall" rate to upper zone (ft/sec); +// = rainfall + snowmelt + runon, +// does not include ponded water (added on below) +// depth = depth of ponded water (ft) +// modelType = either GREEN_AMPT or MOD_GREEN_AMPT +// Output: returns infiltration rate (ft/sec) +// Purpose: computes Green-Ampt infiltration for a subcatchment +// or a storage node. +// +{ + // --- find saturated upper soil zone water volume + Fumax = infil->IMDmax * infil->Lu * sqrt(InfilFactor); + + // --- reduce time until next event + infil->T -= tstep; + + // --- use different procedures depending on upper soil zone saturation + if ( infil->Sat ) return grnampt_getSatInfil(infil, tstep, irate, depth); + else return grnampt_getUnsatInfil(infil, tstep, irate, depth, modelType); +} + +//============================================================================= + +double grnampt_getUnsatInfil(TGrnAmpt *infil, double tstep, double irate, + double depth, int modelType) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// tstep = runoff time step (sec), +// irate = net "rainfall" rate to upper zone (ft/sec); +// = rainfall + snowmelt + runon, +// does not include ponded water (added on below) +// depth = depth of ponded water (ft) +// modelType = either GREEN_AMPT or MOD_GREEN_AMPT +// Output: returns infiltration rate (ft/sec) +// Purpose: computes Green-Ampt infiltration when upper soil zone is +// unsaturated. +// +{ + double ia, c1, F2, dF, Fs, kr, ts; + double ks = infil->Ks * InfilFactor; + double lu = infil->Lu * sqrt(InfilFactor); + + // --- get available infiltration rate (rainfall + ponded water) + ia = irate + depth / tstep; + if ( ia < ZERO ) ia = 0.0; + + // --- no rainfall so recover upper zone moisture + if ( ia == 0.0 ) + { + if ( infil->Fu <= 0.0 ) return 0.0; + kr = lu / 90000.0 * Evap.recoveryFactor; + dF = kr * Fumax * tstep; + infil->F -= dF; + infil->Fu -= dF; + if ( infil->Fu <= 0.0 ) + { + infil->Fu = 0.0; + infil->F = 0.0; + infil->IMD = infil->IMDmax; + return 0.0; + } + + // --- if new wet event begins then reset IMD & F + if ( infil->T <= 0.0 ) + { + infil->IMD = (Fumax - infil->Fu) / lu; + infil->F = 0.0; + } + return 0.0; + } + + // --- rainfall does not exceed Ksat + if ( ia <= ks ) + { + dF = ia * tstep; + infil->F += dF; + infil->Fu += dF; + infil->Fu = MIN(infil->Fu, Fumax); + if ( modelType == GREEN_AMPT && infil->T <= 0.0 ) + { + infil->IMD = (Fumax - infil->Fu) / lu; + infil->F = 0.0; + } + return ia; + } + + // --- rainfall exceeds Ksat; renew time to drain upper zone + infil->T = 5400.0 / lu / Evap.recoveryFactor; + + // --- find volume needed to saturate surface layer + Fs = ks * (infil->S + depth) * infil->IMD / (ia - ks); + + // --- surface layer already saturated + if ( infil->F > Fs ) + { + infil->Sat = TRUE; + return grnampt_getSatInfil(infil, tstep, irate, depth); + } + + // --- surface layer remains unsaturated + if ( infil->F + ia*tstep < Fs ) + { + dF = ia * tstep; + infil->F += dF; + infil->Fu += dF; + infil->Fu = MIN(infil->Fu, Fumax); + return ia; + } + + // --- surface layer becomes saturated during time step; + // --- compute portion of tstep when saturated + ts = tstep - (Fs - infil->F) / ia; + if ( ts <= 0.0 ) ts = 0.0; + + // --- compute new total volume infiltrated + c1 = (infil->S + depth) * infil->IMD; + F2 = grnampt_getF2(Fs, c1, ks, ts); + if ( F2 > Fs + ia*ts ) F2 = Fs + ia*ts; + + // --- compute infiltration rate + dF = F2 - infil->F; + infil->F = F2; + infil->Fu += dF; + infil->Fu = MIN(infil->Fu, Fumax); + infil->Sat = TRUE; + return dF / tstep; +} + +//============================================================================= + +double grnampt_getSatInfil(TGrnAmpt *infil, double tstep, double irate, + double depth) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// tstep = runoff time step (sec), +// irate = net "rainfall" rate to upper zone (ft/sec); +// = rainfall + snowmelt + runon, +// does not include ponded water (added on below) +// depth = depth of ponded water (ft). +// Output: returns infiltration rate (ft/sec) +// Purpose: computes Green-Ampt infiltration when upper soil zone is +// saturated. +// +{ + double ia, c1, dF, F2; + double ks = infil->Ks * InfilFactor; + double lu = infil->Lu * sqrt(InfilFactor); + + // --- get available infiltration rate (rainfall + ponded water) + ia = irate + depth / tstep; + if ( ia < ZERO ) return 0.0; + + // --- re-set new event recovery time + infil->T = 5400.0 / lu / Evap.recoveryFactor; + + // --- solve G-A equation for new cumulative infiltration volume (F2) + c1 = (infil->S + depth) * infil->IMD; + F2 = grnampt_getF2(infil->F, c1, ks, tstep); + dF = F2 - infil->F; + + // --- all available water infiltrates -- set saturated state to false + if ( dF > ia * tstep ) + { + dF = ia * tstep; + infil->Sat = FALSE; + } + + // --- update total infiltration and upper zone moisture deficit + infil->F += dF; + infil->Fu += dF; + infil->Fu = MIN(infil->Fu, Fumax); + return dF / tstep; +} + +//============================================================================= + +double grnampt_getF2(double f1, double c1, double ks, double ts) +// +// Input: f1 = old infiltration volume (ft) +// c1 = head * moisture deficit (ft) +// ks = sat. hyd. conductivity (ft/sec) +// ts = time step (sec) +// Output: returns infiltration volume at end of time step (ft) +// Purpose: computes new infiltration volume over a time step +// using Green-Ampt formula for saturated upper soil zone. +// +{ + int i; + double f2 = f1; + double f2min; + double df2; + double c2; + + // --- find min. infil. volume + f2min = f1 + ks * ts; + + // --- use min. infil. volume for 0 moisture deficit + if ( c1 == 0.0 ) return f2min; + + // --- use direct form of G-A equation for small time steps + // and c1/f1 < 100 + if ( ts < 10.0 && f1 > 0.01 * c1 ) + { + f2 = f1 + ks * (1.0 + c1/f1) * ts; + return MAX(f2, f2min); + } + + // --- use Newton-Raphson method to solve integrated G-A equation + // (convergence limit reduced from that used in previous releases) + c2 = c1 * log(f1 + c1) - ks * ts; + for ( i = 1; i <= 20; i++ ) + { + df2 = (f2 - f1 - c1 * log(f2 + c1) + c2) / (1.0 - c1 / (f2 + c1) ); + if ( fabs(df2) < 0.00001 ) + { + return MAX(f2, f2min); + } + f2 -= df2; + } + return f2min; +} + +//============================================================================= + +int curvenum_setParams(TCurveNum *infil, double p[]) +// +// Input: infil = ptr. to Curve Number infiltration object +// p[] = array of parameter values +// Output: returns TRUE if parameters are valid, FALSE otherwise +// Purpose: assigns Curve Number infiltration parameters to a subcatchment. +// +{ + + // --- convert Curve Number to max. infil. capacity + if ( p[0] < 10.0 ) p[0] = 10.0; + if ( p[0] > 99.0 ) p[0] = 99.0; + infil->Smax = (1000.0 / p[0] - 10.0) / 12.0; + if ( infil->Smax < 0.0 ) return FALSE; + + // --- convert drying time (days) to a regeneration const. (1/sec) + if ( p[2] > 0.0 ) infil->regen = 1.0 / (p[2] * SECperDAY); + else return FALSE; + + // --- compute inter-event time from regeneration const. as in Green-Ampt + infil->Tmax = 0.06 / infil->regen; + + return TRUE; +} + +//============================================================================= + +void curvenum_initState(TCurveNum *infil) +// +// Input: infil = ptr. to Curve Number infiltration object +// Output: none +// Purpose: initializes state of Curve Number infiltration for a subcatchment. +// +{ + infil->S = infil->Smax; + infil->P = 0.0; + infil->F = 0.0; + infil->T = 0.0; + infil->Se = infil->Smax; + infil->f = 0.0; +} + +void curvenum_getState(TCurveNum *infil, double x[]) +{ + x[0] = infil->S; + x[1] = infil->P; + x[2] = infil->F; + x[3] = infil->T; + x[4] = infil->Se; + x[5] = infil->f; +} + +void curvenum_setState(TCurveNum *infil, double x[]) +{ + infil->S = x[0]; + infil->P = x[1]; + infil->F = x[2]; + infil->T = x[3]; + infil->Se = x[4]; + infil->f = x[5]; +} + +//============================================================================= + +double curvenum_getInfil(TCurveNum *infil, double tstep, double irate, + double depth) +// +// Input: infil = ptr. to Curve Number infiltration object +// tstep = runoff time step (sec), +// irate = rainfall rate (ft/sec); +// depth = depth of runon + ponded water (ft) +// Output: returns infiltration rate (ft/sec) +// Purpose: computes infiltration rate using the Curve Number method. +// Note: this function treats runon from other subcatchments as part +// of the ponded depth and not as an effective rainfall rate. +{ + double F1; // new cumulative infiltration (ft) + double f1 = 0.0; // new infiltration rate (ft/sec) + double fa = irate + depth/tstep; // max. available infil. rate (ft/sec) + + // --- case where there is rainfall + if ( irate > ZERO ) + { + // --- check if new rain event + if ( infil->T >= infil->Tmax ) + { + infil->P = 0.0; + infil->F = 0.0; + infil->f = 0.0; + infil->Se = infil->S; + } + infil->T = 0.0; + + // --- update cumulative precip. + infil->P += irate * tstep; + + // --- find potential new cumulative infiltration + F1 = infil->P * (1.0 - infil->P / (infil->P + infil->Se)); + + // --- compute potential infiltration rate + f1 = (F1 - infil->F) / tstep; + if ( f1 < 0.0 || infil->S <= 0.0 ) f1 = 0.0; + + } + + // --- case of no rainfall + else + { + // --- if there is ponded water then use previous infil. rate + if ( depth > MIN_TOTAL_DEPTH && infil->S > 0.0 ) + { + f1 = infil->f; + if ( f1*tstep > infil->S ) f1 = infil->S / tstep; + } + + // --- otherwise update inter-event time + else infil->T += tstep; + } + + // --- if there is some infiltration + if ( f1 > 0.0 ) + { + // --- limit infil. rate to max. available rate + f1 = MIN(f1, fa); + f1 = MAX(f1, 0.0); + + // --- update actual cumulative infiltration + infil->F += f1 * tstep; + + // --- reduce infil. capacity if a regen. constant was supplied + if ( infil->regen > 0.0 ) + { + infil->S -= f1 * tstep; + if ( infil->S < 0.0 ) infil->S = 0.0; + } + } + + // --- otherwise regenerate infil. capacity + else + { + infil->S += infil->regen * infil->Smax * tstep * Evap.recoveryFactor; + if ( infil->S > infil->Smax ) infil->S = infil->Smax; + } + infil->f = f1; + return f1; +} diff --git a/src/infil.h b/src/infil.h new file mode 100644 index 000000000..f5ddf31aa --- /dev/null +++ b/src/infil.h @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------------- +// infil.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Public interface for infiltration functions. +// +// Update History +// ============== +// Build 5.1.010: +// - New Modified Green Ampt infiltration option added. +// Build 5.1.013: +// - New function infil_setInfilFactor() added. +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. +//----------------------------------------------------------------------------- + +#ifndef INFIL_H +#define INFIL_H + +//--------------------- +// Enumerated Constants +//--------------------- +enum InfilType { + HORTON, // Horton infiltration + MOD_HORTON, // Modified Horton infiltration + GREEN_AMPT, // Green-Ampt infiltration + MOD_GREEN_AMPT, // Modified Green-Ampt infiltration + CURVE_NUMBER}; // SCS Curve Number infiltration + +//--------------------- +// Horton Infiltration +//--------------------- +typedef struct +{ + double f0; // initial infil. rate (ft/sec) + double fmin; // minimum infil. rate (ft/sec) + double Fmax; // maximum total infiltration (ft); + double decay; // decay coeff. of infil. rate (1/sec) + double regen; // regeneration coeff. of infil. rate (1/sec) + //----------------------------- + double tp; // present time on infiltration curve (sec) + double Fe; // cumulative infiltration (ft) +} THorton; + + +//------------------------- +// Green-Ampt Infiltration +//------------------------- +typedef struct +{ + double S; // avg. capillary suction (ft) + double Ks; // saturated conductivity (ft/sec) + double IMDmax; // max. soil moisture deficit (ft/ft) + //----------------------------- + double IMD; // current initial soil moisture deficit + double F; // current cumulative infiltrated volume (ft) + double Fu; // current upper zone infiltrated volume (ft) + double Lu; // depth of upper soil zone (ft) + double T; // time until start of next rain event (sec) + char Sat; // saturation flag +} TGrnAmpt; + + +//-------------------------- +// Curve Number Infiltration +//-------------------------- +typedef struct +{ + double Smax; // max. infiltration capacity (ft) + double regen; // infil. capacity regeneration constant (1/sec) + double Tmax; // maximum inter-event time (sec) + //----------------------------- + double S; // current infiltration capacity (ft) + double F; // current cumulative infiltration (ft) + double P; // current cumulative precipitation (ft) + double T; // current inter-event time (sec) + double Se; // current event infiltration capacity (ft) + double f; // previous infiltration rate (ft/sec) + +} TCurveNum; + +//----------------------------------------------------------------------------- +// Exported Variables +//----------------------------------------------------------------------------- +extern THorton* HortInfil; +extern TGrnAmpt* GAInfil; +extern TCurveNum* CNInfil; + +//----------------------------------------------------------------------------- +// Infiltration Methods +//----------------------------------------------------------------------------- +void infil_create(int n); +void infil_delete(void); +int infil_readParams(int m, char* tok[], int ntoks); +void infil_initState(int j); +void infil_getState(int j, double x[]); +void infil_setState(int j, double x[]); +void infil_setInfilFactor(int j); +double infil_getInfil(int area, double tstep, double rainfall, double runon, + double depth); + +void grnampt_getParams(int j, double p[]); +int grnampt_setParams(TGrnAmpt *infil, double p[]); +void grnampt_initState(TGrnAmpt *infil); +double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, + double depth, int modelType); + +#endif diff --git a/src/inflow.c b/src/inflow.c new file mode 100644 index 000000000..7cb5e250f --- /dev/null +++ b/src/inflow.c @@ -0,0 +1,484 @@ +//----------------------------------------------------------------------------- +// inflow.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Manages any Direct External or Dry Weather Flow inflows +// that have been assigned to nodes of the drainage system. +// +// Update History +// ============== +// Build 5.2.0: +// - Removed references to unused extIfaceInflow member of ExtInflow struct. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// External Functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// inflow_initDwfPattern (called createObjects in project.c) +// inflow_readExtInflow (called by input_readLine) +// inflow_readDwfInflow (called by input_readLine) +// inflow_deleteExtInflows (called by deleteObjects in project.c) +// inflow_deleteDwfInflows (called by deleteObjects in project.c) +// inflow_getExtInflow (called by addExternalInflows in routing.c) +// inflow_setExtInflow (called by setNodeInflow in swmm5.c) +// inflow_getDwfInflow (called by addDryWeatherInflows in routing.c) + +//----------------------------------------------------------------------------- +// Local Functions +//----------------------------------------------------------------------------- +double getPatternFactor(int p, int month, int day, int hour); + + +int inflow_readExtInflow(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error message +// Purpose: reads parameters of a direct external inflow from a line of input. +// +// Formats of data line are: +// nodeID FLOW tSeriesID (FLOW 1.0 scaleFactor baseline basePat) +// nodeID pollutID tSeriesID (CONCEN/MASS unitsFactor scaleFactor baseline basePat) +// +{ + int j; // object index + int param; // FLOW (-1) or pollutant index + int type = CONCEN_INFLOW; // FLOW, CONCEN or MASS inflow + int tseries = -1; // time series index + int basePat = -1; // baseline pattern + double cf = 1.0; // units conversion factor + double sf = 1.0; // scaling factor + double baseline = 0.0; // baseline value + + // --- find index of node receiving the inflow + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + j = project_findObject(NODE, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- find index of inflow pollutant or use -1 for FLOW + param = project_findObject(POLLUT, tok[1]); + if ( param < 0 ) + { + if ( match(tok[1], w_FLOW) ) param = -1; + else return error_setInpError(ERR_NAME, tok[1]); + } + + // --- find index of inflow time series (if supplied) in data base + if ( strlen(tok[2]) > 0 ) + { + tseries = project_findObject(TSERIES, tok[2]); + if ( tseries < 0 ) return error_setInpError(ERR_NAME, tok[2]); + Tseries[tseries].refersTo = EXTERNAL_INFLOW; + } + + // --- assign type & cf values for a FLOW inflow + if (param == -1) + { + type = FLOW_INFLOW; + cf = 1.0/UCF(FLOW); + } + + // --- do the same for a pollutant inflow + if ( ntoks >= 4 && param > -1) + { + if ( match(tok[3], w_CONCEN) ) type = CONCEN_INFLOW; + else if ( match(tok[3], w_MASS) ) type = MASS_INFLOW; + else return error_setInpError(ERR_KEYWORD, tok[3]); + if ( ntoks >= 5 && type == MASS_INFLOW ) + { + if ( ! getDouble(tok[4], &cf) ) + { + return error_setInpError(ERR_NUMBER, tok[4]); + } + if ( cf <= 0.0 ) return error_setInpError(ERR_NUMBER, tok[4]); + } + } + + // --- get sf and baseline values + if ( ntoks >= 6 ) + { + if ( ! getDouble(tok[5], &sf) ) + { + return error_setInpError(ERR_NUMBER, tok[5]); + } + } + if ( ntoks >= 7 ) + { + if ( ! getDouble(tok[6], &baseline) ) + { + return error_setInpError(ERR_NUMBER, tok[6]); + } + } + + // --- get baseline time pattern + if ( ntoks >= 8 ) + { + basePat = project_findObject(TIMEPATTERN, tok[7]); + if ( basePat < 0 ) return error_setInpError(ERR_NAME, tok[7]); + } + + // --- include LperFT3 term in conversion factor for MASS_INFLOW + if ( type == MASS_INFLOW ) cf /= LperFT3; + + return(inflow_setExtInflow(j, param, type, tseries, basePat, + cf, baseline, sf)); +} + +//============================================================================= + +int inflow_setExtInflow(int j, int param, int type, int tseries, int basePat, + double cf, double baseline, double sf) +// Purpose: This function assigns property values to the inflow object +// Inputs: j = Node index +// param = FLOW (-1) or pollutant index +// type = FLOW, CONCEN or MASS inflow +// tSeries = time series index +// basePat = baseline pattern +// cf = units conversion factor +// baseline = baseline inflow value +// sf = scaling factor +// Return: returns Error Code + +{ + TExtInflow* inflow; // external inflow object + + // --- check if an external inflow object for this constituent already exists + inflow = Node[j].extInflow; + while ( inflow ) + { + if ( inflow->param == param ) break; + inflow = inflow->next; + } + + // --- if it doesn't exist, then create it + if ( inflow == NULL ) + { + inflow = (TExtInflow *) malloc(sizeof(TExtInflow)); + if ( inflow == NULL ) + { + return error_setInpError(ERR_MEMORY, ""); + } + inflow->next = Node[j].extInflow; + Node[j].extInflow = inflow; + } + + // --- assign property values to the inflow object + inflow->param = param; + inflow->type = type; + inflow->tSeries = tseries; + inflow->cFactor = cf; + inflow->sFactor = sf; + inflow->baseline = baseline; + inflow->basePat = basePat; + return 0; +} + +//============================================================================= + +void inflow_deleteExtInflows(int j) +// +// Input: j = node index +// Output: none +// Purpose: deletes all time series inflow data for a node. +// +{ + TExtInflow* inflow1; + TExtInflow* inflow2; + inflow1 = Node[j].extInflow; + while ( inflow1 ) + { + inflow2 = inflow1->next; + free(inflow1); + inflow1 = inflow2; + } +} + +//============================================================================= + +double inflow_getExtInflow(TExtInflow* inflow, DateTime aDate) +// +// Input: inflow = external inflow data structure +// aDate = current simulation date/time +// Output: returns current value of external inflow parameter +// Purpose: retrieves the value of an external inflow at a specific +// date and time. +// +{ + int month, day, hour; + int p = inflow->basePat; // baseline pattern + int k = inflow->tSeries; // time series index + double cf = inflow->cFactor; // units conversion factor + double sf = inflow->sFactor; // scaling factor + double blv = inflow->baseline; // baseline value + double tsv = 0.0; // time series value + + if ( p >= 0 ) + { + month = datetime_monthOfYear(aDate) - 1; + day = datetime_dayOfWeek(aDate) - 1; + hour = datetime_hourOfDay(aDate); + blv *= getPatternFactor(p, month, day, hour); + } + if ( k >= 0 ) tsv = table_tseriesLookup(&Tseries[k], aDate, FALSE) * sf; + return cf * (tsv + blv); +} + +//============================================================================= + +int inflow_readDwfInflow(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error message +// Purpose: reads dry weather inflow parameters from line of input data. +// +// Format of data line is: +// nodeID FLOW/pollutID avgValue (pattern1 pattern2 ... pattern4) +// +{ + int i; + int j; // node index + int k; // pollutant index (-1 for flow) + int m; // time pattern index + int pats[4]; // time pattern index array + double x; // avg. DWF value + TDwfInflow* inflow; // dry weather flow inflow object + + // --- find index of node receiving the inflow + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + j = project_findObject(NODE, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- find index of inflow pollutant (-1 for FLOW) + k = project_findObject(POLLUT, tok[1]); + if ( k < 0 ) + { + if ( match(tok[1], w_FLOW) ) k = -1; + else return error_setInpError(ERR_NAME, tok[1]); + } + + // --- get avg. value of DWF inflow + if ( !getDouble(tok[2], &x) ) + return error_setInpError(ERR_NUMBER, tok[2]); + if ( k == -1 ) x /= UCF(FLOW); + + // --- get time patterns assigned to the inflow + for (i=0; i<4; i++) pats[i] = -1; + for (i=3; i<7; i++) + { + if ( i >= ntoks ) break; + if ( strlen(tok[i]) == 0 ) continue; + m = project_findObject(TIMEPATTERN, tok[i]); + if ( m < 0 ) return error_setInpError(ERR_NAME, tok[i]); + pats[i-3] = m; + } + + // --- check if inflow for this constituent already exists + inflow = Node[j].dwfInflow; + while ( inflow ) + { + if ( inflow->param == k ) break; + inflow = inflow->next; + } + + // --- if it doesn't exist, then create it + if ( inflow == NULL ) + { + inflow = (TDwfInflow *) malloc(sizeof(TDwfInflow)); + if ( inflow == NULL ) return error_setInpError(ERR_MEMORY, ""); + inflow->next = Node[j].dwfInflow; + Node[j].dwfInflow = inflow; + } + + // --- assign property values to the inflow object + inflow->param = k; + inflow->avgValue = x; + for (i=0; i<4; i++) inflow->patterns[i] = pats[i]; + return 0; +} + +//============================================================================= + +void inflow_deleteDwfInflows(int j) +// +// Input: j = node index +// Output: none +// Purpose: deletes all dry weather inflow data for a node. +// +{ + TDwfInflow* inflow1; + TDwfInflow* inflow2; + inflow1 = Node[j].dwfInflow; + while ( inflow1 ) + { + inflow2 = inflow1->next; + free(inflow1); + inflow1 = inflow2; + } +} + +//============================================================================= + +void inflow_initDwfInflow(TDwfInflow* inflow) +// +// Input: inflow = dry weather inflow data structure +// Output: none +// Purpose: initialzes a dry weather inflow by ordering its time patterns. +// +// This function sorts the user-supplied time patterns for a dry weather +// inflow in the order of the PatternType enumeration (monthly, daily, +// weekday hourly, weekend hourly) to help speed up pattern processing. +// +{ + int i, p; + int tmpPattern[4]; // index of each type of DWF pattern + + // --- assume no patterns were supplied + for (i=0; i<4; i++) tmpPattern[i] = -1; + + // --- assign supplied patterns to proper position (by type) in tmpPattern + for (i=0; i<4; i++) + { + p = inflow->patterns[i]; + if ( p >= 0 ) tmpPattern[Pattern[p].type] = p; + } + + // --- re-fill inflow pattern array by pattern type + for (i=0; i<4; i++) inflow->patterns[i] = tmpPattern[i]; +} + +//============================================================================= + +double inflow_getDwfInflow(TDwfInflow* inflow, int month, int day, int hour) +// +// Input: inflow = dry weather inflow data structure +// month = current month of year of simulation +// day = current day of week of simulation +// hour = current hour of day of simulation +// Output: returns value of dry weather inflow parameter +// Purpose: computes dry weather inflow value at a specific point in time. +// +{ + int p1, p2; // pattern index + double f = 1.0; // pattern factor + + p1 = inflow->patterns[MONTHLY_PATTERN]; + if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); + p1 = inflow->patterns[DAILY_PATTERN]; + if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); + p1 = inflow->patterns[HOURLY_PATTERN]; + p2 = inflow->patterns[WEEKEND_PATTERN]; + if ( p2 >= 0 ) + { + if ( day == 0 || day == 6 ) + f *= getPatternFactor(p2, month, day, hour); + else if ( p1 >= 0 ) + f *= getPatternFactor(p1, month, day, hour); + } + else if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); + return f * inflow->avgValue; + +} + +//============================================================================= + +void inflow_initDwfPattern(int j) +// +// Input: j = time pattern index +// Output: none +// Purpose: initialzes a dry weather inflow time pattern. +// +{ + int i; + for (i=0; i<24; i++) Pattern[j].factor[i] = 1.0; + Pattern[j].count = 0; + Pattern[j].type = -1; + Pattern[j].ID = NULL; +} + +//============================================================================= + +int inflow_readDwfPattern(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error message +// Purpose: reads values of a time pattern from a line of input data. +// +// Format of data line is: +// patternID patternType value(1) value(2) ... +// patternID value(n) value(n+1) .... (for continuation lines) +{ + int i, j, k, n = 1; + + // --- check for minimum number of tokens + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that pattern exists in database + j = project_findObject(TIMEPATTERN, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- check if this is first line of pattern + // (ID pointer will not have been assigned yet) + if ( Pattern[j].ID == NULL ) + { + // --- assign ID pointer & pattern type + Pattern[j].ID = project_findID(TIMEPATTERN, tok[0]); + k = findmatch(tok[1], PatternTypeWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + Pattern[j].type = k; + n = 2; + } + + // --- start reading pattern factors from rest of line + while ( ntoks > n && Pattern[j].count < 24 ) + { + i = Pattern[j].count; + if ( !getDouble(tok[n], &Pattern[j].factor[i]) ) + return error_setInpError(ERR_NUMBER, tok[n]); + Pattern[j].count++; + n++; + } + return 0; +} + +//============================================================================= + +double getPatternFactor(int p, int month, int day, int hour) +// +// Input: p = time pattern index +// month = current month of year of simulation +// day = current day of week of simulation +// hour = current hour of day of simulation +// Output: returns value of a time pattern multiplier +// Purpose: computes time pattern multiplier for a specific point in time. +{ + switch ( Pattern[p].type ) + { + case MONTHLY_PATTERN: + if ( month >= 0 && month < 12 ) return Pattern[p].factor[month]; + break; + case DAILY_PATTERN: + if ( day >= 0 && day < 7 ) return Pattern[p].factor[day]; + break; + case HOURLY_PATTERN: + if ( hour >= 0 && hour < 24 ) return Pattern[p].factor[hour]; + break; + case WEEKEND_PATTERN: + if ( day == 0 || day == 6 ) + { + if ( hour >= 0 && hour < 24 ) return Pattern[p].factor[hour]; + } + break; + } + return 1.0; +} diff --git a/src/inlet.c b/src/inlet.c new file mode 100644 index 000000000..e9d1aaa59 --- /dev/null +++ b/src/inlet.c @@ -0,0 +1,1945 @@ +//----------------------------------------------------------------------------- +// inlet.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Street/Channel Inlet Functions +// +// Computes capture efficiency of inlets placed in Street conduits +// or Rectangular/Trapezoidal channels using FHWA HEC-22 methods (see +// Brown, S.A. et al., Urban Drainage Design Manual, Federal Highway +// Administration Hydraulic Engineering Circular No. 22, 3rd Edition, +// FHWA-NHI-10-009, August 2013). +// +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" + +// Grate inlet +typedef struct +{ + int type; // type of grate used + double length; // length (parallel to flow) (ft) + double width; // width (perpendicular to flow) (ft) + double fracOpenArea; // fraction of grate area that is open + double splashVeloc; // splash-over velocity (ft/s) +} TGrateInlet; + +// Slotted drain inlet +typedef struct +{ + double length; // length (parallel to flow) (ft) + double width; // width (perpendicular to flow) (ft) +} TSlottedInlet; + +// Curb opening inlet +typedef struct +{ + double length; // length of curb opening (ft) + double height; // height of curb opening (ft) + int throatAngle; // type of throat angle +} TCurbInlet; + +// Custom inlet +typedef struct +{ + int onGradeCurve; // flow diversion curve index + int onSagCurve; // flow rating curve index +} TCustomInlet; + +// Inlet design object +typedef struct +{ + char * ID; // name assigned to inlet design + int type; // type of inlet used (grate, curb, etc) + TGrateInlet grateInlet; // length = 0 if not used + TSlottedInlet slottedInlet; // length = 0 if not used + TCurbInlet curbInlet; // length = 0 if not used + int customCurve; // curve index = -1 if not used +} TInletDesign; + + +// Inlet performance statistics +typedef struct +{ + int flowPeriods; // # periods with approach flow + int capturePeriods; // # periods with captured flow + int backflowPeriods; // # periods with backflow + double peakFlow; // peak flow seen by inlet (cfs) + double peakFlowCapture; // capture efficiency at peak flow + double avgFlowCapture; // average capture efficiency + double bypassFreq; // frequency of bypass flow +} TInletStats; + +// Inlet list object +struct TInlet +{ + int linkIndex; // index of conduit link with the inlet + int designIndex; // index of inlet's design + int nodeIndex; // index of node receiving captured flow + int numInlets; // # inlets on each side of street or in channel + int placement; // whether inlet is on-grade or on-sag + double clogFactor; // fractional degree of inlet clogging + double flowLimit; // inlet flow restriction (cfs) + double localDepress; // local gutter depression (ft) + double localWidth; // local depression width (ft) + + double flowFactor; // flow = flowFactor * (flow spread)^2.67 + double flowCapture; // captured flow rate (cfs) + double backflow; // backflow from capture node (cfs) + double backflowRatio; // inlet backflow / capture node overflow + TInletStats stats; // inlet performance statistics + TInlet * nextInlet; // next inlet in list +}; + +// Shared inlet variables +TInletDesign * InletDesigns; // array of available inlet designs +int InletDesignCount; // number of inlet designs +int UsesInlets; // TRUE if project uses inlets + +//----------------------------------------------------------------------------- +// Enumerations +//----------------------------------------------------------------------------- + +enum InletType { + GRATE_INLET, CURB_INLET, COMBO_INLET, SLOTTED_INLET, + DROP_GRATE_INLET, DROP_CURB_INLET, CUSTOM_INLET +}; + +enum GrateType { + P50, P50x100, P30, CURVED_VANE, TILT_BAR_45, + TILT_BAR_30, RETICULINE, GENERIC +}; + +enum InletPlacementType { AUTOMATIC, ON_GRADE, ON_SAG }; + +enum ThroatAngleType { HORIZONTAL_THROAT, INCLINED_THROAT, VERTICAL_THROAT }; + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static char* InletTypeWords[] = + {"GRATE", "CURB", "", "SLOTTED", "DROP_GRATE", "DROP_CURB", "CUSTOM", NULL}; + +static char* GrateTypeWords[] = + {"P_BAR-50", "P_BAR-50x100", "P_BAR-30", "CURVED_VANE", "TILT_BAR-45", "TILT_BAR-30", + "RETICULINE", "GENERIC", NULL}; + +static char* ThroatAngleWords[] = + {"HORIZONTAL", "INCLINED", "VERTICAL", NULL}; + +static char *PlacementTypeWords[] = + {"AUTOMATIC", "ON_GRADE", "ON_SAG"}; + +// Coefficients for cubic polynomials fitted to Splash Over Velocity v. +// Grate Length curves in Chart 5B of HEC-22 manual taken from Denver +// UDFCD manual. +static const double SplashCoeffs[][4] = { + {2.22, 4.03, 0.65, 0.06}, //P_BAR-50 + {0.74, 2.44, 0.27, 0.02}, //P_BAR-50x100 + {1.76, 3.12, 0.45, 0.03}, //P_BAR-30 + {0.30, 4.85, 1.31, 0.15}, //Curved_Vane + {0.99, 2.64, 0.36, 0.03}, //Tilt_Bar-45 + {0.51, 2.34, 0.2, 0.01}, //Tilt_Bar-30 + {0.28, 2.28, 0.18, 0.01}}; //Reticuline + +// Grate opening ratios (Chart 9B of HEC-22 manual) +static const double GrateOpeningRatios[] = { + 0.90, //P_BAR-50 + 0.80, //P_BAR-50x100 + 0.60, //P_BAR-30 + 0.35, //Curved_Vane + 0.17, //Tilt_Bar-45 (assumed) + 0.34, //Tilt_Bar-30 + 0.80, //Reticuline + 1.00}; //Generic + +//----------------------------------------------------------------------------- +// Imported Variables +//----------------------------------------------------------------------------- +extern TLinkStats* LinkStats; // defined in STATS.C +extern TNodeStats* NodeStats; // defined in STATS.C + +//----------------------------------------------------------------------------- +// Local Shared Variables +//----------------------------------------------------------------------------- +// Variables as named in the HEC-22 manual. +static double Sx; // street cross slope +static double SL; // conduit longitudinal slope +static double Sw; // gutter + cross slope +static double a; // street gutter depression (ft) +static double W; // street gutter width (ft) +static double T; // top width of flow spread (ft) +static double n; // Manning's roughness coeff. + +// Additional variables +static int Nsides; // 1- or 2-sided street +static double Tcrown; // distance from street curb to crown (ft) +static double Beta; // = 1.486 * sqrt(SL) / n +static double Qfactor; // factor f in Izzard's eqn. Q = f*T^2.67 +static TXsect* xsect; // cross-section data of inlet's conduit +static double* InletFlow; // captured inlet flow received by each node +static TInlet* FirstInlet; // head of list of deployed inlets + +//----------------------------------------------------------------------------- +// External functions (declared in inlet.h) +//----------------------------------------------------------------------------- +// inlet_create called by createObjects in project.c +// inlet_delete called by deleteObjects in project.c +// inlet_readDesignParams called by parseLine in input.c +// inlet_readUsageParams called by parseLine in input.c +// inlet_validate called by project_validate +// inlet_findCapturedFlows called by routing_execute +// inlet_adjustQualInflows called by routing_execute +// inlet_adjustQualOutflows called by routing execute +// inlet_writeStatsReport called by statsrpt_writeReport +// inlet_capturedFlow called by findLinkMassFlow in qualrout.c + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int readGrateInletParams(int inletIndex, char* tok[], int ntoks); +static int readCurbInletParams(int inletIndex, char* tok[], int ntoks); +static int readSlottedInletParams(int inletIndex, char* tok[], int ntoks); +static int readCustomInletParams(int inletIndex, char* tok[], int ntoks); + +static void initInletStats(TInlet* inlet); +static void updateInletStats(TInlet* inlet, double q); +static void writeStreetStatsHeader(); +static void writeStreetStats(int link); + +static void getBackflowRatios(); +static double getInletArea(TInlet* inlet); + +static int getInletPlacement(TInlet* inlet, int node); +static void getConduitGeometry(TInlet* inlet); +static double getFlowSpread(double flow); +static double getEo(double slopeRatio, double spread, double gutterWidth); + +static double getCustomCapturedFlow(TInlet* inlet, double flow, double depth); +static double getOnGradeCapturedFlow(TInlet* inlet, double flow, double depth); +static double getOnGradeInletCapture(int inletIndex, double flow, double depth); +static double getGrateInletCapture(int inletIndex, double flow); +static double getCurbInletCapture(double flow, double length); + +static double getGutterFlowRatio(double gutterWidth); +static double getGutterAreaRatio(double grateWidth, double area); +static double getSplashOverVelocity(int grateType, double grateLength); + +static double getOnSagCapturedFlow(TInlet* inlet, double flow, double depth); +static double getOnSagInletCapture(int inletIndex, double depth); +static void findOnSagGrateFlows(int inletIndex, double depth, + double *weirFlow, double *orificeFlow); +static void findOnSagCurbFlows(int inletIndex, double depth, + double openingLength, double *weirFlow, + double *orificeFlow); +static double getCurbOrificeFlow(double flowDepth, double openingHeight, + double openingLength, int throatAngle); +static double getOnSagSlottedFlow(int inletIndex, double depth); + +//============================================================================= + +int inlet_create(int numInlets) +// +// Input: numInlets = number of inlet designs to create +// Output: none +// Purpose: creats a collection of inlet designs. +// +{ + int i; + + InletDesigns = NULL; + InletFlow = NULL; + InletDesignCount = 0; + UsesInlets = FALSE; + FirstInlet = NULL; + InletDesigns = (TInletDesign *)calloc(numInlets, sizeof(TInletDesign)); + if (InletDesigns == NULL) return ERR_MEMORY; + InletDesignCount = numInlets; + + InletFlow = (double *)calloc(Nobjects[NODE], sizeof(double)); + if (InletFlow == NULL) return ERR_MEMORY; + + for (i = 0; i < InletDesignCount; i++) + { + InletDesigns[i].customCurve = -1; + InletDesigns[i].curbInlet.length = 0.0; + InletDesigns[i].grateInlet.length = 0.0; + InletDesigns[i].slottedInlet.length = 0.0; + InletDesigns[i].type = CUSTOM_INLET; + } + return 0; +} + +//============================================================================= + +void inlet_delete() +// +// Input: none +// Output: none +// Purpose: frees all memory allocated for inlet analysis. +// +{ + TInlet* inlet = FirstInlet; + TInlet* nextInlet; + while (inlet) + { + nextInlet = inlet->nextInlet; + free(inlet); + inlet = nextInlet; + } + FirstInlet = NULL; + FREE(InletFlow); + FREE(InletDesigns); +} + +//============================================================================= + +int inlet_readDesignParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts a set of inlet design parameters from a tokenized line +// of the [INLETS] section of a SWMM input file. +// +// Format of input line is: +// ID GRATE Length Width GrateType (OpenArea) (SplashVeloc) +// ID CURB Length Height (ThroatType) +// ID SLOTTED Length Width +// ID DROP_GRATE Length Width GrateType (OpenArea) (SplashVeloc) +// ID DROP_CURB Length Height +// ID CUSTOM CurveID +// +{ + int i; + + // --- check for minimum number of tokens + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that design ID already registered in project + i = project_findObject(INLET, tok[0]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[0]); + InletDesigns[i].ID = project_findID(INLET, tok[0]); + + // --- retrieve type of inlet design + InletDesigns[i].type = findmatch(tok[1], InletTypeWords); + + // --- read inlet's design parameters + switch (InletDesigns[i].type) + { + case GRATE_INLET: + case DROP_GRATE_INLET: + return readGrateInletParams(i, tok, ntoks); + case CURB_INLET: + case DROP_CURB_INLET: + return readCurbInletParams(i, tok, ntoks); + case SLOTTED_INLET: + return readSlottedInletParams(i, tok, ntoks); + case CUSTOM_INLET: + return readCustomInletParams(i, tok, ntoks); + default: return error_setInpError(ERR_KEYWORD, tok[1]); + } + return 0; +} +//============================================================================= + +int inlet_readUsageParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts inlet usage parameters from a tokenized line +// of the [INLET_USAGE] section of a SWMM input file. +// +// Format of input line is: +// linkID inletID nodeID (#Inlets %Clog Qmax aLocal wLocal placement) +// where +// linkID = ID name of link containing the inlet +// inletID = ID name of inlet design being used +// nodeID = ID name of node receiving captured flow +// #Inlets = number of identical inlets used (default = 1) +// %Clog = percent that inlet is clogged +// Qmax = maximum flow that inlet can capture (default = 0 (no limit)) +// aLocal = local gutter depression (ft or m) (default = 0) +// wLocal = width of local gutter depression (ft or m) (default = 0) +// placement = ON_GRADE, ON_SAG, or AUTO (the default) +// +{ + int linkIndex, designIndex, nodeIndex, numInlets = 1; + int placement = AUTOMATIC; + double flowLimit = 0.0, pctClogged = 0.0; + double aLocal = 0.0, wLocal = 0.0; + TInlet* inlet; + + // --- check that inlet's link exists + if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); + linkIndex = project_findObject(LINK, tok[0]); + if (linkIndex < 0) return error_setInpError(ERR_NAME, tok[0]); + + // --- check that inlet design type exists + designIndex = project_findObject(INLET, tok[1]); + if (designIndex < 0) return error_setInpError(ERR_NAME, tok[1]); + + // --- check that receiving node exists + nodeIndex = project_findObject(NODE, tok[2]); + if (nodeIndex < 0) return error_setInpError(ERR_NAME, tok[2]); + + // --- get number of inlets + if (ntoks > 3) + if (!getInt(tok[3], &numInlets) || numInlets < 1) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- get flow limit & percent clogged + if (ntoks > 4) + { + if (!getDouble(tok[4], &pctClogged) || pctClogged < 0.0 + || pctClogged > 99.) + return error_setInpError(ERR_NUMBER, tok[4]); + } + if (ntoks > 5) + if (!getDouble(tok[5], &flowLimit) || flowLimit < 0.0) + return error_setInpError(ERR_NUMBER, tok[5]); + + // --- get local depression parameters + if (ntoks > 6) + if (!getDouble(tok[6], &aLocal) || aLocal < 0.0) + return error_setInpError(ERR_NUMBER, tok[6]); + if (ntoks > 7) + if (!getDouble(tok[7], &wLocal) || wLocal < 0.0) + return error_setInpError(ERR_NUMBER, tok[7]); + + // --- get inlet placement + if (ntoks > 8) + { + placement = findmatch(tok[8], PlacementTypeWords); + if (placement < 0) return error_setInpError(ERR_KEYWORD, tok[8]); + } + + // --- create an inlet usage object for the link + inlet = Link[linkIndex].inlet; + if (inlet == NULL) + { + inlet = (TInlet *)malloc(sizeof(TInlet)); + if (!inlet) return error_setInpError(ERR_MEMORY, ""); + Link[linkIndex].inlet = inlet; + inlet->nextInlet = FirstInlet; + FirstInlet = inlet; + } + + // --- save inlet usage parameters + inlet->linkIndex = linkIndex; + inlet->designIndex = designIndex; + inlet->nodeIndex = nodeIndex; + inlet->numInlets = numInlets; + inlet->placement = placement; + inlet->clogFactor = 1.0 - (pctClogged / 100.); + inlet->flowLimit = flowLimit / UCF(FLOW); + inlet->localDepress = aLocal / UCF(LENGTH); + inlet->localWidth = wLocal / UCF(LENGTH); + inlet->flowFactor = 0.0; + inlet->backflowRatio = 0.0; + initInletStats(inlet); + UsesInlets = TRUE; + return 0; +} + +//============================================================================= + +void inlet_validate() +// +// Input: none +// Output: none +// Purpose: checks that inlets have been assigned to conduits with proper +// cross section shapes and counts the number of inlets that each +// node receives either bypased or captured flow from. +// +{ + int i, j, inletType, inletValid; + TInlet* inlet; + TInlet* prevInlet; + + // --- traverse the list of inlets placed in conduits + if (!UsesInlets) return; + prevInlet = FirstInlet; + inlet = FirstInlet; + while (inlet) + { + // --- check that inlet's conduit can accept the inlet's type + inletValid = FALSE; + i = inlet->linkIndex; + xsect = &Link[i].xsect; + inletType = InletDesigns[inlet->designIndex].type; + if (inletType == CUSTOM_INLET) + { + j = InletDesigns[inlet->designIndex].customCurve; + if (j >= 0) + { + if (Curve[j].curveType == DIVERSION_CURVE || + Curve[j].curveType == RATING_CURVE) + inletValid = TRUE; + } + } + else if ((xsect->type == TRAPEZOIDAL || xsect->type == RECT_OPEN) && + (inletType == DROP_GRATE_INLET || + inletType == DROP_CURB_INLET)) + inletValid = TRUE; + else if (xsect->type == STREET_XSECT && + inletType != DROP_GRATE_INLET && + inletType != DROP_CURB_INLET) + inletValid = TRUE; + + // --- if inlet placement is valid then + if (inletValid) + { + // --- record that receptor node has inlets + Node[Link[i].node2].inlet = BYPASS; + Node[inlet->nodeIndex].inlet = CAPTURE; + + // --- initialize inlet's backflow + inlet->backflow = 0.0; + + // --- compute street inlet's flow factor for Izzard's eqn. + // (used in Q = flowFactor * Spread^2.67 equation) + getConduitGeometry(inlet); + inlet->flowFactor = (0.56/n) * pow(SL,0.5) * pow(Sx,1.67); + + // --- save reference to current inlet & continue to next inlet + prevInlet = inlet; + inlet = inlet->nextInlet; + } + + // --- if inlet placement is not valid then issue a warning message + // and remove the inlet from the conduit + else + { + report_writeWarningMsg(WARN12, Link[i].ID); + if (inlet == FirstInlet) + { + FirstInlet = inlet->nextInlet; + prevInlet = FirstInlet; + free(inlet); + inlet = FirstInlet; + } + else + { + prevInlet->nextInlet = inlet->nextInlet; + free(inlet); + inlet = prevInlet->nextInlet; + } + Link[i].inlet = NULL; + } + } + + // --- determine how capture node's overflow is split between its inlets + getBackflowRatios(); +} + +//============================================================================= + +void inlet_findCapturedFlows(double tStep) +// +// Input: tStep = current flow routing time step (sec) +// Output: none +// Purpose: computes flow captured by each inlet and adjusts the +// lateral flows of the inlet's bypass and capture nodes accordingly. +// +// This function is called after regular lateral flows to all nodes have been +// set but before a flow routing step has been taken. +{ + int i, j, m, placement; + double q; + TInlet *inlet; + + // --- For non-DW routing find conduit flow into each node + // (used to limit max. amount of on-sag capture) + if (!UsesInlets) return; + memset(InletFlow, 0, Nobjects[NODE]*sizeof(double)); + if (RouteModel != DW) + { + for (j = 0; j < Nobjects[NODE]; j++) + Node[j].inflow = MAX(0., Node[j].newLatFlow); + for (i = 0; i < Nobjects[LINK]; i++) + Node[Link[i].node2].inflow += MAX(0.0, Link[i].newFlow); + } + + // --- loop through each inlet + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- identify indexes of inlet's bypass (j) and capture (m) nodes + i = inlet->linkIndex; + j = Link[i].node2; + m = inlet->nodeIndex; + + // --- get inlet's placement (ON_GRADE or ON_SAG) + placement = getInletPlacement(inlet, j); + + // --- find flow captured by a Custom inlet + if (InletDesigns[inlet->designIndex].type == CUSTOM_INLET) + { + q = fabs(Link[i].newFlow); + inlet->flowCapture = getCustomCapturedFlow(inlet, q, Node[j].newDepth); + } + + // --- find flow captured by on-grade inlet + else if (placement == ON_GRADE) + { + q = fabs(Link[i].newFlow); + inlet->flowCapture = getOnGradeCapturedFlow(inlet, q, Node[j].newDepth); + } + + // --- find flow captured by on-sag inlet + else + { + q = Node[j].inflow; + inlet->flowCapture = getOnSagCapturedFlow(inlet, q, Node[j].newDepth); + } + if (fabs(inlet->flowCapture) < FUDGE) inlet->flowCapture = 0.0; + + // --- add to total flow captured by inlet's node + InletFlow[j] += inlet->flowCapture; + + // --- capture node's overflow becomes inlet's backflow + inlet->backflow = Node[m].overflow * inlet->backflowRatio; + if (fabs(inlet->backflow) < FUDGE) inlet->backflow = 0.0; + } + + // --- make second pass through each inlet + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- identify indexes of inlet's bypass (j) and capture (m) nodes + i = inlet->linkIndex; + j = Link[i].node2; + m = inlet->nodeIndex; + + // --- for on-sag placement under non-DW routing, captured flow + // is limited to inlet's share of bypass node's inflow plus + // any stored volume + if (RouteModel != DW && getInletPlacement(inlet, j) == ON_SAG) + { + q = Node[j].newVolume / tStep; + q += MAX(Node[j].inflow, 0.0); + if (InletFlow[j] > q) + inlet->flowCapture *= q / InletFlow[j]; + } + + // --- adjust lateral flows at bypass and capture nodes + // (subtract captured flow from bypass node, add it to capture + // node, and add any backflow to bypass node) + Node[j].newLatFlow -= (inlet->flowCapture - inlet->backflow); + Node[m].newLatFlow += inlet->flowCapture; + + // --- update inlet's performance if reporting has begun + if (getDateTime(NewRoutingTime) > ReportStart) + updateInletStats(inlet, fabs(Link[i].newFlow)); + } +} + +//============================================================================= + +void inlet_adjustQualInflows() +// +// Input: none +// Output: none +// Purpose: adjusts accumulated flow rates and pollutant mass inflows at each +// inlet's bypass and capture nodes after a flow routing step has +// been taken prior to a quality routing step. +// +{ + int i, j, m, p; + double qNet; + TInlet* inlet; + + if (!UsesInlets) return; + if (IgnoreQuality || Nobjects[POLLUT] == 0) return; + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- identify indexes of inlet's bypass (j) and capture (m) nodes + i = inlet->linkIndex; + j = Link[i].node2; + m = inlet->nodeIndex; + + // --- there's a net flow from the bypass to the capture node + qNet = inlet->flowCapture - inlet->backflow; + if (qNet > 0.0) + { + // --- add net capture flow to capture node's accumulated flow + // inflow for quality routing + Node[m].qualInflow += qNet; + + // --- and do the same for pollutant mass flows + // (Node[m].newQual is the mass inflow accumulator for node m) + for (p = 0; p < Nobjects[POLLUT]; p++) + Node[m].newQual[p] += qNet * Node[j].oldQual[p]; + } + + // --- there's a net backflow from the capture to the bypass node + else + { + // --- add the backflow flow rate and pollutant mass flow to the + // bypass node's accumulated flow and pollutant mass inflow + qNet = -qNet; + Node[j].qualInflow += qNet; + for (p = 0; p < Nobjects[POLLUT]; p++) + Node[j].newQual[p] += qNet * Node[m].oldQual[p]; + } + } +} + +//============================================================================= + +void inlet_adjustQualOutflows() +// +// Input: none +// Output: none +// Purpose: adjusts mass balance totals after a complete routing step has been +// taken so as not to treat inlet transfer flows as system outflows. +// +{ + int j, p; + double q, w; + TInlet* inlet; + + // --- these variables, declared in massbal.c, accumulate system-wide flow and + // pollutant mass fluxes over a time step to use in mass balances + extern TRoutingTotals StepFlowTotals; + extern TRoutingTotals* StepQualTotals; + + // --- examine each node + for (j = 0; j < Nobjects[NODE]; j++) + { + // --- node receives captured flow from an inlet + if (Node[j].inlet == CAPTURE) + { + // --- node also has an overflow (e.g., it's a surcharged sewer node) + q = Node[j].overflow; + if (q > 0.0) + { + // --- remove overflow from system flooding total since it does + // not leave the system (it is sent to inlet's bypass node) + StepFlowTotals.flooding -= q; + + // --- also remove pollutant overflow mass from system totals + if (!IgnoreQuality) + for (p = 0; p < Nobjects[POLLUT]; p++) + { + w = q * Node[j].newQual[p]; + StepQualTotals[p].flooding -= w; + } + } + } + } + + // --- for WQ analysis, examine each inlet's bypass node + if (!IgnoreQuality && Nobjects[POLLUT] > 0) + { + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + j = Link[inlet->linkIndex].node2; + + // --- inlet has net positive flow capture leading to + // node having a net negative lateral inflow + q = inlet->flowCapture - inlet->backflow; + if (q > 0.0 && Node[j].newLatFlow < 0.0) + + // --- remove the pollutant mass in the captured flow from + // the system totals since it does not leave the system + // (it is sent to the inlet's capture node) + for (p = 0; p < Nobjects[POLLUT]; p++) + { + w = q * Node[j].newQual[p]; + StepQualTotals[p].outflow -= w; + } + } + } +} + +//============================================================================= + +void inlet_writeStatsReport() +// +// Input: none +// Output: none +// Purpose: writes table of street & inlet flow statistics to SWMM's report file. +// +{ + int j, header = FALSE; + + if (Nobjects[STREET] == 0) return; + for (j = 0; j < Nobjects[LINK]; j++) + { + if (Link[j].xsect.type == STREET_XSECT) + { + if (!header) + { + writeStreetStatsHeader(); + header = TRUE; + } + writeStreetStats(j); + } + } + report_writeLine(""); +} + +//============================================================================= + +double inlet_capturedFlow(int i) +// +// Input: i = a link index +// Output: returns captured flow rate (cfs) +// Purpose: gets the current flow captured by an inlet. +// +{ + if (Link[i].inlet) return Link[i].inlet->flowCapture; + return 0.0; +} + +//============================================================================= + +int readGrateInletParams(int i, char* tok[], int ntoks) +{ +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts a grate's inlet parameters from a set of string tokens. +// + int grateType; + double width, length, areaRatio = 0.0, vSplash = 0.0; + + // --- check for enough tokens + if (ntoks < 5) return error_setInpError(ERR_ITEMS, ""); + + // --- retrieve length & width + if (!getDouble(tok[2], &length) || length <= 0.0) + return error_setInpError(ERR_NUMBER, tok[2]); + if (!getDouble(tok[3], &width) || width <= 0.0) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- retrieve grate type + grateType = findmatch(tok[4], GrateTypeWords); + if (grateType < 0) return error_setInpError(ERR_KEYWORD, tok[4]); + + // --- only read open area & splash velocity for GENERIC type grate + if (grateType == GENERIC) + { + if (ntoks < 6) return error_setInpError(ERR_ITEMS, ""); + if (!getDouble(tok[5], &areaRatio) || areaRatio <= 0.0 + || areaRatio > 1.0) return error_setInpError(ERR_NUMBER, tok[5]); + if (ntoks > 6) + { + if (!getDouble(tok[6], &vSplash) || vSplash < 0.0) + return error_setInpError(ERR_NUMBER, tok[6]); + } + } + + // --- save grate inlet parameters + InletDesigns[i].grateInlet.length = length / UCF(LENGTH); + InletDesigns[i].grateInlet.width = width / UCF(LENGTH); + InletDesigns[i].grateInlet.type = grateType; + InletDesigns[i].grateInlet.fracOpenArea = areaRatio; + InletDesigns[i].grateInlet.splashVeloc = vSplash / UCF(LENGTH); + + // --- check if grate is part of a combo inlet + if (InletDesigns[i].type == GRATE_INLET && + InletDesigns[i].curbInlet.length > 0.0) + InletDesigns[i].type = COMBO_INLET; + return 0; +} + +//============================================================================= + +int readCurbInletParams(int i, char* tok[], int ntoks) +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts curb opening inlet parameters from a set of string tokens. +// +{ + int throatAngle; + double height, length; + + // --- check for enough tokens + if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); + + // --- retrieve length & width of opening + if (!getDouble(tok[2], &length) || length <= 0.0) + return error_setInpError(ERR_NUMBER, tok[2]); + if (!getDouble(tok[3], &height) || height <= 0.0) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- retrieve type of throat angle for curb inlet + throatAngle = VERTICAL_THROAT; + if (InletDesigns[i].type == CURB_INLET && ntoks > 4) + { + throatAngle = findmatch(tok[4], ThroatAngleWords); + if (throatAngle < 0) return error_setInpError(ERR_KEYWORD, tok[4]); + } + + // ---- save curb opening inlet parameters + InletDesigns[i].curbInlet.length = length / UCF(LENGTH); + InletDesigns[i].curbInlet.height = height / UCF(LENGTH); + InletDesigns[i].curbInlet.throatAngle = throatAngle; + + // --- check if curb inlet is part of a combo inlet + if (InletDesigns[i].type == CURB_INLET && + InletDesigns[i].grateInlet.length > 0.0) + InletDesigns[i].type = COMBO_INLET; + return 0; +} + +//============================================================================= + +int readSlottedInletParams(int i, char* tok[], int ntoks) +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts slotted drain inlet parameters from a set of string tokens. +// +{ + double width, length; + + // --- check for enough tokens + if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); + + // --- retrieve length and width + if (!getDouble(tok[2], &length) || length <= 0.0) + return error_setInpError(ERR_NUMBER, tok[2]); + if (!getDouble(tok[3], &width) || width <= 0.0) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- save slotted inlet parameters + InletDesigns[i].slottedInlet.length = length / UCF(LENGTH); + InletDesigns[i].slottedInlet.width = width / UCF(LENGTH); + return 0; +} + +//============================================================================= + +int readCustomInletParams(int i, char* tok[], int ntoks) +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts custom inlet parameters from a set of string tokens. +// +{ + int c; // capture curve index + + if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); + else + { + c = project_findObject(CURVE, tok[2]); + if (c < 0) return error_setInpError(ERR_NAME, tok[2]); + } + InletDesigns[i].customCurve = c; + return 0; +} + +//============================================================================= + +void initInletStats(TInlet* inlet) +// +// Input: inlet = an inlet object placed in a conduit link +// Output: none +// Purpose: initializes the performance statistics of an inlet. +// +{ + if (inlet) + { + inlet->flowCapture = 0.0; + inlet->backflow = 0.0; + inlet->stats.flowPeriods = 0; + inlet->stats.capturePeriods = 0; + inlet->stats.backflowPeriods = 0; + inlet->stats.peakFlow = 0.0; + inlet->stats.peakFlowCapture = 0; + inlet->stats.avgFlowCapture = 0; + inlet->stats.bypassFreq = 0; + } +} + +//============================================================================= + +void updateInletStats(TInlet* inlet, double q) +// +// Input: inlet = an inlet object placed in a conduit link +// q = inlet's approach flow (cfs) +// Output: none +// Purpose: updates the performance statistics of an inlet. +// +{ + double qCapture = inlet->flowCapture, + qBackflow = inlet->backflow, + qNet = qCapture - qBackflow, + qBypass = q - qNet, + fCapture = 0.0; + + // --- check for no flow condition + if (q < MIN_RUNOFF_FLOW && qBackflow <= 0.0) return; + inlet->stats.flowPeriods++; + + // --- there is positive net flow from inlet to capture node + if (qNet > 0.0) + { + inlet->stats.capturePeriods++; + fCapture = qNet / q; + fCapture = MIN(fCapture, 1.0); + inlet->stats.avgFlowCapture += fCapture; + if (qBypass > MIN_RUNOFF_FLOW) inlet->stats.bypassFreq++; + } + + // --- otherwise inlet receives backflow from capture node + else inlet->stats.backflowPeriods++; + + // --- update peak flow stats + if (q > inlet->stats.peakFlow) + { + inlet->stats.peakFlow = q; + inlet->stats.peakFlowCapture = fCapture * 100.0; + } +} + +//============================================================================= + +void writeStreetStatsHeader() +// +// Input: none +// Output: none +// Purpose: writes column headers for Street Flow Summary table to SWMM's report file. +// +{ + report_writeLine(""); + report_writeLine("*******************"); + report_writeLine("Street Flow Summary"); + report_writeLine("*******************"); + report_writeLine(""); + fprintf(Frpt.file, +"\n ----------------------------------------------------------------------------------------------------------------------" +"\n Peak Maximum Maximum Peak Flow Average Bypass BackFlow" +"\n Flow Spread Depth Inlet Inlet Capture Capture Frequency Frequency"); + if (UnitSystem == US) fprintf(Frpt.file, +"\n Street Conduit %3s ft ft Design Location %% %% %% %%", + FlowUnitWords[FlowUnits]); + else fprintf(Frpt.file, +"\n Street Conduit %3s m m Design Location %% %% %% %%", + FlowUnitWords[FlowUnits]); + fprintf(Frpt.file, +"\n ----------------------------------------------------------------------------------------------------------------------"); +} + +//============================================================================= + +void writeStreetStats(int link) +// +// Input: link = index of a conduit link containing an inlet +// Output: none +// Purpose: writes flow statistics for a Street conduit and its inlet to +// SWMM's report file. +// +{ + int k, t, placement; + double maxSpread, maxDepth, maxFlow; + double fp, cp, afc = 0.0, bpf = 0.0; + TInlet* inlet; + + // --- retrieve street parameters + k = Link[link].subIndex; + t = Link[link].xsect.transect; + inlet = Link[link].inlet; + + // --- get recorded max flow and depth + maxFlow = LinkStats[link].maxFlow; + maxDepth = LinkStats[link].maxDepth; + + // --- SWMM's spread (flow width) at max depth + maxSpread = xsect_getWofY(&Link[link].xsect, maxDepth) / Street[t].sides; + maxSpread = MIN(maxSpread, Street[t].width); +/* + // HEC-22's spread based on max flow (doesn't account for backwater) + Sx = Street[t].slope; + a = Street[t].gutterDepression; + W = Street[t].gutterWidth; + n = Street[t].roughness; + Qfactor = (0.56 / n) * sqrt(Conduit[k].slope) * pow(Sx, 1.67); + maxSpread = getFlowSpread(maxFlow / Street[t].sides); + maxSpread = MIN(maxSpread, Street[t].width); +*/ + // --- write street stats + fprintf(Frpt.file, "\n %-16s", Link[link].ID); + fprintf(Frpt.file, " %9.3f", maxFlow * UCF(FLOW)); + fprintf(Frpt.file, " %9.3f", maxSpread * UCF(LENGTH)); + fprintf(Frpt.file, " %9.3f", maxDepth * UCF(LENGTH)); + + // --- write inlet stats + if (inlet) + { + fprintf(Frpt.file, " %-16s", InletDesigns[inlet->designIndex].ID); + placement = getInletPlacement(inlet, Link[inlet->linkIndex].node2); + if (placement == ON_GRADE) + fprintf(Frpt.file, " ON-GRADE"); + else + fprintf(Frpt.file, " ON-SAG "); + fp = inlet->stats.flowPeriods / 100.0; + if (fp > 0.0) + { + cp = inlet->stats.capturePeriods / 100.0; + fprintf(Frpt.file, " %9.2f", inlet->stats.peakFlowCapture); + if (cp > 0.0) + { + afc = inlet->stats.avgFlowCapture / cp; + bpf = inlet->stats.bypassFreq / cp; + } + fprintf(Frpt.file, " %9.2f", afc); + fprintf(Frpt.file, " %9.2f", bpf); + fprintf(Frpt.file, " %9.2f", inlet->stats.backflowPeriods / fp); + } + } +} + +//============================================================================= + +int getInletPlacement(TInlet* inlet, int j) +// +// Input: inlet = an inlet object placed in a conduit link +// j = index of inlet's bypass node +// Output: returns type of inlet placement +// Purpose: determines actual placement for an inlet with AUTOMATIC placement. +// +{ + if (inlet->placement == AUTOMATIC) + { + if (Node[j].degree > 0) return ON_GRADE; + else return ON_SAG; + } + else return inlet->placement; +} + +//============================================================================= + +void getConduitGeometry(TInlet* inlet) +// +// Input: inlet = an inlet object placed in a conduit link +// Output: none +// Purpose: assigns properties of an inlet's conduit to +// module-level shared variables used by other functions. +// +{ + int linkIndex = inlet->linkIndex; + int t, k = Link[linkIndex].subIndex; + + SL = Conduit[k].slope; // longitudinal slope + Beta = Conduit[k].beta; // 1.486 * sqrt(SL) / n + xsect = &Link[linkIndex].xsect; + + // --- if conduit has a Street cross section + if (xsect->type == STREET_XSECT) + { + t = xsect->transect; + Sx = Street[t].slope; // street cross slope + a = Street[t].gutterDepression; // gutter depression + W = Street[t].gutterWidth; // gutter width + n = Street[t].roughness; // street roughness + Nsides = Street[t].sides; // 1 or 2 sided street + Tcrown = Street[t].width; // distance from curb to crown + Qfactor = inlet->flowFactor; // factor used in Izzard's eqn. + + // --- add inlet's local depression to street's continuous depression + if (inlet && inlet->localDepress * inlet->localWidth > 0) + { + a += inlet->localDepress; // inlet depression + W = inlet->localWidth; // inlet depressed width + } + + // --- slope of depressed gutter section + if (W * a > 0.0) Sw = Sx + a / W; + else Sw = Sx; + } + + // --- conduit has rectangular or trapezoidal cross section + else + { + a = 0.0; + W = 0.0; + n = Conduit[k].roughness; + Nsides = 1; + Sx = 0.01; + Sw = Sx; + } +} + +//============================================================================= + +double getFlowSpread(double Q) +// +// Input: Q = conduit flow rate (cfs) +// Output: returns width of flow spread (ft) +// Purpose: computes width of flow spread across a Street cross section using +// HEC-22 equations derived from Izzard's form of the Manning eqn. +// +{ + int iter; + double f, f1, Sr, Ts1, Ts2, Tw, Qs, Eo; + + f = Qfactor; // = (0.56/n) * SL^0.5 * Sx^1.67 + + // --- no depressed curb + if (a == 0.0) + { + Ts1 = pow(Q / f, 0.375); //HEC-22 Eq(4-2) + } + else + { + // --- check if spread is within curb width + f1 = f * pow((a / W) / Sx, 1.67); + Tw = pow(Q / f1, 0.375); //HEC-22 Eq(4-2) + if (Tw <= W) Ts1 = Tw; + else + { + // --- spread extends beyond curb width + Sr = (Sx + a / W) / Sx; + iter = 1; + Ts1 = pow(Q / f, 0.375) - W; + if (Ts1 <= 0) Ts1 = Tw - W; + while (iter < 11) + { + Eo = getEo(Sr, Ts1, W); + Qs = (1.0 - Eo) * Q; //HEC-22 Eq(4-6) + Ts2 = pow(Qs / f, 0.375); //HEC-22 Eq(4-2) + if (fabs(Ts2 - Ts1) < 0.01) break; + Ts1 = Ts2; + iter++; + } + Ts1 = Ts2 + W; + } + } + return MIN(Ts1, Tcrown); +} + +//============================================================================= + +double getEo(double Sr, double Ts, double w) +// +// Input: Sr = ratio of gutter slope to street cross slope +// Ts = amount of flow spread outside of gutter width (ft) +// w = gutter width (ft) +// Output: returns ratio of gutter flow to total flow in street cross section +// Purpose: solves HEC-22 Eq. (4-4) for Eo with Ts/w substituted for +// (T/w) - 1 where Ts = T - w. +// +{ + double x; + x = Sr / (Ts / w); + x = pow((1.0 + x), 2.67) - 1.0; + x = 1.0 + Sr / x; + return 1.0 / x; +} + +//============================================================================= + +double getOnGradeCapturedFlow(TInlet* inlet, double q, double d) +// +// Input: inlet = an inlet object placed in a conduit link +// q = flow in link prior to any inlet capture (cfs) +// d = flow depth seen by inlet (ft) +// Output: returns flow captured by the inlet (cfs) +// Purpose: computes flow captured by an inlet placed on-grade. +// +// An inlet object placed in a conduit can have multiple inlets of +// the same type distributed along the conduit's length that all +// send their captured flow to the same sewer node. This function +// finds the total captured flow as each individual inlet is analyzed +// sequentially, where its approach flow has been reduced by the +// amount of flow captured by prior inlets. +{ + int i, + linkIndex; // index of link containing inlets + double qApproach, // single inlet's approach flow (cfs) + qc, // single inlet's captured flow (cfs) + qCaptured, // total flow captured by link's inlets (cfs) + qBypassed, // total flow bypassed by link's inlets (cfs) + qMax; // max. flow that a single inlet can capture (cfs) + + if (inlet->numInlets == 0) return 0.0; + linkIndex = inlet->linkIndex; + + // --- check that link has flow + qApproach = q; + if (qApproach < MIN_RUNOFF_FLOW) return 0.0; + + // --- store conduit geometry in shared variables + getConduitGeometry(inlet); + + // --- adjust flow for 2-sided street + qApproach /= Nsides; + qBypassed = qApproach; + qCaptured = 0.0; + + // --- set limit on max. flow captured per inlet + qMax = BIG; + if (inlet->flowLimit > 0.0) qMax = inlet->flowLimit; + + // --- evaluate each inlet + for (i = 1; i <= inlet->numInlets; i++) + { + qc = getOnGradeInletCapture(inlet->designIndex, qBypassed, d) * + inlet->clogFactor; + qc = MIN(qc, qMax); + qc = MIN(qc, qBypassed); + qCaptured += qc; + qBypassed -= qc; + if (qBypassed < MIN_RUNOFF_FLOW) break; + } + return qCaptured *= Nsides; +} + +//============================================================================= + +double getOnGradeInletCapture(int i, double Q, double d) +// +// Input: i = an InletDesigns index +// Q = flow rate seen by inlet (cfs) +// d = flow depth seen by inlet (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by a single on-grade inlet. +// +{ + double Q1 = Q, Qc = 0.0, Lsweep = 0.0, Lcurb = 0.0, Lgrate = 0.0; + + // --- drop curb inlet (in non-Street conduit) only operates in on sag mode + if (InletDesigns[i].type == DROP_CURB_INLET) + { + Qc = getOnSagInletCapture(i, d); + return MIN(Qc, Q); + } + + // --- drop grate inlet (in non-Street conduit) + if (InletDesigns[i].type == DROP_GRATE_INLET) + { + Qc = getGrateInletCapture(i, Q); + return MIN(Qc, Q); + } + + // --- Remaining inlet types apply to Street conduits + + // --- find flow spread + T = getFlowSpread(Q); + + // --- slotted inlet (behaves as a curb opening inlet per HEC-22) + if (InletDesigns[i].type == SLOTTED_INLET) + { + Qc = getCurbInletCapture(Q, InletDesigns[i].slottedInlet.length); + return MIN(Qc, Q); + } + + Lcurb = InletDesigns[i].curbInlet.length; + Lgrate = InletDesigns[i].grateInlet.length; + + // --- curb opening inlet + if (Lcurb > 0.0) + { + Lsweep = Lcurb - Lgrate; + if (Lsweep > 0.0) + { + Qc = getCurbInletCapture(Q1, Lsweep); + Q1 -= Qc; + } + } + + // --- grate inlet + if (Lgrate > 0.0 && Q1 > 0.0) + { + if (Q1 != Q) T = getFlowSpread(Q1); + Qc += getGrateInletCapture(i, Q1); + } + return Qc; +} + +//============================================================================= + +double getGrateInletCapture(int i, double Q) +// +// Input: i = inlet type index +// Q = flow rate seen by inlet (cfs) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-grade grate inlet. +// +{ + int grateType; + double Lg, // grate length (ft) + Wg, // grate width (ft) + A, // total cross section flow area (ft2) + Y, // flow depth (ft) + Eo, // ratio of gutter to total flow + V, // flow velocity (ft/s) + Vo, // splash-over velocity (ft/s) + Qo = Q, // flow over street area (cfs) + Rf = 1.0, // ratio of intercepted to total frontal flow + Rs = 0.0; // ratio of intercepted to total side flow + +// xsect, a, W, & Sx were from getConduitGeometry(). T was from getFlowSpread(). + + Lg = InletDesigns[i].grateInlet.length; + Wg = InletDesigns[i].grateInlet.width; + + // --- flow ratio for drop inlet + if (xsect->type == TRAPEZOIDAL || xsect->type == RECT_OPEN) + { + A = xsect_getAofS(xsect, Q / Beta); + Y = xsect_getYofA(xsect, A); + T = xsect_getWofY(xsect, Y); + Eo = Beta * pow(Y*Wg, 1.67) / pow(Wg + 2*Y, 0.67) / Q; + if (Wg > 0.99*xsect->yBot && xsect->type == TRAPEZOIDAL && xsect->sBot > 0.0) + { + Wg = xsect->yBot; + Sx = 1.0 / xsect->sBot; + } + } + + // --- flow ratio & area for conventional street gutter + else if (a == 0.0) + { + A = T * T * Sx / 2.0; + Eo = getGutterFlowRatio(Wg); // flow ratio based on grate width + if (T >= Tcrown) Qo = Qfactor * pow(Tcrown, 2.67); + } + + // --- flow ratio & area for composite street gutter + else + { + // --- spread confined to gutter + if (T <= W) A = T * T * Sw / 2.0; + + // --- spread beyond gutter width + else A = (T * T * Sx + a * W) / 2.0; + + // flow ratio based on gutter width corrected for grate width + Eo = getGutterFlowRatio(W); + if (Eo < 1.0) + { + if (T >= Tcrown) + Qo = Qfactor * pow(Tcrown, 2.67) / (1.0 - Eo); + Eo = Eo * getGutterAreaRatio(Wg, A); //HEC-22 Eq(4-20a) + } + } + + // --- flow and splash-over velocities + V = Qo / A; + grateType = InletDesigns[i].grateInlet.type; + if (grateType < 0 || grateType == GENERIC) + Vo = InletDesigns[i].grateInlet.splashVeloc; + else + Vo = getSplashOverVelocity(grateType, Lg); + + // --- frontal flow capture efficiency + if (V > Vo) Rf = 1.0 - 0.09 * (V - Vo); //HEC-22 Eq(4-18) + + // --- side flow capture efficiency + if (Eo < 1.0) + { + Rs = 1.0 / (1.0 + (0.15 * pow(V, 1.8) / + Sx / pow(Lg, 2.3))); //HEC-22 Eq(4-19) + } + + // --- return total flow captured + return Q * (Rf * Eo + Rs * (1.0 - Eo)); //HEC-22 Eq(4-21) +} + +//============================================================================= + +double getCurbInletCapture(double Q, double L) +// +// Input: Q = flow rate seen by inlet (cfs) +// L = length of inlet opening (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-sag inlet. +// +{ + double Se = Sx, // equivalent gutter slope + Lt, // length for full capture + Sr, // ratio of gutter slope to cross slope + Eo = 0.0, // ratio of gutter to total flow + E = 1.0; // capture efficiency + +// a, W, Sx, Sw, SL, & n were from getConduitGeometry(). T was from getFlowSpread(). + + // --- for depressed gutter section + if (a > 0.0) + { + Sr = Sw / Sx; + Eo = getEo(Sr, T-W, W); + Se = Sx + Sw * Eo; //HEC-22 Eq(4-24) + } + + // --- opening length for full capture + Lt = 0.6 * pow(Q, 0.42) * pow(SL, 0.3) * + pow(1.0/(n*Se), 0.6); //HEC-22 Eq(4-22a) + + // --- capture efficiency for actual opening length + if (L < Lt) + { + E = 1.0 - (L/Lt); + E = 1 - pow(E, 1.8); //HEC-22 Eq(4-23) + } + E = MIN(E, 1.0); + E = MAX(E, 0.0); + return E * Q; +} + +//============================================================================= + +double getGutterFlowRatio(double w) +// +// Input: w = gutter width (ft) +// Output: returns a flow ratio +// Purpose: computes the ratio of flow over a width of gutter to the total +// flow in a street cross section. +// +{ + if (T <= w) return 1.0; + else if (a > 0.0) + return getEo(Sw / Sx, T - w, w); + else + return 1.0 - pow((1.0 - w / T), 2.67); //HEC-22 Eq(4-16) +} + +//============================================================================= + +double getGutterAreaRatio(double Wg, double A) +// +// Input: Wg = width of grate inlet (ft) +// A = total flow area (ft2) +// Output: returns an area ratio +// Purpose: computes the ratio of the flow area above a grate to the flow +// area above depressed gutter in a street cross section. +// +{ + double As, // flow area beyond gutter width (ft2) + Ag; // flow area over grate width (ft2) + + if (Wg >= W) return 1.0; + if (T <= Wg) return 1.0; + if (T <= W) return Wg / T; + As = 0.5 * SQR((T - W)) * Sx; + Ag = Wg * ( (T * Sx) + a - (Wg * Sw / 2.) ); + return Ag / (A - As); +} + +//============================================================================= + +double getSplashOverVelocity(int grateType, double L) +// +// Input: grateType = grate inlet type code +// L = length of grate inlet (ft) +// Output: returns a splash over velocity +// Purpose: computes the splash over velocity for a standard type of grate +// inlet as a function of its length. +// +{ + return SplashCoeffs[grateType][0] + + SplashCoeffs[grateType][1] * L - + SplashCoeffs[grateType][2] * L * L + + SplashCoeffs[grateType][3] * L * L * L; +} + +//============================================================================= + +double getOnSagCapturedFlow(TInlet* inlet, double q, double d) +// +// Input: inlet = an inlet object placed in a conduit link +// q = flow in link prior to any inlet capture (cfs) +// d = flow depth seen by inlet (ft) +// Output: returns flow captured by the inlet (cfs) +// Purpose: computes flow captured by an inlet placed on-sag. +// +{ + int linkIndex, designIndex, totalInlets; + double qCaptured = 0.0, qMax = HUGE; + + if (inlet->numInlets == 0) return 0.0; + totalInlets = Nsides * inlet->numInlets; + linkIndex = inlet->linkIndex; + designIndex = inlet->designIndex; + + // --- store conduit geometry in shared variables + getConduitGeometry(inlet); + + // --- set flow limit per inlet + if (inlet->flowLimit > 0.0) + qMax = inlet->flowLimit; + + // --- find nominal flow captured by inlet + qCaptured = getOnSagInletCapture(designIndex, fabs(d)); + + // --- find actual flow captured by the inlet + qCaptured *= inlet->clogFactor; + qCaptured = MIN(qCaptured, qMax); + qCaptured *= (double)totalInlets; + return qCaptured; +} + +//============================================================================= + +double getOnSagInletCapture(int i, double d) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-sag inlet. +// +{ + double Lsweep = 0.0, Lcurb = 0.0, Lgrate = 0.0; + double Qsw = 0.0, //Sweeper curb opening weir flow + Qso = 0.0, //Sweeper curb opening orifice flow + Qgw = 0.0, //Grate weir flow + Qgo = 0.0, //Grate orifice flow + Qcw = 0.0, //Curb opening weir flow + Qco = 0.0; //Curb opening orifice flow + + if (InletDesigns[i].slottedInlet.length > 0.0) + return getOnSagSlottedFlow(i, d); + + Lgrate = InletDesigns[i].grateInlet.length; + if (Lgrate > 0.0) findOnSagGrateFlows(i, d, &Qgw, &Qgo); + + Lcurb = InletDesigns[i].curbInlet.length; + if (Lcurb > 0.0) + { + Lsweep = Lcurb - Lgrate; + if (Lsweep > 0.0) findOnSagCurbFlows(i, d, Lsweep, &Qsw, &Qso); + if (Qgo > 0.0) findOnSagCurbFlows(i, d, Lgrate, &Qcw, &Qco); + } + return Qgw + Qgo + Qsw + Qso + Qco; +} + +//============================================================================= + +void findOnSagGrateFlows(int i, double d, double *Qw, double *Qo) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// Output: Qw = flow captured in weir mode (cfs) +// Qo = flow captured in orifice mode (cfs) +// Purpose: finds the flow captured by an on-sag grate inlet. +// +{ + int grateType = InletDesigns[i].grateInlet.type; + double Lg = InletDesigns[i].grateInlet.length; + double Wg = InletDesigns[i].grateInlet.width; + double P, // grate perimeter (ft) + Ao, // grate opening area (ft2) + di; // average flow depth across grate (ft) + + // --- for drop grate inlets + if (InletDesigns[i].type == DROP_GRATE_INLET) + { + di = d; + P = 2.0 * (Lg + Wg); + } + + // --- for gutter grate inlets: + else + { + // --- check for spread within grate width + if (d <= Wg * Sw) + Wg = d / Sw; + + // --- avergage depth over grate + di = d - (Wg / 2.0) * Sw; + + // --- effective grate perimeter + P = Lg + 2.0 * Wg; + } + + if (grateType == GENERIC) + Ao = Lg * Wg * InletDesigns[i].grateInlet.fracOpenArea; + else + Ao = Lg * Wg * GrateOpeningRatios[grateType]; + + // --- weir flow applies (based on depth where result of + // weir eqn. equals result of orifice eqn.) + + if (d <= 1.79 * Ao / P) + { + *Qw = 3.0 * P * pow(di, 1.5); //HEC-22 Eq(4-26) + } + + // --- orifice flow applies + else + { + *Qo = 0.67 * Ao * sqrt(2.0 * 32.16 * di); //HEC-22 Eq(4-27) + } +} + +//============================================================================= + +void findOnSagCurbFlows(int i, double d, double L, double *Qw, double *Qo) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// L = length of curb opening (ft) +// Output: Qw = flow captured in weir mode (cfs) +// Qo = flow captured in orifice mode (cfs) +// Purpose: finds the flow captured by an on-sag curb opening inlet. +// +{ + int throatAngle = InletDesigns[i].curbInlet.throatAngle; + double h = InletDesigns[i].curbInlet.height; + double Qweir, Qorif, P; + double dweir, dorif, r; + + // --- check for orifice flow + if (L <= 0.0) return; + if (InletDesigns[i].type == DROP_CURB_INLET) L = L * 4.0; + dorif = 1.4 * h; + if (d > dorif) + { + *Qo = getCurbOrificeFlow(d, h, L, throatAngle); + return; + } + + // --- for uniform cross slope or very long opening + if (a == 0.0 || L > 12.0) + { + // --- check for weir flow + dweir = h; + if (d < dweir) + { + *Qw = 3.0 * L * pow(d, 1.5); //HEC-22 Eq(4-30) + return; + } + else Qweir = 3.0 * L * pow(dweir, 1.5); + } + + // --- for depressed gutter + else + { + // --- check for weir flow + P = L + 1.8 * W; + dweir = h + a; + if (d < dweir) + { + *Qw = 2.3 * P * pow(d, 1.5); //HEC-22 Eq(4-28) + return; + } + else Qweir = 2.3 * P * pow(dweir, 1.5); + } + + // --- interpolate between Qweir at depth dweir and Qorif at depth dorif + Qorif = getCurbOrificeFlow(dorif, h, L, throatAngle); + r = (d - dweir) / (dorif - dweir); + *Qw = (1.0 -r) * Qweir; + *Qo = r * Qorif; +} + +//============================================================================= + +double getCurbOrificeFlow(double di, double h, double L, int throatAngle) +// +// Input: di = water level at lip of inlet opening (ft) +// h = height of curb opening (ft) +// L = length of curb opening (ft) +// throatAngle = type of throat angle in curb opening +// Output: return flow captured by inlet (cfs) +// Purpose: finds the flow captured by an on-sag curb opening inlet under +// orifice flow conditions. +// +{ + double d = di; + if (throatAngle == HORIZONTAL_THROAT) + d = di - h / 2.0; + else if (throatAngle == INCLINED_THROAT) + d = di + (h / 2.0) * 0.7071; + return 0.67 * h * L * sqrt(2.0 * 32.16 * d); //HEC-22 Eq(4-31a) +} + +//============================================================================= + +double getOnSagSlottedFlow(int i, double d) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-sag slotted inlet. +// +// Note: weir flow = orifice flow at d = 2.587 * inlet width +{ + double L = InletDesigns[i].slottedInlet.length; + double w = InletDesigns[i].slottedInlet.width; + + if (d <= 2.587 * w) + return 2.48 * L * pow(d, 1.5); //HEC-22 Eq(4-32) + else + return 0.8 * L * w * sqrt(64.32 * d); //HEC-22 Eq(4-33) +} + +//============================================================================= + +void getBackflowRatios() +// +// Input: none +// Output: overflow ratio for each inlet +// Purpose: finds the fraction of the overflow produced by an inlet's capture +// node that becomes backflow into the inlet. +// +// Note: when a capture node receives flow from two or more inlets +// its backflow is divided among the inlets based on: +// i) the fraction of total open area for standard inlets +// ii) the fraction of total number of inlets for custom inlets +{ + TInlet* inlet; + double area; + double f; + int n; + + // --- info for each node receiving flow from an inlet + typedef struct + { + int numInletLinks; // total # inlet links + int numStdInletLinks; // total # standard inlet links + int numCustomInlets; // # custom inlets + double totalInletArea; // open area of standard inlets + } TInletNode; + TInletNode* inletNodes = (TInletNode *) calloc(Nobjects[NODE], sizeof(TInletNode)); + if (inletNodes == NULL) return; + + // --- Finds each inlet's contribution to its capture node + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + n = inlet->nodeIndex; + inletNodes[n].numInletLinks++; + area = getInletArea(inlet); + if (area > 0.0) + { + inletNodes[n].numStdInletLinks++; + inletNodes[n].totalInletArea += area; + } + else + inletNodes[n].numCustomInlets += inlet->numInlets; + } + + // --- find fraction of capture node's overflow that becomes inlet backflow + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- f is ratio of links with standard inlets to all inlet links + // connected to receptor node n + n = inlet->nodeIndex; + f = (double) inletNodes[n].numStdInletLinks / + (double) inletNodes[n].numInletLinks; + + // --- backflow ratio depends if inlet is standard or custom (area = 0) + area = getInletArea(inlet); + if (area == 0.0) + inlet->backflowRatio = (double)inlet->numInlets / + (double)inletNodes[n].numCustomInlets * (1. - f); + else + inlet->backflowRatio = area / inletNodes[n].totalInletArea * f; + } + free(inletNodes); +} + +//============================================================================= + +double getInletArea(TInlet* inlet) +// +// Input: inlet = an inlet object placed in a conduit link +// Output: returns the unclogged open area of the inlet (ft2) +// Purpose: finds the total open flow area inlets placed in a conduit. +// +{ + double area = 0.0; + double curbLength; + int i = inlet->designIndex; + int grateType = InletDesigns[i].grateInlet.type; + + if (InletDesigns[i].grateInlet.length > 0.0) + { + area = InletDesigns[i].grateInlet.length * InletDesigns[i].grateInlet.width; + if (grateType == GENERIC) + area *= InletDesigns[i].grateInlet.fracOpenArea; + else + area *= GrateOpeningRatios[grateType]; + } + + curbLength = InletDesigns[i].curbInlet.length - InletDesigns[i].grateInlet.length; + if (curbLength > 0.0) + area += curbLength * InletDesigns[i].curbInlet.height; + + if (InletDesigns[i].slottedInlet.length > 0.0) + area = InletDesigns[i].slottedInlet.length * InletDesigns[i].slottedInlet.width; + return area * inlet->numInlets * inlet->clogFactor; +} + +//============================================================================= + +double getCustomCapturedFlow(TInlet* inlet, double q, double d) +{ + int i = inlet->designIndex; // inlet's position in InletDesigns array + int j; // counter for replicate inlets + int sides = 1; // number of sides for inlet's street (1 or 2) + int c; // an index into the Curve array + double qApproach, // inlet's approach flow (cfs) + qBypassed, // inlet's bypassed flow (cfs) + qCaptured, // inlet's captured flow (cfs) + qIncrement, // increment to captured flow (cfs) + qMax = HUGE; // user-supplied flow capture limit (cfs) + + if (inlet->numInlets == 0) return 0.0; + + // --- set limit on max. flow captured per inlet + qMax = BIG; + if (inlet->flowLimit > 0.0) qMax = inlet->flowLimit; + + // --- get number of sides to a street xsection + xsect = &Link[inlet->linkIndex].xsect; + if (xsect->type == STREET_XSECT) + sides = Street[xsect->transect].sides; + + // --- adjust flow for 2-sided street + qApproach = q / sides; + qBypassed = qApproach; + qCaptured = 0.0; + + // --- get index of inlet's capture curve + c = InletDesigns[i].customCurve; + if (c >= 0) + { + // --- curve is captured flow v. approach flow + if (Curve[c].curveType == DIVERSION_CURVE) + { + // --- add up incrmental capture of each replicate inlet + for (j = 1; j <= inlet->numInlets; j++) + { + qIncrement = inlet->clogFactor * + table_lookupEx(&Curve[c], qBypassed * UCF(FLOW)) / UCF(FLOW); + qIncrement = MIN(qIncrement, qMax); + qIncrement = MIN(qIncrement, qBypassed); + qCaptured += qIncrement; + qBypassed -= qIncrement; + if (qBypassed < MIN_RUNOFF_FLOW) break; + } + } + + // --- curve is captured flow v. downstream node depth + else if (Curve[c].curveType == RATING_CURVE) + { + qCaptured = inlet->numInlets * inlet->clogFactor * + table_lookupEx(&Curve[c], d * UCF(LENGTH)) / UCF(FLOW); + } + qCaptured *= sides; + } + return qCaptured; +} diff --git a/src/inlet.h b/src/inlet.h new file mode 100644 index 000000000..ca7830725 --- /dev/null +++ b/src/inlet.h @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// inlet.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Street/Channel Inlet Functions +// +//----------------------------------------------------------------------------- +#ifndef INLET_H +#define INLET_H + +typedef struct TInlet TInlet; + +int inlet_create(int nInlets); +void inlet_delete(); +int inlet_readDesignParams(char* tok[], int ntoks); +int inlet_readUsageParams(char* tok[], int ntoks); +void inlet_validate(); + +void inlet_findCapturedFlows(double tStep); +void inlet_adjustQualInflows(); +void inlet_adjustQualOutflows(); + +void inlet_writeStatsReport(); +double inlet_capturedFlow(int link); + +#endif diff --git a/src/input.c b/src/input.c new file mode 100644 index 000000000..3613297b9 --- /dev/null +++ b/src/input.c @@ -0,0 +1,928 @@ +//----------------------------------------------------------------------------- +// input.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Input data processing functions. +// +// Update History +// ============== +// Build 5.1.007: +// - Support added for climate adjustment input data. +// Build 5.1.011: +// - Support added for reading hydraulic event dates. +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for named variables & math expressions in control rules. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" +#include "lid.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const int MAXERRS = 100; // Max. input errors reported + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +static char *Tok[MAXTOKS]; // String tokens from line of input +static int Ntokens; // Number of tokens in line of input +static int Mobjects[MAX_OBJ_TYPES]; // Working number of objects of each type +static int Mnodes[MAX_NODE_TYPES]; // Working number of node objects +static int Mlinks[MAX_LINK_TYPES]; // Working number of link objects +static int Mevents; // Working number of event periods + +//----------------------------------------------------------------------------- +// External Functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// input_countObjects (called by swmm_open in swmm5.c) +// input_readData (called by swmm_open in swmm5.c) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int addObject(int objType, char* id); +static int getTokens(char *s); +static int parseLine(int sect, char* line); +static int readOption(char* line); +static int readTitle(char* line); +static int readControl(char* tok[], int ntoks); +static int readNode(int type); +static int readLink(int type); +static int readEvent(char* tok[], int ntoks); + +//============================================================================= + +int input_countObjects() +// +// Input: none +// Output: returns error code +// Purpose: reads input file to determine number of system objects. +// +{ + char line[MAXLINE+1]; // line from input data file + char wLine[MAXLINE+1]; // working copy of input line + char *tok; // first string token of line + int sect = -1, newsect; // input data sections + int errcode = 0; // error code + int errsum = 0; // number of errors found + int i; + long lineCount = 0; + + // --- initialize number of objects & set default values + if ( ErrorCode ) return ErrorCode; + error_setInpError(0, ""); + for (i = 0; i < MAX_OBJ_TYPES; i++) Nobjects[i] = 0; + for (i = 0; i < MAX_NODE_TYPES; i++) Nnodes[i] = 0; + for (i = 0; i < MAX_LINK_TYPES; i++) Nlinks[i] = 0; + controls_init(); + + // --- make pass through data file counting number of each object + while ( fgets(line, MAXLINE, Finp.file) != NULL ) + { + // --- skip blank lines & those beginning with a comment + lineCount++; + sstrncpy(wLine, line, MAXLINE); // make working copy of line + tok = strtok(wLine, SEPSTR); // get first text token on line + if ( tok == NULL ) continue; + if ( *tok == ';' ) continue; + + // --- check if line begins with a new section heading + if ( *tok == '[' ) + { + // --- look for heading in list of section keywords + newsect = findmatch(tok, SectWords); + if ( newsect >= 0 ) + { + sect = newsect; + continue; + } + else + { + sect = -1; + errcode = ERR_KEYWORD; + } + } + + // --- if in OPTIONS section then read the option setting + // otherwise add object and its ID name (tok) to project + if ( sect == s_OPTION ) errcode = readOption(line); + else if ( sect >= 0 ) errcode = addObject(sect, tok); + + // --- report any error found + if ( errcode ) + { + report_writeInputErrorMsg(errcode, sect, line, lineCount); + errsum++; + if (errsum >= MAXERRS ) break; + } + } + + // --- set global error code if input errors were found + if ( errsum > 0 ) ErrorCode = ERR_INPUT; + return ErrorCode; +} + +//============================================================================= + +int input_readData() +// +// Input: none +// Output: returns error code +// Purpose: reads input file to determine input parameters for each object. +// +{ + char line[MAXLINE+1]; // line from input data file + char wLine[MAXLINE+1]; // working copy of input line + char* comment; // ptr. to start of comment in input line + int sect, newsect; // data sections + int inperr, errsum; // error code & total error count + int lineLength; // number of characters in input line + int i; + long lineCount = 0; + + // --- initialize working item count arrays + // (final counts in Mobjects, Mnodes & Mlinks should + // match those in Nobjects, Nnodes and Nlinks). + if ( ErrorCode ) return ErrorCode; + error_setInpError(0, ""); + for (i = 0; i < MAX_OBJ_TYPES; i++) Mobjects[i] = 0; + for (i = 0; i < MAX_NODE_TYPES; i++) Mnodes[i] = 0; + for (i = 0; i < MAX_LINK_TYPES; i++) Mlinks[i] = 0; + Mevents = 0; + + // --- initialize starting date for all time series + for ( i = 0; i < Nobjects[TSERIES]; i++ ) + { + Tseries[i].lastDate = StartDate + StartTime; + } + + // --- read each line from input file + sect = 0; + errsum = 0; + rewind(Finp.file); + while ( fgets(line, MAXLINE, Finp.file) != NULL ) + { + // --- make copy of line and scan for tokens + lineCount++; + sstrncpy(wLine, line, MAXLINE); + Ntokens = getTokens(wLine); + + // --- skip blank lines and comments + if ( Ntokens == 0 ) continue; + if ( *Tok[0] == ';' ) continue; + + // --- check if max. line length exceeded + lineLength = (int)strlen(line); + if ( lineLength >= MAXLINE ) + { + // --- don't count comment if present + comment = strchr(line, ';'); + if ( comment ) lineLength = (int)(comment - line); // Pointer math here + if ( lineLength >= MAXLINE ) + { + inperr = ERR_LINE_LENGTH; + report_writeInputErrorMsg(inperr, sect, line, lineCount); + errsum++; + } + } + + // --- check if at start of a new input section + if (*Tok[0] == '[') + { + // --- match token against list of section keywords + newsect = findmatch(Tok[0], SectWords); + if (newsect >= 0) + { + // --- SPECIAL CASE FOR TRANSECTS + // finish processing the last set of transect data + if ( sect == s_TRANSECT ) + transect_validate(Nobjects[TRANSECT]-1); + + // --- begin a new input section + sect = newsect; + continue; + } + else + { + inperr = error_setInpError(ERR_KEYWORD, Tok[0]); + report_writeInputErrorMsg(inperr, sect, line, lineCount); + errsum++; + break; + } + } + + // --- otherwise parse tokens from input line + else + { + inperr = parseLine(sect, line); + if ( inperr > 0 ) + { + errsum++; + if ( errsum > MAXERRS ) report_writeLine(FMT19); + else report_writeInputErrorMsg(inperr, sect, line, lineCount); + } + } + + // --- stop if reach end of file or max. error count + if (errsum > MAXERRS) break; + } /* End of while */ + + // --- check for errors + if (errsum > 0) ErrorCode = ERR_INPUT; + return ErrorCode; +} + +//============================================================================= + +int addObject(int objType, char* id) +// +// Input: objType = object type index +// id = object's ID string +// Output: returns an error code +// Purpose: adds a new object to the project. +// +{ + int errcode = 0; + switch( objType ) + { + case s_RAINGAGE: + if ( !project_addObject(GAGE, id, Nobjects[GAGE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[GAGE]++; + break; + + case s_SUBCATCH: + if ( !project_addObject(SUBCATCH, id, Nobjects[SUBCATCH]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[SUBCATCH]++; + break; + + case s_AQUIFER: + if ( !project_addObject(AQUIFER, id, Nobjects[AQUIFER]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[AQUIFER]++; + break; + + case s_UNITHYD: + // --- the same Unit Hydrograph can span several lines + if ( project_findObject(UNITHYD, id) < 0 ) + { + if ( !project_addObject(UNITHYD, id, Nobjects[UNITHYD]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[UNITHYD]++; + } + break; + + case s_SNOWMELT: + // --- the same Snowmelt object can appear on several lines + if ( project_findObject(SNOWMELT, id) < 0 ) + { + if ( !project_addObject(SNOWMELT, id, Nobjects[SNOWMELT]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[SNOWMELT]++; + } + break; + + case s_JUNCTION: + if ( !project_addObject(NODE, id, Nobjects[NODE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[NODE]++; + Nnodes[JUNCTION]++; + break; + + case s_OUTFALL: + if ( !project_addObject(NODE, id, Nobjects[NODE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[NODE]++; + Nnodes[OUTFALL]++; + break; + + case s_STORAGE: + if ( !project_addObject(NODE, id, Nobjects[NODE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[NODE]++; + Nnodes[STORAGE]++; + break; + + case s_DIVIDER: + if ( !project_addObject(NODE, id, Nobjects[NODE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[NODE]++; + Nnodes[DIVIDER]++; + break; + + case s_CONDUIT: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[CONDUIT]++; + break; + + case s_PUMP: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[PUMP]++; + break; + + case s_ORIFICE: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[ORIFICE]++; + break; + + case s_WEIR: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[WEIR]++; + break; + + case s_OUTLET: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[OUTLET]++; + break; + + case s_POLLUTANT: + if ( !project_addObject(POLLUT, id, Nobjects[POLLUT]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[POLLUT]++; + break; + + case s_LANDUSE: + if ( !project_addObject(LANDUSE, id, Nobjects[LANDUSE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LANDUSE]++; + break; + + case s_PATTERN: + // --- a time pattern can span several lines + if ( project_findObject(TIMEPATTERN, id) < 0 ) + { + if ( !project_addObject(TIMEPATTERN, id, Nobjects[TIMEPATTERN]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[TIMEPATTERN]++; + } + break; + + case s_CURVE: + // --- a Curve can span several lines + if ( project_findObject(CURVE, id) < 0 ) + { + if ( !project_addObject(CURVE, id, Nobjects[CURVE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[CURVE]++; + + // --- check for a conduit shape curve + id = strtok(NULL, SEPSTR); + if ( findmatch(id, CurveTypeWords) == SHAPE_CURVE ) + Nobjects[SHAPE]++; + } + break; + + case s_TIMESERIES: + // --- a Time Series can span several lines + if ( project_findObject(TSERIES, id) < 0 ) + { + if ( !project_addObject(TSERIES, id, Nobjects[TSERIES]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[TSERIES]++; + } + break; + + case s_CONTROL: + if ( match(id, w_RULE) ) Nobjects[CONTROL]++; + else controls_addToCount(id); + break; + + case s_TRANSECT: + // --- for TRANSECTS, ID name appears as second entry on X1 line + if ( match(id, "X1") ) + { + id = strtok(NULL, SEPSTR); + if ( id ) + { + if ( !project_addObject(TRANSECT, id, Nobjects[TRANSECT]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[TRANSECT]++; + } + } + break; + + case s_LID_CONTROL: + // --- an LID object can span several lines + if ( project_findObject(LID, id) < 0 ) + { + if ( !project_addObject(LID, id, Nobjects[LID]) ) + { + errcode = error_setInpError(ERR_DUP_NAME, id); + } + Nobjects[LID]++; + } + break; + + case s_EVENT: NumEvents++; break; + + case s_STREET: + if ( !project_addObject(STREET, id, Nobjects[STREET]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[STREET]++; + break; + + case s_INLET: + // --- an INLET object can span several lines + if (project_findObject(INLET, id) < 0) + { + if ( !project_addObject(INLET, id, Nobjects[INLET]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[INLET]++; + } + break; + } + return errcode; +} + +//============================================================================= + +int parseLine(int sect, char *line) +// +// Input: sect = current section of input file +// *line = line of text read from input file +// Output: returns error code or 0 if no error found +// Purpose: parses contents of a tokenized line of text read from input file. +// +{ + int j, err; + switch (sect) + { + case s_TITLE: + return readTitle(line); + + case s_RAINGAGE: + j = Mobjects[GAGE]; + err = gage_readParams(j, Tok, Ntokens); + Mobjects[GAGE]++; + return err; + + case s_TEMP: + return climate_readParams(Tok, Ntokens); + + case s_EVAP: + return climate_readEvapParams(Tok, Ntokens); + + case s_ADJUST: + return climate_readAdjustments(Tok, Ntokens); + + case s_SUBCATCH: + j = Mobjects[SUBCATCH]; + err = subcatch_readParams(j, Tok, Ntokens); + Mobjects[SUBCATCH]++; + return err; + + case s_SUBAREA: + return subcatch_readSubareaParams(Tok, Ntokens); + + case s_INFIL: + return infil_readParams(InfilModel, Tok, Ntokens); + + case s_AQUIFER: + j = Mobjects[AQUIFER]; + err = gwater_readAquiferParams(j, Tok, Ntokens); + Mobjects[AQUIFER]++; + return err; + + case s_GROUNDWATER: + return gwater_readGroundwaterParams(Tok, Ntokens); + + case s_GWF: + return gwater_readFlowExpression(Tok, Ntokens); + + case s_SNOWMELT: + return snow_readMeltParams(Tok, Ntokens); + + case s_JUNCTION: + return readNode(JUNCTION); + + case s_OUTFALL: + return readNode(OUTFALL); + + case s_STORAGE: + return readNode(STORAGE); + + case s_DIVIDER: + return readNode(DIVIDER); + + case s_CONDUIT: + return readLink(CONDUIT); + + case s_PUMP: + return readLink(PUMP); + + case s_ORIFICE: + return readLink(ORIFICE); + + case s_WEIR: + return readLink(WEIR); + + case s_OUTLET: + return readLink(OUTLET); + + case s_XSECTION: + return link_readXsectParams(Tok, Ntokens); + + case s_TRANSECT: + return transect_readParams(&Mobjects[TRANSECT], Tok, Ntokens); + + case s_LOSSES: + return link_readLossParams(Tok, Ntokens); + + case s_POLLUTANT: + j = Mobjects[POLLUT]; + err = landuse_readPollutParams(j, Tok, Ntokens); + Mobjects[POLLUT]++; + return err; + + case s_LANDUSE: + j = Mobjects[LANDUSE]; + err = landuse_readParams(j, Tok, Ntokens); + Mobjects[LANDUSE]++; + return err; + + case s_BUILDUP: + return landuse_readBuildupParams(Tok, Ntokens); + + case s_WASHOFF: + return landuse_readWashoffParams(Tok, Ntokens); + + case s_COVERAGE: + return subcatch_readLanduseParams(Tok, Ntokens); + + case s_INFLOW: + return inflow_readExtInflow(Tok, Ntokens); + + case s_DWF: + return inflow_readDwfInflow(Tok, Ntokens); + + case s_PATTERN: + return inflow_readDwfPattern(Tok, Ntokens); + + case s_RDII: + return rdii_readRdiiInflow(Tok, Ntokens); + + case s_UNITHYD: + return rdii_readUnitHydParams(Tok, Ntokens); + + case s_LOADING: + return subcatch_readInitBuildup(Tok, Ntokens); + + case s_TREATMENT: + return treatmnt_readExpression(Tok, Ntokens); + + case s_CURVE: + return table_readCurve(Tok, Ntokens); + + case s_TIMESERIES: + return table_readTimeseries(Tok, Ntokens); + + case s_CONTROL: + return readControl(Tok, Ntokens); + + case s_REPORT: + return report_readOptions(Tok, Ntokens); + + case s_FILE: + return iface_readFileParams(Tok, Ntokens); + + case s_LID_CONTROL: + return lid_readProcParams(Tok, Ntokens); + + case s_LID_USAGE: + return lid_readGroupParams(Tok, Ntokens); + + case s_EVENT: + return readEvent(Tok, Ntokens); + + case s_STREET: + return street_readParams(Tok, Ntokens); + + case s_INLET: + return inlet_readDesignParams(Tok, Ntokens); + + case s_INLET_USAGE: + return inlet_readUsageParams(Tok, Ntokens); + + default: return 0; + } +} + +//============================================================================= + +int readControl(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads a line of input for a control rule. +// +{ + int index; + int keyword; + + // --- check for minimum number of tokens + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + + if (match(tok[0], w_VARIABLE)) + return controls_addVariable(tok, ntoks); + if (match(tok[0], w_EXPRESSION)) + return controls_addExpression(tok, ntoks); + + // --- get index of control rule keyword + keyword = findmatch(tok[0], RuleKeyWords); + if ( keyword < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); + + // --- if line begins a new control rule, add rule ID to the database + if ( keyword == 0 ) + { + if ( !project_addObject(CONTROL, tok[1], Mobjects[CONTROL]) ) + { + return error_setInpError(ERR_DUP_NAME, Tok[1]); + } + Mobjects[CONTROL]++; + } + + // --- get index of last control rule processed + index = Mobjects[CONTROL] - 1; + if ( index < 0 ) return error_setInpError(ERR_RULE, ""); + + // --- add current line as a new clause to the control rule + return controls_addRuleClause(index, keyword, Tok, Ntokens); +} + +//============================================================================= + +int readOption(char* line) +// +// Input: line = line of input data +// Output: returns error code +// Purpose: reads an input line containing a project option. +// +{ + Ntokens = getTokens(line); + if ( Ntokens < 2 ) return 0; + return project_readOption(Tok[0], Tok[1]); +} + +//============================================================================= + +int readTitle(char* line) +// +// Input: line = line from input file +// Output: returns error code +// Purpose: reads project title from line of input. +// +{ + int i, n; + for (i = 0; i < MAXTITLE; i++) + { + // --- find next empty Title entry + if ( strlen(Title[i]) == 0 ) + { + // --- strip line feed character from input line + n = (int)strlen(line); + if (line[n-1] == 10) line[n-1] = ' '; + + // --- copy input line into Title entry + sstrncpy(Title[i], line, MAXMSG); + break; + } + } + return 0; +} + +//============================================================================= + +int readNode(int type) +// +// Input: type = type of node +// Output: returns error code +// Purpose: reads data for a node from a line of input. +// +{ + int j = Mobjects[NODE]; + int k = Mnodes[type]; + int err = node_readParams(j, type, k, Tok, Ntokens); + Mobjects[NODE]++; + Mnodes[type]++; + return err; +} + +//============================================================================= + +int readLink(int type) +// +// Input: type = type of link +// Output: returns error code +// Purpose: reads data for a link from a line of input. +// +{ + int j = Mobjects[LINK]; + int k = Mlinks[type]; + int err = link_readParams(j, type, k, Tok, Ntokens); + Mobjects[LINK]++; + Mlinks[type]++; + return err; +} + +//============================================================================= + +int readEvent(char* tok[], int ntoks) +{ + DateTime x[4]; + + if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); + if ( !datetime_strToDate(tok[0], &x[0]) ) + return error_setInpError(ERR_DATETIME, tok[0]); + if ( !datetime_strToTime(tok[1], &x[1]) ) + return error_setInpError(ERR_DATETIME, tok[1]); + if ( !datetime_strToDate(tok[2], &x[2]) ) + return error_setInpError(ERR_DATETIME, tok[2]); + if ( !datetime_strToTime(tok[3], &x[3]) ) + return error_setInpError(ERR_DATETIME, tok[3]); + + Event[Mevents].start = x[0] + x[1]; + Event[Mevents].end = x[2] + x[3]; + if ( Event[Mevents].start >= Event[Mevents].end ) + return error_setInpError(ERR_DATETIME, " - start date exceeds end date"); + Mevents++; + return 0; +} + +//============================================================================= + +int findmatch(char *s, char *keyword[]) +// +// Input: s = character string +// keyword = array of keyword strings +// Output: returns index of matching keyword or -1 if no match found +// Purpose: finds match between string and array of keyword strings. +// +{ + int i = 0; + while (keyword[i] != NULL) + { + if (match(s, keyword[i])) return(i); + i++; + } + return(-1); +} + +//============================================================================= + +int match(char *str, char *substr) +// +// Input: str = character string being searched +// substr = sub-string being searched for +// Output: returns 1 if sub-string found, 0 if not +// Purpose: sees if a sub-string of characters appears in a string +// (not case sensitive). +// +{ + int i,j,k; + + // --- fail if substring is empty + if (!substr[0]) return(0); + + // --- skip leading blanks of str + for (k = 0; str[k]; k++) + { + if (str[k] != ' ') break; + } + + // --- check if substr matches remainder of str + for (i = k,j = 0; substr[j]; i++,j++) + { + if (!str[i] || UCHAR(str[i]) != UCHAR(substr[j])) return(0); + } + return(1); +} + +//============================================================================= + +int getInt(char *s, int *y) +// +// Input: s = a character string +// Output: y = converted value of s, +// returns 1 if conversion successful, 0 if not +// Purpose: converts a string to an integer number. +// +{ + double x; + if ( getDouble(s, &x) ) + { + if ( x < 0.0 ) x -= 0.01; + else x += 0.01; + *y = (int)x; + return 1; + } + *y = 0; + return 0; +} + +//============================================================================= + +int getFloat(char *s, float *y) +// +// Input: s = a character string +// Output: y = converted value of s, +// returns 1 if conversion successful, 0 if not +// Purpose: converts a string to a single precision floating point number. +// +{ + char *endptr; + *y = (float) strtod(s, &endptr); + if (*endptr > 0) return(0); + return(1); +} + +//============================================================================= + +int getDouble(char *s, double *y) +// +// Input: s = a character string +// Output: y = converted value of s, +// returns 1 if conversion successful, 0 if not +// Purpose: converts a string to a double precision floating point number. +// +{ + char *endptr; + *y = strtod(s, &endptr); + if (*endptr > 0) return(0); + return(1); +} + +//============================================================================= + +int getTokens(char *s) +// +// Input: s = a character string +// Output: returns number of tokens found in s +// Purpose: scans a string for tokens, saving pointers to them +// in shared variable Tok[]. +// +// Notes: Tokens can be separated by the characters listed in SEPSTR +// (spaces, tabs, newline, carriage return) which is defined +// in CONSTS.H. Text between quotes is treated as a single token. +// +{ + int n; + size_t len, m; + char *c; + + // --- begin with no tokens + for (n = 0; n < MAXTOKS; n++) Tok[n] = NULL; + n = 0; + + // --- truncate s at start of comment + c = strchr(s,';'); + if (c) *c = '\0'; + len = (int)strlen(s); + + // --- scan s for tokens until nothing left + while (len > 0 && n < MAXTOKS) + { + m = strcspn(s,SEPSTR); // find token length + if (m == 0) s++; // no token found + else + { + if (*s == '"') // token begins with quote + { + s++; // start token after quote + len--; // reduce length of s + m = strcspn(s,"\"\n"); // find end quote or new line + } + s[m] = '\0'; // null-terminate the token + Tok[n] = s; // save pointer to token + n++; // update token count + s += m+1; // begin next token + } + len -= m+1; // update length of s + } + return n; +} + +//============================================================================= diff --git a/src/inputrpt.c b/src/inputrpt.c new file mode 100644 index 000000000..46a6f952f --- /dev/null +++ b/src/inputrpt.c @@ -0,0 +1,354 @@ +//----------------------------------------------------------------------------- +// inputrpt.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Report writing functions for input data summary. +// +// Update History +// ============== +// Build 5.2.0: +// - Support added for reporting Street geometry tables. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" +#include "lid.h" + +#define WRITE(x) (report_writeLine((x))) + +//============================================================================= + +void inputrpt_writeInput() +// +// Input: none +// Output: none +// Purpose: writes summary of input data to report file. +// +{ + int m; + int i, k; + int lidCount = 0; + if ( ErrorCode ) return; + + WRITE(""); + WRITE("*************"); + WRITE("Element Count"); + WRITE("*************"); + fprintf(Frpt.file, "\n Number of rain gages ...... %d", Nobjects[GAGE]); + fprintf(Frpt.file, "\n Number of subcatchments ... %d", Nobjects[SUBCATCH]); + fprintf(Frpt.file, "\n Number of nodes ........... %d", Nobjects[NODE]); + fprintf(Frpt.file, "\n Number of links ........... %d", Nobjects[LINK]); + fprintf(Frpt.file, "\n Number of pollutants ...... %d", Nobjects[POLLUT]); + fprintf(Frpt.file, "\n Number of land uses ....... %d", Nobjects[LANDUSE]); + + if ( Nobjects[POLLUT] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("*****************"); + WRITE("Pollutant Summary"); + WRITE("*****************"); + fprintf(Frpt.file, + "\n Ppt. GW Kdecay"); + fprintf(Frpt.file, + "\n Name Units Concen. Concen. 1/days CoPollutant"); + fprintf(Frpt.file, + "\n -----------------------------------------------------------------------"); + for (i = 0; i < Nobjects[POLLUT]; i++) + { + fprintf(Frpt.file, "\n %-20s %5s%10.2f%10.2f%10.2f", Pollut[i].ID, + QualUnitsWords[Pollut[i].units], Pollut[i].pptConcen, + Pollut[i].gwConcen, Pollut[i].kDecay*SECperDAY); + if ( Pollut[i].coPollut >= 0 ) + fprintf(Frpt.file, " %-s (%.2f)", + Pollut[Pollut[i].coPollut].ID, Pollut[i].coFraction); + } + } + + if ( Nobjects[LANDUSE] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("***************"); + WRITE("Landuse Summary"); + WRITE("***************"); + fprintf(Frpt.file, + "\n Sweeping Maximum Last"); + fprintf(Frpt.file, + "\n Name Interval Removal Swept"); + fprintf(Frpt.file, + "\n ---------------------------------------------------"); + for (i=0; i 0 ) + { + WRITE(""); + WRITE(""); + WRITE("****************"); + WRITE("Raingage Summary"); + WRITE("****************"); + fprintf(Frpt.file, +"\n Data Recording"); + fprintf(Frpt.file, +"\n Name Data Source Type Interval "); + fprintf(Frpt.file, +"\n ------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[GAGE]; i++) + { + if ( Gage[i].tSeries >= 0 ) + { + fprintf(Frpt.file, "\n %-20s %-30s ", + Gage[i].ID, Tseries[Gage[i].tSeries].ID); + fprintf(Frpt.file, "%-10s %3d min.", + RainTypeWords[Gage[i].rainType], + (Gage[i].rainInterval)/60); + } + else fprintf(Frpt.file, "\n %-20s %-30s", + Gage[i].ID, Gage[i].fname); + } + } + + if ( Nobjects[SUBCATCH] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("********************"); + WRITE("Subcatchment Summary"); + WRITE("********************"); + fprintf(Frpt.file, +"\n Name Area Width %%Imperv %%Slope Rain Gage Outlet "); + fprintf(Frpt.file, +"\n -----------------------------------------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[SUBCATCH]; i++) + { + fprintf(Frpt.file,"\n %-20s %10.2f%10.2f%10.2f%10.4f %-20s ", + Subcatch[i].ID, Subcatch[i].area*UCF(LANDAREA), + Subcatch[i].width*UCF(LENGTH), Subcatch[i].fracImperv*100.0, + Subcatch[i].slope*100.0, Gage[Subcatch[i].gage].ID); + if ( Subcatch[i].outNode >= 0 ) + { + fprintf(Frpt.file, "%-20s", Node[Subcatch[i].outNode].ID); + } + else if ( Subcatch[i].outSubcatch >= 0 ) + { + fprintf(Frpt.file, "%-20s", Subcatch[Subcatch[i].outSubcatch].ID); + } + if ( Subcatch[i].lidArea ) lidCount++; + } + } + if ( lidCount > 0 ) lid_writeSummary(); + + if ( Nobjects[NODE] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("************"); + WRITE("Node Summary"); + WRITE("************"); + fprintf(Frpt.file, +"\n Invert Max. Ponded External"); + fprintf(Frpt.file, +"\n Name Type Elev. Depth Area Inflow "); + fprintf(Frpt.file, +"\n -------------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[NODE]; i++) + { + fprintf(Frpt.file, "\n %-20s %-16s%10.2f%10.2f%10.1f", Node[i].ID, + NodeTypeWords[Node[i].type-JUNCTION], + Node[i].invertElev*UCF(LENGTH), + Node[i].fullDepth*UCF(LENGTH), + Node[i].pondedArea*UCF(LENGTH)*UCF(LENGTH)); + if ( Node[i].extInflow || Node[i].dwfInflow || Node[i].rdiiInflow ) + { + fprintf(Frpt.file, " Yes"); + } + } + } + + if ( Nobjects[LINK] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("************"); + WRITE("Link Summary"); + WRITE("************"); + fprintf(Frpt.file, +"\n Name From Node To Node Type Length %%Slope Roughness"); + fprintf(Frpt.file, +"\n ---------------------------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[LINK]; i++) + { + // --- list end nodes in their original orientation + if ( Link[i].direction == 1 ) + fprintf(Frpt.file, "\n %-16s %-16s %-16s ", + Link[i].ID, Node[Link[i].node1].ID, Node[Link[i].node2].ID); + else + fprintf(Frpt.file, "\n %-16s %-16s %-16s ", + Link[i].ID, Node[Link[i].node2].ID, Node[Link[i].node1].ID); + + // --- list link type + if ( Link[i].type == PUMP ) + { + k = Link[i].subIndex; + fprintf(Frpt.file, "%-5s PUMP ", + PumpTypeWords[Pump[k].type]); + } + else fprintf(Frpt.file, "%-12s", + LinkTypeWords[Link[i].type-CONDUIT]); + + // --- list length, slope and roughness for conduit links + if (Link[i].type == CONDUIT) + { + k = Link[i].subIndex; + fprintf(Frpt.file, "%10.1f%10.4f%10.4f", + Conduit[k].length*UCF(LENGTH), + Conduit[k].slope*100.0*Link[i].direction, + Conduit[k].roughness); + } + } + + WRITE(""); + WRITE(""); + WRITE("*********************"); + WRITE("Cross Section Summary"); + WRITE("*********************"); + fprintf(Frpt.file, +"\n Full Full Hyd. Max. No. of Full"); + fprintf(Frpt.file, +"\n Conduit Shape Depth Area Rad. Width Barrels Flow"); + fprintf(Frpt.file, +"\n ---------------------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[LINK]; i++) + { + if (Link[i].type == CONDUIT) + { + k = Link[i].subIndex; + fprintf(Frpt.file, "\n %-16s ", Link[i].ID); + if ( Link[i].xsect.type == CUSTOM ) + fprintf(Frpt.file, "%-16s ", Curve[Link[i].xsect.transect].ID); + else if ( Link[i].xsect.type == IRREGULAR ) + fprintf(Frpt.file, "%-16s ", + Transect[Link[i].xsect.transect].ID); + else if ( Link[i].xsect.type == STREET_XSECT ) + fprintf(Frpt.file, "%-16s ", + Street[Link[i].xsect.transect].ID); + else fprintf(Frpt.file, "%-16s ", + XsectTypeWords[Link[i].xsect.type]); + fprintf(Frpt.file, "%8.2f %8.2f %8.2f %8.2f %3d %8.2f", + Link[i].xsect.yFull*UCF(LENGTH), + Link[i].xsect.aFull*UCF(LENGTH)*UCF(LENGTH), + Link[i].xsect.rFull*UCF(LENGTH), + Link[i].xsect.wMax*UCF(LENGTH), + Conduit[k].barrels, + Link[i].qFull*UCF(FLOW)); + } + } + } + + if (Nobjects[SHAPE] > 0) + { + WRITE(""); + WRITE(""); + WRITE("*************"); + WRITE("Shape Summary"); + WRITE("*************"); + for (i = 0; i < Nobjects[SHAPE]; i++) + { + k = Shape[i].curve; + fprintf(Frpt.file, "\n\n Shape %s", Curve[k].ID); + fprintf(Frpt.file, "\n Area: "); + for ( m = 1; m < N_SHAPE_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Shape[i].areaTbl[m]); + } + fprintf(Frpt.file, "\n Hrad: "); + for ( m = 1; m < N_SHAPE_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Shape[i].hradTbl[m]); + } + fprintf(Frpt.file, "\n Width: "); + for ( m = 1; m < N_SHAPE_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Shape[i].widthTbl[m]); + } + } + } + + if (Nobjects[TRANSECT] > 0) + { + WRITE(""); + WRITE(""); + WRITE("****************"); + WRITE("Transect Summary"); + WRITE("****************"); + for (i = 0; i < Nobjects[TRANSECT]; i++) + { + fprintf(Frpt.file, "\n\n Transect %s", Transect[i].ID); + fprintf(Frpt.file, "\n Area: "); + for ( m = 1; m < N_TRANSECT_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Transect[i].areaTbl[m]); + } + fprintf(Frpt.file, "\n Hrad: "); + for ( m = 1; m < N_TRANSECT_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Transect[i].hradTbl[m]); + } + fprintf(Frpt.file, "\n Width: "); + for ( m = 1; m < N_TRANSECT_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Transect[i].widthTbl[m]); + } + } + } + + if (Nobjects[STREET] > 0) + { + WRITE(""); + WRITE(""); + WRITE("**************"); + WRITE("Street Summary"); + WRITE("**************"); + for (i = 0; i < Nobjects[STREET]; i++) + { + fprintf(Frpt.file, "\n\n Street %s", Street[i].ID); + fprintf(Frpt.file, "\n Area: "); + for (m = 1; m < Street[i].transect.nTbl; m++) + { + if (m % 5 == 1) fprintf(Frpt.file, "\n "); + fprintf(Frpt.file, "%10.4f ", Street[i].transect.areaTbl[m]); + } + fprintf(Frpt.file, "\n Hrad: "); + for (m = 1; m < Street[i].transect.nTbl; m++) + { + if (m % 5 == 1) fprintf(Frpt.file, "\n "); + fprintf(Frpt.file, "%10.4f ", Street[i].transect.hradTbl[m]); + } + fprintf(Frpt.file, "\n Width: "); + for (m = 1; m < Street[i].transect.nTbl; m++) + { + if (m % 5 == 1) fprintf(Frpt.file, "\n "); + fprintf(Frpt.file, "%10.4f ", Street[i].transect.widthTbl[m]); + } + } + } + WRITE(""); +} diff --git a/src/keywords.c b/src/keywords.c new file mode 100644 index 000000000..17973d182 --- /dev/null +++ b/src/keywords.c @@ -0,0 +1,170 @@ +//----------------------------------------------------------------------------- +// keywords.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Exportable keyword dictionary +// +// NOTE: the keywords in each list must appear in same order used +// by its complementary enumerated variable in enums.h and +// must be terminated by NULL. The actual text of each keyword +// is defined in text.h. +// +// Update History +// ============== +// Build 5.1.007: +// - Keywords for Ignore RDII option and groundwater flow equation +// and climate adjustment input sections added. +// Build 5.1.008: +// - Keyword arrays placed in alphabetical order for better readability. +// - Keywords added for Minimum Routing Step and Number of Threads options. +// Build 5.1.010: +// - New Modified Green Ampt keyword added to InfilModelWords. +// - New Roadway weir keyword added to WeirTypeWords. +// Build 5.1.011: +// - New section keyword for [EVENTS] added. +// Build 5.1.013: +// - New option keywords w_SURCHARGE_METHOD, w_RULE_STEP, w_AVERAGES +// and w_WEIR added. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for variable speed pumps. +// - Support added for analytical storage shapes. +// - Support added for RptFlags.disabled option. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include // need this to define NULL +#include "text.h" + +char* BuildupTypeWords[] = { w_NONE, w_POW, w_EXP, w_SAT, w_EXT, NULL}; +char* CurveTypeWords[] = { w_STORAGE, w_DIVERSION, w_TIDAL, w_RATING, + w_CONTROLS, w_SHAPE, w_WEIR, + w_PUMP1, w_PUMP2, w_PUMP3, w_PUMP4, + w_PUMP5, NULL}; +char* DividerTypeWords[] = { w_CUTOFF, w_TABULAR, w_WEIR, w_OVERFLOW, NULL}; +char* EvapTypeWords[] = { w_CONSTANT, w_MONTHLY, w_TIMESERIES, + w_TEMPERATURE, w_FILE, w_RECOVERY, + w_DRYONLY, NULL}; +char* FileTypeWords[] = { w_RAINFALL, w_RUNOFF, w_HOTSTART, w_RDII, + w_INFLOWS, w_OUTFLOWS, NULL}; +char* FileModeWords[] = { w_NO, w_SCRATCH, w_USE, w_SAVE, NULL}; +char* FlowUnitWords[] = { w_CFS, w_GPM, w_MGD, w_CMS, w_LPS, w_MLD, NULL}; +char* ForceMainEqnWords[] = { w_H_W, w_D_W, NULL}; +char* GageDataWords[] = { w_TIMESERIES, w_FILE, NULL}; +char* InfilModelWords[] = { w_HORTON, w_MOD_HORTON, w_GREEN_AMPT, + w_MOD_GREEN_AMPT, w_CURVE_NUMEBR, NULL}; +char* InertDampingWords[] = { w_NONE, w_PARTIAL, w_FULL, NULL}; +char* LinkOffsetWords[] = { w_DEPTH, w_ELEVATION, NULL}; +char* LinkTypeWords[] = { w_CONDUIT, w_PUMP, w_ORIFICE, + w_WEIR, w_OUTLET }; +char* LoadUnitsWords[] = { w_LBS, w_KG, w_LOGN }; +char* NodeTypeWords[] = { w_JUNCTION, w_OUTFALL, + w_STORAGE, w_DIVIDER }; +char* NoneAllWords[] = { w_NONE, w_ALL, NULL}; +char* NormalFlowWords[] = { w_SLOPE, w_FROUDE, w_BOTH, NULL}; +char* NormalizerWords[] = { w_PER_AREA, w_PER_CURB, NULL}; +char* NoYesWords[] = { w_NO, w_YES, NULL}; +char* OffOnWords[] = { w_OFF, w_ON, NULL}; +char* OldRouteModelWords[] = { w_NONE, w_NF, w_KW, w_EKW, w_DW, NULL}; +char* OptionWords[] = { w_FLOW_UNITS, w_INFIL_MODEL, + w_ROUTE_MODEL, w_START_DATE, + w_START_TIME, w_END_DATE, + w_END_TIME, w_REPORT_START_DATE, + w_REPORT_START_TIME, w_SWEEP_START, + w_SWEEP_END, w_START_DRY_DAYS, + w_WET_STEP, w_DRY_STEP, + w_ROUTE_STEP, w_RULE_STEP, + w_REPORT_STEP, + w_ALLOW_PONDING, w_INERT_DAMPING, + w_SLOPE_WEIGHTING, w_VARIABLE_STEP, + w_NORMAL_FLOW_LTD, w_LENGTHENING_STEP, + w_MIN_SURFAREA, w_COMPATIBILITY, + w_SKIP_STEADY_STATE, w_TEMPDIR, + w_IGNORE_RAINFALL, w_FORCE_MAIN_EQN, + w_LINK_OFFSETS, w_MIN_SLOPE, + w_IGNORE_SNOWMELT, w_IGNORE_GWATER, + w_IGNORE_ROUTING, w_IGNORE_QUALITY, + w_MAX_TRIALS, w_HEAD_TOL, + w_SYS_FLOW_TOL, w_LAT_FLOW_TOL, + w_IGNORE_RDII, w_MIN_ROUTE_STEP, + w_NUM_THREADS, w_SURCHARGE_METHOD, + NULL }; +char* OrificeTypeWords[] = { w_SIDE, w_BOTTOM, NULL}; +char* OutfallTypeWords[] = { w_FREE, w_NORMAL, w_FIXED, w_TIDAL, + w_TIMESERIES, NULL}; +char* PatternTypeWords[] = { w_MONTHLY, w_DAILY, w_HOURLY, w_WEEKEND, NULL}; +char* PondingUnitsWords[] = { w_PONDED_FEET, w_PONDED_METERS }; +char* ProcessVarWords[] = { w_HRT, w_DT, w_FLOW, w_DEPTH, w_AREA, NULL}; +char* PumpTypeWords[] = { w_TYPE1, w_TYPE2, w_TYPE3, w_TYPE4, w_TYPE5, w_IDEAL }; +char* QualUnitsWords[] = { w_MGperL, w_UGperL, w_COUNTperL, NULL}; +char* RainTypeWords[] = { w_INTENSITY, w_VOLUME, w_CUMULATIVE, NULL}; +char* RainUnitsWords[] = { w_INCHES, w_MMETER, NULL}; +char* RelationWords[] = { w_TABULAR, w_FUNCTIONAL, + w_CYLINDRICAL, w_CONICAL, w_PARABOLIC, + w_PYRAMIDAL, NULL}; +char* ReportWords[] = { w_DISABLED, w_INPUT, w_SUBCATCH, w_NODE, w_LINK, + w_CONTINUITY, w_FLOWSTATS,w_CONTROLS, + w_AVERAGES, w_NODESTATS, NULL}; +char* RouteModelWords[] = { w_NONE, w_STEADY, w_KINWAVE, w_XKINWAVE, + w_DYNWAVE, NULL}; +char* RuleKeyWords[] = { w_RULE, w_IF, w_AND, w_OR, w_THEN, w_ELSE, + w_PRIORITY, NULL}; +char* SectWords[] = { ws_TITLE, ws_OPTION, + ws_FILE, ws_RAINGAGE, + ws_TEMP, ws_EVAP, + ws_SUBCATCH, ws_SUBAREA, + ws_INFIL, ws_AQUIFER, + ws_GROUNDWATER, ws_SNOWMELT, + ws_JUNCTION, ws_OUTFALL, + ws_STORAGE, ws_DIVIDER, + ws_CONDUIT, ws_PUMP, + ws_ORIFICE, ws_WEIR, + ws_OUTLET, ws_XSECTION, + ws_TRANSECT, ws_LOSS, + ws_CONTROL, ws_POLLUTANT, + ws_LANDUSE, ws_BUILDUP, + ws_WASHOFF, ws_COVERAGE, + ws_INFLOW, ws_DWF, + ws_PATTERN, ws_RDII, + ws_UNITHYD, ws_LOADING, + ws_TREATMENT, ws_CURVE, + ws_TIMESERIES, ws_REPORT, + ws_COORDINATE, ws_VERTICES, + ws_POLYGON, ws_LABEL, + ws_SYMBOL, ws_BACKDROP, + ws_TAG, ws_PROFILE, + ws_MAP, ws_LID_CONTROL, + ws_LID_USAGE, ws_GWF, + ws_ADJUST, ws_EVENT, + ws_STREET, ws_INLET_USAGE, + ws_INLET, NULL}; +char* SnowmeltWords[] = { w_PLOWABLE, w_IMPERV, w_PERV, w_REMOVAL, NULL}; +char* SurchargeWords[] = { w_EXTRAN, w_SLOT, NULL}; +char* TempKeyWords[] = { w_TIMESERIES, w_FILE, w_WINDSPEED, w_SNOWMELT, + w_ADC, NULL}; +char* TransectKeyWords[] = { w_NC, w_X1, w_GR, NULL}; +char* TreatTypeWords[] = { w_REMOVAL, w_CONCEN, NULL}; +char* UHTypeWords[] = { w_SHORT, w_MEDIUM, w_LONG, NULL}; +char* VolUnitsWords[] = { w_MGAL, w_MLTRS }; +char* VolUnitsWords2[] = { w_GAL, w_LTR }; +char* WashoffTypeWords[] = { w_NONE, w_EXP, w_RC, w_EMC, NULL}; +char* WeirTypeWords[] = { w_TRANSVERSE, w_SIDEFLOW, w_VNOTCH, + w_TRAPEZOIDAL, w_ROADWAY, NULL}; +char* XsectTypeWords[] = { w_DUMMY, w_CIRCULAR, + w_FILLED_CIRCULAR, w_RECT_CLOSED, + w_RECT_OPEN, w_TRAPEZOIDAL, + w_TRIANGULAR, w_PARABOLIC, + w_POWERFUNC, w_RECT_TRIANG, + w_RECT_ROUND, w_MOD_BASKET, + w_HORIZELLIPSE, w_VERTELLIPSE, + w_ARCH, w_EGGSHAPED, + w_HORSESHOE, w_GOTHIC, + w_CATENARY, w_SEMIELLIPTICAL, + w_BASKETHANDLE, w_SEMICIRCULAR, + w_IRREGULAR, w_CUSTOM, + w_FORCE_MAIN, w_STREET, + NULL}; diff --git a/src/keywords.h b/src/keywords.h new file mode 100644 index 000000000..e330c59bc --- /dev/null +++ b/src/keywords.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// keywords.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Exportable keyword dictionary +// +// Update History +// ============== +// Build 5.1.008: +// - Keyword arrays listed in alphabetical order. +// Build 5.1.013: +// - New keyword array defined for surcharge method. +//----------------------------------------------------------------------------- + +#ifndef KEYWORDS_H +#define KEYWORDS_H + + +extern char* BuildupTypeWords[]; +extern char* CurveTypeWords[]; +extern char* DividerTypeWords[]; +extern char* DynWaveMethodWords[]; +extern char* EvapTypeWords[]; +extern char* FileModeWords[]; +extern char* FileTypeWords[]; +extern char* FlowUnitWords[]; +extern char* ForceMainEqnWords[]; +extern char* GageDataWords[]; +extern char* InertDampingWords[]; +extern char* InfilModelWords[]; +extern char* LinkOffsetWords[]; +extern char* LinkTypeWords[]; +extern char* LoadUnitsWords[]; +extern char* NodeTypeWords[]; +extern char* NoneAllWords[]; +extern char* NormalFlowWords[]; +extern char* NormalizerWords[]; +extern char* NoYesWords[]; +extern char* OldRouteModelWords[]; +extern char* OffOnWords[]; +extern char* OptionWords[]; +extern char* OrificeTypeWords[]; +extern char* OutfallTypeWords[]; +extern char* PatternTypeWords[]; +extern char* PondingUnitsWords[]; +extern char* ProcessVarWords[]; +extern char* PumpTypeWords[]; +extern char* QualUnitsWords[]; +extern char* RainTypeWords[]; +extern char* RainUnitsWords[]; +extern char* ReportWords[]; +extern char* RelationWords[]; +extern char* RouteModelWords[]; +extern char* RuleKeyWords[]; +extern char* SectWords[]; +extern char* SnowmeltWords[]; +extern char* SurchargeWords[]; +extern char* TempKeyWords[]; +extern char* TransectKeyWords[]; +extern char* TreatTypeWords[]; +extern char* UHTypeWords[]; +extern char* VolUnitsWords[]; +extern char* VolUnitsWords2[]; +extern char* WashoffTypeWords[]; +extern char* WeirTypeWords[]; +extern char* XsectTypeWords[]; + + +#endif //KEYWORDS_H diff --git a/src/kinwave.c b/src/kinwave.c new file mode 100644 index 000000000..b137e4ce9 --- /dev/null +++ b/src/kinwave.c @@ -0,0 +1,272 @@ +//----------------------------------------------------------------------------- +// kinwave.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// M. Tryby (EPA) +// +// Kinematic wave flow routing functions. +// +// Update History +// ============== +// Build 5.1.008: +// - Conduit inflow passed to function that computes conduit losses. +// Build 5.1.014: +// - Arguments to function link_getLossRate changed. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "headers.h" +#include "findroot.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double WX = 0.6; // distance weighting +static const double WT = 0.6; // time weighting +static const double EPSIL = 0.001; // convergence criterion + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +static double Beta1; +static double C1; +static double C2; +static double Afull; +static double Qfull; +static TXsect* pXsect; + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// kinwave_execute (called by flowrout_execute) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int solveContinuity(double qin, double ain, double* aout); +static void evalContinuity(double a, double* f, double* df, void* p); + +//============================================================================= + +int kinwave_execute(int j, double* qinflow, double* qoutflow, double tStep) +// +// Input: j = link index +// qinflow = inflow at current time (cfs) +// tStep = time step (sec) +// Output: qoutflow = outflow at current time (cfs), +// returns number of iterations used +// Purpose: finds outflow over time step tStep given flow entering a +// conduit using Kinematic Wave flow routing. +// +// +// ^ q3 +// t | +// | qin, ain |-------------------| qout, aout +// | | Flow ---> | +// |----> x q1, a1 |-------------------| q2, a2 +// +// +{ + int k; + int result = 1; + double dxdt, dq; + double ain, aout; + double qin, qout; + double a1, a2, q1, q2, q3; + + // --- no routing for non-conduit link + (*qoutflow) = (*qinflow); + if ( Link[j].type != CONDUIT ) return result; + + // --- no routing for dummy xsection + if ( Link[j].xsect.type == DUMMY ) return result; + + // --- assign module-level variables + pXsect = &Link[j].xsect; + Qfull = Link[j].qFull; + Afull = Link[j].xsect.aFull; + k = Link[j].subIndex; + Beta1 = Conduit[k].beta / Qfull; + + // --- normalize previous flows + q1 = Conduit[k].q1 / Qfull; + q2 = Conduit[k].q2 / Qfull; + + // --- normalize inflow + qin = (*qinflow) / Conduit[k].barrels / Qfull; + + // --- compute evaporation and infiltration loss rate + q3 = link_getLossRate(j, qin*Qfull) / Qfull; + + // --- normalize previous areas + a1 = Conduit[k].a1 / Afull; + a2 = Conduit[k].a2 / Afull; + + // --- use full area when inlet flow >= full flow + if ( qin >= 1.0 ) ain = 1.0; + + // --- get normalized inlet area corresponding to inlet flow + else ain = xsect_getAofS(pXsect, qin/Beta1) / Afull; + + // --- check for no flow + if ( qin <= TINY && q2 <= TINY ) + { + qout = 0.0; + aout = 0.0; + } + + // --- otherwise solve finite difference form of continuity eqn. + else + { + // --- compute constant factors + dxdt = link_getLength(j) / tStep * Afull / Qfull; + dq = q2 - q1; + C1 = dxdt * WT / WX; + C2 = (1.0 - WT) * (ain - a1); + C2 = C2 - WT * a2; + C2 = C2 * dxdt / WX; + C2 = C2 + (1.0 - WX) / WX * dq - qin; + C2 = C2 + q3 / WX; + + // --- starting guess for aout is value from previous time step + aout = a2; + + // --- solve continuity equation for aout + result = solveContinuity(qin, ain, &aout); + + // --- report error if continuity eqn. not solved + if ( result == -1 ) + { + report_writeErrorMsg(ERR_KINWAVE, Link[j].ID); + return 1; + } + if ( result <= 0 ) result = 1; + + // --- compute normalized outlet flow from outlet area + qout = Beta1 * xsect_getSofA(pXsect, aout*Afull); + if ( qin > 1.0 ) qin = 1.0; + } + + // --- save new flows and areas + Conduit[k].q1 = qin * Qfull; + Conduit[k].a1 = ain * Afull; + Conduit[k].q2 = qout * Qfull; + Conduit[k].a2 = aout * Afull; + Conduit[k].fullState = + link_getFullState(Conduit[k].a1, Conduit[k].a2, Afull); + (*qinflow) = Conduit[k].q1 * Conduit[k].barrels; + (*qoutflow) = Conduit[k].q2 * Conduit[k].barrels; + return result; +} + +//============================================================================= + +int solveContinuity(double qin, double ain, double* aout) +// +// Input: qin = upstream normalized flow +// ain = upstream normalized area +// aout = downstream normalized area +// Output: new value for aout; returns an error code +// Purpose: solves continuity equation f(a) = Beta1*S(a) + C1*a + C2 = 0 +// for 'a' using the Newton-Raphson root finder function. +// Return code has the following meanings: +// >= 0 number of function evaluations used +// -1 Newton function failed +// -2 flow always above max. flow +// -3 flow always below zero +// +// Note: pXsect (pointer to conduit's cross-section), and constants Beta1, +// C1, and C2 are module-level shared variables assigned values +// in kinwave_execute(). +// +{ + int n; // # evaluations or error code + double aLo, aHi, aTmp; // lower/upper bounds on a + double fLo, fHi; // lower/upper bounds on f + double tol = EPSIL; // absolute convergence tol. + + // --- first determine bounds on 'a' so that f(a) passes through 0. + + // --- set upper bound to area at full flow + aHi = 1.0; + fHi = 1.0 + C1 + C2; + + // --- try setting lower bound to area where section factor is maximum + aLo = xsect_getAmax(pXsect) / Afull; + if ( aLo < aHi ) + { + fLo = ( Beta1 * pXsect->sMax ) + (C1 * aLo) + C2; + } + else fLo = fHi; + + // --- if fLo and fHi have same sign then set lower bound to 0 + if ( fHi*fLo > 0.0 ) + { + aHi = aLo; + fHi = fLo; + aLo = 0.0; + fLo = C2; + } + + // --- proceed with search for root if fLo and fHi have different signs + if ( fHi*fLo <= 0.0 ) + { + // --- start search at midpoint of lower/upper bounds + // if initial value outside of these bounds + if ( *aout < aLo || *aout > aHi ) *aout = 0.5*(aLo + aHi); + + // --- if fLo > fHi then switch aLo and aHi + if ( fLo > fHi ) + { + aTmp = aLo; + aLo = aHi; + aHi = aTmp; + } + + // --- call the Newton root finder method passing it the + // evalContinuity function to evaluate the function + // and its derivatives + n = findroot_Newton(aLo, aHi, aout, tol, evalContinuity, NULL); + + // --- check if root finder succeeded + if ( n <= 0 ) n = -1; + } + + // --- if lower/upper bound functions both negative then use full flow + else if ( fLo < 0.0 ) + { + if ( qin > 1.0 ) *aout = ain; + else *aout = 1.0; + n = -2; + } + + // --- if lower/upper bound functions both positive then use no flow + else if ( fLo > 0 ) + { + *aout = 0.0; + n = -3; + } + else n = -1; + return n; +} + +//============================================================================= + +void evalContinuity(double a, double* f, double* df, void* p) +// +// Input: a = outlet normalized area +// Output: f = value of continuity eqn. +// df = derivative of continuity eqn. +// Purpose: computes value of continuity equation (f) and its derivative (df) +// w.r.t. normalized area for link with normalized outlet area 'a'. +// +{ + *f = (Beta1 * xsect_getSofA(pXsect, a*Afull)) + (C1 * a) + C2; + *df = (Beta1 * Afull * xsect_getdSdA(pXsect, a*Afull)) + C1; +} + +//============================================================================= diff --git a/src/landuse.c b/src/landuse.c new file mode 100644 index 000000000..4c8402790 --- /dev/null +++ b/src/landuse.c @@ -0,0 +1,723 @@ +//----------------------------------------------------------------------------- +// landuse.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Pollutant buildup and washoff functions. +// +// Update History +// ============== +// Build 5.1.008: +// - landuse_getWashoffMass() re-named to landuse_getWashoffQual() and +// modified to return concentration instead of mass load. +// - landuse_getRunoffLoad() re-named to landuse_getWashoffLoad() and +// modified to work with landuse_getWashoffQual(). +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// landuse_readParams (called by parseLine in input.c) +// landuse_readPollutParams (called by parseLine in input.c) +// landuse_readBuildupParams (called by parseLine in input.c) +// landuse_readWashoffParams (called by parseLine in input.c) + +// landuse_getInitBuildup (called by subcatch_initState) +// landuse_getBuildup (called by surfqual_getBuildup) +// landuse_getWashoffLoad (called by surfqual_getWashoff) +// landuse_getCoPollutLoad (called by surfqual_getwashoff)); +// landuse_getAvgBMPEffic (called by updatePondedQual in surfqual.c) + +//----------------------------------------------------------------------------- +// Function declarations +//----------------------------------------------------------------------------- +static double landuse_getBuildupDays(int landuse, int pollut, double buildup); +static double landuse_getBuildupMass(int landuse, int pollut, double days); +static double landuse_getWashoffQual(int landuse, int pollut, double buildup, + double runoff, double area); +static double landuse_getExternalBuildup(int i, int p, double buildup, + double tStep); + +//============================================================================= + +int landuse_readParams(int j, char* tok[], int ntoks) +// +// Input: j = land use index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads landuse parameters from a tokenized line of input. +// +// Data format is: +// landuseID (sweepInterval sweepRemoval sweepDays0) +// +{ + char *id; + if ( ntoks < 1 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LANDUSE, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + Landuse[j].ID = id; + if ( ntoks > 1 ) + { + if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); + if ( ! getDouble(tok[1], &Landuse[j].sweepInterval) ) + return error_setInpError(ERR_NUMBER, tok[1]); + if ( ! getDouble(tok[2], &Landuse[j].sweepRemoval) ) + return error_setInpError(ERR_NUMBER, tok[2]); + if ( ! getDouble(tok[3], &Landuse[j].sweepDays0) ) + return error_setInpError(ERR_NUMBER, tok[3]); + } + else + { + Landuse[j].sweepInterval = 0.0; + Landuse[j].sweepRemoval = 0.0; + Landuse[j].sweepDays0 = 0.0; + } + if ( Landuse[j].sweepRemoval < 0.0 + || Landuse[j].sweepRemoval > 1.0 ) + return error_setInpError(ERR_NUMBER, tok[2]); + return 0; +} + +//============================================================================= + +int landuse_readPollutParams(int j, char* tok[], int ntoks) +// +// Input: j = pollutant index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads pollutant parameters from a tokenized line of input. +// +// Data format is: +// ID Units cRain cGW cRDII kDecay (snowOnly coPollut coFrac cDWF cInit) +// +{ + int i, k, coPollut, snowFlag; + double x[4], coFrac, cDWF, cInit; + char *id; + + // --- extract pollutant name & units + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(POLLUT, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + k = findmatch(tok[1], QualUnitsWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- extract concen. in rain, gwater, & I&I + for ( i = 2; i <= 4; i++ ) + { + if ( ! getDouble(tok[i], &x[i-2]) || x[i-2] < 0.0 ) + { + return error_setInpError(ERR_NUMBER, tok[i]); + } + } + + // --- extract decay coeff. (which can be negative for growth) + if ( ! getDouble(tok[5], &x[3]) ) + { + return error_setInpError(ERR_NUMBER, tok[5]); + } + + // --- set defaults for snow only flag & co-pollut. parameters + snowFlag = 0; + coPollut = -1; + coFrac = 0.0; + cDWF = 0.0; + cInit = 0.0; + + // --- check for snow only flag + if ( ntoks >= 7 ) + { + snowFlag = findmatch(tok[6], NoYesWords); + if ( snowFlag < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); + } + + // --- check for co-pollutant + if ( ntoks >= 9 ) + { + if ( !strcomp(tok[7], "*") ) + { + coPollut = project_findObject(POLLUT, tok[7]); + if ( coPollut < 0 ) return error_setInpError(ERR_NAME, tok[7]); + if ( ! getDouble(tok[8], &coFrac) || coFrac < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[8]); + } + } + + // --- check for DWF concen. + if ( ntoks >= 10 ) + { + if ( ! getDouble(tok[9], &cDWF) || cDWF < 0.0) + return error_setInpError(ERR_NUMBER, tok[9]); + } + + // --- check for initial concen. + if ( ntoks >= 11 ) + { + if ( ! getDouble(tok[10], &cInit) || cInit < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[9]); + } + + // --- save values for pollutant object + Pollut[j].ID = id; + Pollut[j].units = k; + if ( Pollut[j].units == MG ) Pollut[j].mcf = UCF(MASS); + else if ( Pollut[j].units == UG ) Pollut[j].mcf = UCF(MASS) / 1000.0; + else Pollut[j].mcf = 1.0; + Pollut[j].pptConcen = x[0]; + Pollut[j].gwConcen = x[1]; + Pollut[j].rdiiConcen = x[2]; + Pollut[j].kDecay = x[3]/SECperDAY; + Pollut[j].snowOnly = snowFlag; + Pollut[j].coPollut = coPollut; + Pollut[j].coFraction = coFrac; + Pollut[j].dwfConcen = cDWF; + Pollut[j].initConcen = cInit; + return 0; +} + +//============================================================================= + +int landuse_readBuildupParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads pollutant buildup parameters from a tokenized line of input. +// +// Data format is: +// landuseID pollutID buildupType c1 c2 c3 normalizerType +// +{ + int i, j, k, n, p; + double c[3] = {0, 0, 0}, tmax; + + if ( ntoks < 3 ) return 0; + j = project_findObject(LANDUSE, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + p = project_findObject(POLLUT, tok[1]); + if ( p < 0 ) return error_setInpError(ERR_NAME, tok[1]); + k = findmatch(tok[2], BuildupTypeWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[2]); + Landuse[j].buildupFunc[p].funcType = k; + if ( k > NO_BUILDUP ) + { + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + if ( k != EXTERNAL_BUILDUP ) for (i=0; i<3; i++) + { + if ( ! getDouble(tok[i+3], &c[i]) || c[i] < 0.0 ) + { + return error_setInpError(ERR_NUMBER, tok[i+3]); + } + } + n = findmatch(tok[6], NormalizerWords); + if (n < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); + Landuse[j].buildupFunc[p].normalizer = n; + } + + // Find time until max. buildup (or time series for external buildup) + switch (Landuse[j].buildupFunc[p].funcType) + { + case POWER_BUILDUP: + // --- check for too small or large an exponent + if ( c[2] > 0.0 && (c[2] < 0.01 || c[2] > 10.0) ) + return error_setInpError(ERR_KEYWORD, tok[5]); + + // --- find time to reach max. buildup + // --- use zero if coeffs. are 0 + if ( c[1]*c[2] == 0.0 ) tmax = 0.0; + + // --- use 10 years if inverse power function tends to blow up + else if ( log10(c[0]) / c[2] > 3.5 ) tmax = 3650.0; + + // --- otherwise use inverse power function + else tmax = pow(c[0]/c[1], 1.0/c[2]); + break; + + case EXPON_BUILDUP: + if ( c[1] == 0.0 ) tmax = 0.0; + else tmax = -log(0.001)/c[1]; + break; + + case SATUR_BUILDUP: + tmax = 1000.0*c[2]; + break; + + case EXTERNAL_BUILDUP: + if ( !getDouble(tok[3], &c[0]) || c[0] < 0.0 ) //max. buildup + return error_setInpError(ERR_NUMBER, tok[3]); + if ( !getDouble(tok[4], &c[1]) || c[1] < 0.0 ) //scaling factor + return error_setInpError(ERR_NUMBER, tok[3]); + n = project_findObject(TSERIES, tok[5]); //time series + if ( n < 0 ) return error_setInpError(ERR_NAME, tok[4]); + Tseries[n].refersTo = EXTERNAL_BUILDUP; + c[2] = n; + tmax = 0.0; + break; + + default: + tmax = 0.0; + } + + // Assign parameters to buildup object + Landuse[j].buildupFunc[p].coeff[0] = c[0]; + Landuse[j].buildupFunc[p].coeff[1] = c[1]; + Landuse[j].buildupFunc[p].coeff[2] = c[2]; + Landuse[j].buildupFunc[p].maxDays = tmax; + return 0; +} + +//============================================================================= + +int landuse_readWashoffParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads pollutant washoff parameters from a tokenized line of input. +// +// Data format is: +// landuseID pollutID washoffType c1 c2 sweepEffic bmpRemoval +{ + int i, j, p; + int func; + double x[4]; + + if ( ntoks < 3 ) return 0; + for (i=0; i<4; i++) x[i] = 0.0; + func = NO_WASHOFF; + j = project_findObject(LANDUSE, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + p = project_findObject(POLLUT, tok[1]); + if ( p < 0 ) return error_setInpError(ERR_NAME, tok[1]); + if ( ntoks > 2 ) + { + func = findmatch(tok[2], WashoffTypeWords); + if ( func < 0 ) return error_setInpError(ERR_KEYWORD, tok[2]); + if ( func != NO_WASHOFF ) + { + if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); + if ( ! getDouble(tok[3], &x[0]) ) + return error_setInpError(ERR_NUMBER, tok[3]); + if ( ! getDouble(tok[4], &x[1]) ) + return error_setInpError(ERR_NUMBER, tok[4]); + if ( ntoks >= 6 ) + { + if ( ! getDouble(tok[5], &x[2]) ) + return error_setInpError(ERR_NUMBER, tok[5]); + } + if ( ntoks >= 7 ) + { + if ( ! getDouble(tok[6], &x[3]) ) + return error_setInpError(ERR_NUMBER, tok[6]); + } + } + } + + // --- check for valid parameter values + // x[0] = washoff coeff. + // x[1] = washoff expon. + // x[2] = sweep effic. + // x[3] = BMP effic. + if ( x[0] < 0.0 ) return error_setInpError(ERR_NUMBER, tok[3]); + if ( x[1] < -10.0 || x[1] > 10.0 ) + return error_setInpError(ERR_NUMBER, tok[4]);; + if ( x[2] < 0.0 || x[2] > 100.0 ) + return error_setInpError(ERR_NUMBER, tok[5]); + if ( x[3] < 0.0 || x[3] > 100.0 ) + return error_setInpError(ERR_NUMBER, tok[6]); + + // --- convert units of washoff coeff. + if ( func == EXPON_WASHOFF ) x[0] /= 3600.0; + if ( func == RATING_WASHOFF ) x[0] *= pow(UCF(FLOW), x[1]); + if ( func == EMC_WASHOFF ) x[0] *= LperFT3; + + // --- assign washoff parameters to washoff object + Landuse[j].washoffFunc[p].funcType = func; + Landuse[j].washoffFunc[p].coeff = x[0]; + Landuse[j].washoffFunc[p].expon = x[1]; + Landuse[j].washoffFunc[p].sweepEffic = x[2] / 100.0; + Landuse[j].washoffFunc[p].bmpEffic = x[3] / 100.0; + return 0; +} + +//============================================================================= + +void landuse_getInitBuildup(TLandFactor* landFactor, double* initBuildup, + double area, double curb) +// +// Input: landFactor = array of land use factors +// initBuildup = total initial buildup of each pollutant +// area = subcatchment's area (ft2) +// curb = subcatchment's curb length (users units) +// Output: modifies each land use factor's initial pollutant buildup +// Purpose: determines the initial buildup of each pollutant on +// each land use for a given subcatchment. +// +// Notes: Contributions from co-pollutants to initial buildup are not +// included since the co-pollutant mechanism only applies to +// washoff. +// +{ + int i, p; + double startDrySeconds; // antecedent dry period (sec) + double f; // faction of total land area + double fArea; // area of land use (ft2) + double fCurb; // curb length of land use + double buildup; // pollutant mass buildup + + // --- convert antecedent dry days into seconds + startDrySeconds = StartDryDays*SECperDAY; + + // --- examine each land use + for (i = 0; i < Nobjects[LANDUSE]; i++) + { + // --- initialize date when last swept + landFactor[i].lastSwept = StartDateTime - Landuse[i].sweepDays0; + + // --- determine area and curb length covered by land use + f = landFactor[i].fraction; + fArea = f * area * UCF(LANDAREA); + fCurb = f * curb; + + // --- determine buildup of each pollutant + for (p = 0; p < Nobjects[POLLUT]; p++) + { + // --- if an initial loading was supplied, then use it to + // find the starting buildup over the land use + buildup = 0.0; + if ( initBuildup[p] > 0.0 ) buildup = initBuildup[p] * fArea; + + // --- otherwise use the land use's buildup function to + // compute a buildup over the antecedent dry period + else buildup = landuse_getBuildup(i, p, fArea, fCurb, buildup, + startDrySeconds); + landFactor[i].buildup[p] = buildup; + } + } +} + +//============================================================================= + +double landuse_getBuildup(int i, int p, double area, double curb, double buildup, + double tStep) +// +// Input: i = land use index +// p = pollutant index +// area = land use area (ac or ha) +// curb = land use curb length (users units) +// buildup = current pollutant buildup (lbs or kg) +// tStep = time increment for buildup (sec) +// Output: returns new buildup mass (lbs or kg) +// Purpose: computes new pollutant buildup on a landuse after a time increment. +// +{ + int n; // normalizer code + double days; // accumulated days of buildup + double perUnit; // normalizer value (area or curb length) + + // --- return current buildup if no buildup function or time increment + if ( Landuse[i].buildupFunc[p].funcType == NO_BUILDUP || tStep == 0.0 ) + { + return buildup; + } + + // --- see what buildup is normalized to + n = Landuse[i].buildupFunc[p].normalizer; + perUnit = 1.0; + if ( n == PER_AREA ) perUnit = area; + if ( n == PER_CURB ) perUnit = curb; + if ( perUnit == 0.0 ) return 0.0; + + // --- buildup determined by loading time series + if ( Landuse[i].buildupFunc[p].funcType == EXTERNAL_BUILDUP ) + { + return landuse_getExternalBuildup(i, p, buildup/perUnit, tStep) * + perUnit; + } + + // --- determine equivalent days of current buildup + days = landuse_getBuildupDays(i, p, buildup/perUnit); + + // --- compute buildup after adding on time increment + days += tStep / SECperDAY; + return landuse_getBuildupMass(i, p, days) * perUnit; +} + +//============================================================================= + +double landuse_getBuildupDays(int i, int p, double buildup) +// +// Input: i = land use index +// p = pollutant index +// buildup = amount of pollutant buildup +// Output: returns number of days it takes for buildup to reach a given level +// Purpose: finds the number of days corresponding to a pollutant buildup. +// +{ + double c0 = Landuse[i].buildupFunc[p].coeff[0]; + double c1 = Landuse[i].buildupFunc[p].coeff[1]; + double c2 = Landuse[i].buildupFunc[p].coeff[2]; + + if ( buildup == 0.0 ) return 0.0; + if ( buildup >= c0 ) return Landuse[i].buildupFunc[p].maxDays; + switch (Landuse[i].buildupFunc[p].funcType) + { + case POWER_BUILDUP: + if ( c1*c2 == 0.0 ) return 0.0; + else return pow( (buildup/c1), (1.0/c2) ); + + case EXPON_BUILDUP: + if ( c0*c1 == 0.0 ) return 0.0; + else return -log(1. - buildup/c0) / c1; + + case SATUR_BUILDUP: + if ( c0 == 0.0 ) return 0.0; + else return buildup*c2 / (c0 - buildup); + + default: + return 0.0; + } +} + +//============================================================================= + +double landuse_getBuildupMass(int i, int p, double days) +// +// Input: i = land use index +// p = pollutant index +// days = time over which buildup has occurred (days) +// Output: returns mass of pollutant buildup (lbs or kg per area or curblength) +// Purpose: finds amount of buildup of pollutant on a land use. +// +{ + double b; + double c0 = Landuse[i].buildupFunc[p].coeff[0]; + double c1 = Landuse[i].buildupFunc[p].coeff[1]; + double c2 = Landuse[i].buildupFunc[p].coeff[2]; + + if ( days == 0.0 ) return 0.0; + if ( days >= Landuse[i].buildupFunc[p].maxDays ) return c0; + switch (Landuse[i].buildupFunc[p].funcType) + { + case POWER_BUILDUP: + b = c1 * pow(days, c2); + if ( b > c0 ) b = c0; + break; + + case EXPON_BUILDUP: + b = c0*(1.0 - exp(-days*c1)); + break; + + case SATUR_BUILDUP: + b = days*c0/(c2 + days); + break; + + default: b = 0.0; + } + return b; +} + +//============================================================================= + +double landuse_getAvgBmpEffic(int j, int p) +// +// Input: j = subcatchment index +// p = pollutant index +// Output: returns a BMP removal fraction for pollutant p +// Purpose: finds the overall average BMP removal achieved for pollutant p +// treated in subcatchment j. +// +{ + int i; + double r = 0.0; + for (i = 0; i < Nobjects[LANDUSE]; i++) + { + r += Subcatch[j].landFactor[i].fraction * + Landuse[i].washoffFunc[p].bmpEffic; + } + return r; +} + +//============================================================================= + +double landuse_getWashoffLoad(int i, int p, double area, + TLandFactor landFactor[], double runoff, double vOutflow) +// +// Input: i = land use index +// p = pollut. index +// area = sucatchment area (ft2) +// landFactor[] = array of land use data for subcatchment +// runoff = runoff flow generated by subcatchment (ft/sec) +// vOutflow = runoff volume leaving the subcatchment (ft3) +// Output: returns pollutant runoff load (mass) +// Purpose: computes pollutant load generated by a land use over a time step. +// +{ + double landuseArea; // area of current land use (ft2) + double buildup; // current pollutant buildup (lb or kg) + double washoffQual; // pollutant concentration in washoff (mass/ft3) + double washoffLoad; // pollutant washoff load over time step (lb or kg) + double bmpRemoval; // pollutant load removed by BMP treatment (lb or kg) + + // --- compute concen. of pollutant in washoff (mass/ft3) + buildup = landFactor[i].buildup[p]; + landuseArea = landFactor[i].fraction * area; + washoffQual = landuse_getWashoffQual(i, p, buildup, runoff, landuseArea); + + // --- compute washoff load exported (lbs or kg) from landuse + // (Pollut[].mcf converts from mg (or ug) mass units to lbs (or kg) + washoffLoad = washoffQual * vOutflow * landuseArea / area * Pollut[p].mcf; + + // --- if buildup modelled, reduce it by amount of washoff + if ( Landuse[i].buildupFunc[p].funcType != NO_BUILDUP || + buildup > washoffLoad ) + { + washoffLoad = MIN(washoffLoad, buildup); + buildup -= washoffLoad; + landFactor[i].buildup[p] = buildup; + } + + // --- otherwise add washoff to buildup mass balance totals + // so that things will balance + else + { + massbal_updateLoadingTotals(BUILDUP_LOAD, p, washoffLoad); + landFactor[i].buildup[p] = 0.0; + } + + // --- apply any BMP removal to washoff + bmpRemoval = Landuse[i].washoffFunc[p].bmpEffic * washoffLoad; + if ( bmpRemoval > 0.0 ) + { + massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, bmpRemoval); + washoffLoad -= bmpRemoval; + } + + // --- return washoff load converted back to mass (mg or ug) + return washoffLoad / Pollut[p].mcf; +} + +//============================================================================= + +double landuse_getWashoffQual(int i, int p, double buildup, double runoff, + double area) +// +// Input: i = land use index +// p = pollutant index +// buildup = current buildup over land use (lbs or kg) +// runoff = current runoff on subcatchment (ft/sec) +// area = area devoted to land use (ft2) +// Output: returns pollutant concentration in washoff (mass/ft3) +// Purpose: finds concentration of pollutant washed off a land use. +// +// Notes: "coeff" for each washoff function was previously adjusted to +// result in units of mass/sec +// +{ + double cWashoff = 0.0; + double coeff = Landuse[i].washoffFunc[p].coeff; + double expon = Landuse[i].washoffFunc[p].expon; + int func = Landuse[i].washoffFunc[p].funcType; + + // --- if no washoff function or no runoff, return 0 + if ( func == NO_WASHOFF || runoff == 0.0 ) return 0.0; + + // --- if buildup function exists but no current buildup, return 0 + if ( Landuse[i].buildupFunc[p].funcType != NO_BUILDUP && buildup == 0.0 ) + return 0.0; + + // --- Exponential Washoff function + if ( func == EXPON_WASHOFF ) + { + // --- evaluate washoff eqn. with runoff in in/hr (or mm/hr) + // and buildup converted from lbs (or kg) to concen. mass units + cWashoff = coeff * pow(runoff * UCF(RAINFALL), expon) * + buildup / Pollut[p].mcf; + cWashoff /= runoff * area; + } + + // --- Rating Curve Washoff function + else if ( func == RATING_WASHOFF ) + { + cWashoff = coeff * pow(runoff * area, expon-1.0); + } + + // --- Event Mean Concentration Washoff + else if ( func == EMC_WASHOFF ) + { + cWashoff = coeff; // coeff includes LperFT3 factor + } + return cWashoff; +} + +//============================================================================= + +double landuse_getCoPollutLoad(int p, double washoff[]) +// +// Input: p = pollutant index +// washoff = pollut. washoff rate (mass/sec) +// Output: returns washoff mass added by co-pollutant relation (mass) +// Purpose: finds washoff mass added by a co-pollutant of a given pollutant. +// +{ + int k; + double w; + + // --- check if pollutant p has a co-pollutant k + k = Pollut[p].coPollut; + if ( k >= 0 ) + { + // --- compute addition to washoff from co-pollutant + w = Pollut[p].coFraction * washoff[k]; + + // --- add washoff to buildup mass balance totals + // so that things will balance + massbal_updateLoadingTotals(BUILDUP_LOAD, p, w * Pollut[p].mcf); + return w; + } + return 0.0; +} + +//============================================================================= + +double landuse_getExternalBuildup(int i, int p, double buildup, double tStep) +// +// Input: i = landuse index +// p = pollutant index +// buildup = buildup at start of time step (mass/unit) +// tStep = time step (sec) +// Output: returns pollutant buildup at end of time interval (mass/unit) +// Purpose: finds pollutant buildup contributed by external loading over a +// given time step. +// +{ + double maxBuildup = Landuse[i].buildupFunc[p].coeff[0]; + double sf = Landuse[i].buildupFunc[p].coeff[1]; // scaling factor + int ts = (int)floor(Landuse[i].buildupFunc[p].coeff[2]); // time series index + double rate = 0.0; + + // --- no buildup increment at start of simulation + if (NewRunoffTime == 0.0) return 0.0; + + // --- get buildup rate (mass/unit/day) over the interval + if ( ts >= 0 ) + { + rate = sf * table_tseriesLookup(&Tseries[ts], + getDateTime(NewRunoffTime), FALSE); + } + + // --- compute buildup at end of time interval + buildup = buildup + rate * tStep / SECperDAY; + buildup = MIN(buildup, maxBuildup); + return buildup; +} diff --git a/src/lid.c b/src/lid.c new file mode 100644 index 000000000..7bb69dcef --- /dev/null +++ b/src/lid.c @@ -0,0 +1,2031 @@ +//----------------------------------------------------------------------------- +// lid.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// This module handles all data processing involving LID (Low Impact +// Development) practices used to treat runoff for individual subcatchments +// within a project. The actual computation of LID performance is made by +// functions within the lidproc.c module. See LidTypes below for the types +// of LIDs that can be modeled. +// +// An LID process is described by the TLidProc data structure and consists of +// size-independent design data for the different vertical layers that make +// up a specific type of LID. The collection of these LID process designs is +// stored in the LidProcs array. +// +// When a member of LidProcs is to be deployed in a particular subcatchment, +// its sizing and treatment data are stored in a TLidUnit data structure. +// The collection of all TLidUnits deployed in a subcatchment is held in a +// TLidGroup list data structure. The LidGroups array contains a TLidGroup +// list for each subcatchment in the project. +// +// During a runoff time step, each subcatchment calls the lid_getRunoff() +// function to compute flux rates and a water balance through each layer +// of each LID unit in the subcatchment. The resulting outflows (runoff, +// drain flow, evaporation and infiltration) are added to those computed +// for the non-LID portion of the subcatchment. +// +// An option exists for the detailed time series of flux rates and storage +// levels for a specific LID unit to be written to a text file named by the +// user for viewing outside of the SWMM program. +// +// Update History +// ============== +// Build 5.1.008: +// - More input error reporting added. +// - Rooftop Disconnection added to the types of LIDs. +// - LID drain flows are now tracked separately. +// - LID drain flows can now be routed to separate outlets. +// - Check added to insure LID flows not returned to nonexistent pervious area. +// Build 5.1.009: +// - Fixed bug where LID's could return outflow to non-LID area when LIDs +// make up entire subcatchment. +// Build 5.1.010: +// - Support for new Modified Green Ampt infiltration model added. +// - Imported variable HasWetLids now properly initialized. +// - Initial state of reporting (lidUnit->rptFile->wasDry) changed to +// prevent duplicate printing of first line of detailed report file. +// Build 5.1.011: +// - The top of the storage layer is no longer used as a limit for an +// underdrain offset thus allowing upturned drains to be modeled. +// - Column headings for the detailed LID report file were modified. +// Build 5.1.012: +// - Redefined initialization of wasDry for LID reporting. +// Build 5.1.013: +// - Support added for LID units treating pervious area runoff. +// - Support added for open/closed head levels and multiplier v. head +// control curve for underdrain flow. +// - Support added for unclogging permeable pavement at fixed intervals. +// - Support added for pollutant removal in underdrain flow. +// Build 5.1.014: +// - Fixed bug in creating LidProcs when there are no subcatchments. +// - Fixed bug in adding underdrain pollutant loads to mass balances. +// Build 5.1.015: +// - Support added for mutiple infiltration methods within a project. +// Build 5.2.0: +// - Covered property added to RAIN_BARREL parameters +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "headers.h" +#include "lid.h" + +#define ERR_PAVE_LAYER " - check pavement layer parameters" +#define ERR_SOIL_LAYER " - check soil layer parameters" +#define ERR_STOR_LAYER " - check storage layer parameters" +#define ERR_SWALE_SURF " - check swale surface parameters" +#define ERR_GREEN_AMPT " - check subcatchment Green-Ampt parameters" +#define ERR_DRAIN_OFFSET " - drain offset exceeds storage height" +#define ERR_DRAIN_HEADS " - invalid drain open/closed heads" +#define ERR_SWALE_WIDTH " - invalid swale width" + +//----------------------------------------------------------------------------- +// Enumerations +//----------------------------------------------------------------------------- +enum LidLayerTypes { + SURF, // surface layer + SOIL, // soil layer + STOR, // storage layer + PAVE, // pavement layer + DRAINMAT, // drainage mat layer + DRAIN, // underdrain system + REMOVALS}; // pollutant removals + +//// Note: DRAINMAT must be placed before DRAIN so the two keywords can +/// be distinguished from one another when parsing a line of input. + +char* LidLayerWords[] = + {"SURFACE", "SOIL", "STORAGE", "PAVEMENT", "DRAINMAT", "DRAIN", + "REMOVALS", NULL}; + +char* LidTypeWords[] = + {"BC", //bio-retention cell + "RG", //rain garden + "GR", //green roof + "IT", //infiltration trench + "PP", //porous pavement + "RB", //rain barrel + "VS", //vegetative swale + "RD", //rooftop disconnection + NULL}; + +//----------------------------------------------------------------------------- +// Data Structures +//----------------------------------------------------------------------------- + +// LID List - list of LID units contained in an LID group +struct LidList +{ + TLidUnit* lidUnit; // ptr. to a LID unit + struct LidList* nextLidUnit; +}; +typedef struct LidList TLidList; + +// LID Group - collection of LID units applied to a specific subcatchment +struct LidGroup +{ + double pervArea; // amount of pervious area in group (ft2) + double flowToPerv; // total flow sent to pervious area (cfs) + double oldDrainFlow; // total drain flow in previous period (cfs) + double newDrainFlow; // total drain flow in current period (cfs) + TLidList* lidList; // list of LID units in the group +}; +typedef struct LidGroup* TLidGroup; + + +//----------------------------------------------------------------------------- +// Shared Variables +//----------------------------------------------------------------------------- +static TLidProc* LidProcs; // array of LID processes +static int LidCount; // number of LID processes +static TLidGroup* LidGroups; // array of LID process groups +static int GroupCount; // number of LID groups (subcatchments) + +static double EvapRate; // evaporation rate (ft/s) +static double NativeInfil; // native soil infil. rate (ft/s) +static double MaxNativeInfil; // native soil infil. rate limit (ft/s) + +//----------------------------------------------------------------------------- +// Imported Variables (from SUBCATCH.C) +//----------------------------------------------------------------------------- +// Volumes (ft3) for a subcatchment over a time step +extern double Vevap; // evaporation +extern double Vpevap; // pervious area evaporation +extern double Vinfil; // non-LID infiltration +extern double VlidInfil; // infiltration from LID units +extern double VlidIn; // impervious area flow to LID units +extern double VlidOut; // surface outflow from LID units +extern double VlidDrain; // drain outflow from LID units +extern double VlidReturn; // LID outflow returned to pervious area +extern char HasWetLids; // TRUE if any LIDs are wet + // (from RUNOFF.C) + +//----------------------------------------------------------------------------- +// External Functions (prototyped in lid.h) +//----------------------------------------------------------------------------- +// lid_create called by createObjects in project.c +// lid_delete called by deleteObjects in project.c +// lid_validate called by project_validate +// lid_initState called by project_init + +// lid_readProcParams called by parseLine in input.c +// lid_readGroupParams called by parseLine in input.c + +// lid_setOldGroupState called by subcatch_setOldState +// lid_setReturnQual called by findLidLoads in surfqual.c +// lid_getReturnQual called by subcatch_getRunon + +// lid_getPervArea called by subcatch_getFracPerv +// lid_getFlowToPerv called by subcatch_getRunon +// lid_getSurfaceDepth called by subcatch_getDepth +// lid_getDepthOnPavement called by sweptSurfacesDry in subcatch.c +// lid_getStoredVolume called by subcatch_getStorage +// lid_getRunon called by subcatch_getRunon +// lid_getRunoff called by subcatch_getRunoff + +// lid_addDrainRunon called by subcatch_getRunon +// lid_addDrainLoads called by surfqual_getWashoff +// lid_addDrainInflow called by addLidDrainInflows in routing.c + +// lid_writeSummary called by inputrpt_writeInput +// lid_writeWaterBalance called by statsrpt_writeReport + + +//----------------------------------------------------------------------------- +// Local Functions +//----------------------------------------------------------------------------- +static void freeLidGroup(int j); +static int readSurfaceData(int j, char* tok[], int ntoks); +static int readPavementData(int j, char* tok[], int ntoks); +static int readSoilData(int j, char* tok[], int ntoks); +static int readStorageData(int j, char* tok[], int ntoks); +static int readDrainData(int j, char* tok[], int ntoks); +static int readDrainMatData(int j, char* toks[], int ntoks); +static int readRemovalsData(int j, char* toks[], int ntoks); + +static int addLidUnit(int j, int k, int n, double x[], char* fname, + int drainSubcatch, int drainNode); +static int createLidRptFile(TLidUnit* lidUnit, char* fname); +static void initLidRptFile(char* title, char* lidID, char* subcatchID, + TLidUnit* lidUnit); +static void validateLidProc(int j); +static void validateLidGroup(int j); + +static int isLidPervious(int k); +static double getImpervAreaRunoff(int j); +static double getPervAreaRunoff(int j); +static double getSurfaceDepth(int subcatch); +static double getRainInflow(int j, TLidUnit* lidUnit); +static void findNativeInfil(int j, double tStep); + + +static void evalLidUnit(int j, TLidUnit* lidUnit, double lidArea, + double lidInflow, double tStep, double *qRunoff, + double *qDrain, double *qReturn); + +//============================================================================= + +void lid_create(int lidCount, int subcatchCount) +// +// Purpose: creates an array of LID objects. +// Input: n = number of LID processes +// Output: none +// +{ + int j; + + //... assign NULL values to LID arrays + LidProcs = NULL; + LidGroups = NULL; + LidCount = lidCount; + + //... create LID groups + GroupCount = subcatchCount; + if ( GroupCount > 0 ) + { + LidGroups = (TLidGroup *) calloc(GroupCount, sizeof(TLidGroup)); + if ( LidGroups == NULL ) + { + ErrorCode = ERR_MEMORY; + return; + } + } + + //... initialize LID groups + for (j = 0; j < GroupCount; j++) LidGroups[j] = NULL; + + //... create LID objects + if ( LidCount == 0 ) return; + LidProcs = (TLidProc *) calloc(LidCount, sizeof(TLidProc)); + if ( LidProcs == NULL ) + { + ErrorCode = ERR_MEMORY; + return; + } + + //... initialize LID objects + for (j = 0; j < LidCount; j++) + { + LidProcs[j].lidType = -1; + LidProcs[j].surface.thickness = 0.0; + LidProcs[j].surface.voidFrac = 1.0; + LidProcs[j].surface.roughness = 0.0; + LidProcs[j].surface.surfSlope = 0.0; + LidProcs[j].pavement.thickness = 0.0; + LidProcs[j].soil.thickness = 0.0; + LidProcs[j].storage.thickness = 0.0; + LidProcs[j].storage.kSat = 0.0; + LidProcs[j].drain.coeff = 0.0; + LidProcs[j].drain.offset = 0.0; + LidProcs[j].drainMat.thickness = 0.0; + LidProcs[j].drainMat.roughness = 0.0; + LidProcs[j].drainRmvl = NULL; + LidProcs[j].drainRmvl = (double *) + calloc(Nobjects[POLLUT], sizeof(double)); + if (LidProcs[j].drainRmvl == NULL) + { + ErrorCode = ERR_MEMORY; + return; + } + } +} + +//============================================================================= + +void lid_delete() +// +// Purpose: deletes all LID objects +// Input: none +// Output: none +// +{ + int j; + for (j = 0; j < GroupCount; j++) freeLidGroup(j); + FREE(LidGroups); + for (j = 0; j < LidCount; j++) FREE(LidProcs[j].drainRmvl); + FREE(LidProcs); + GroupCount = 0; + LidCount = 0; +} + +//============================================================================= + +void freeLidGroup(int j) +// +// Purpose: frees all LID units associated with a subcatchment. +// Input: j = group (or subcatchment) index +// Output: none +// +{ + TLidGroup lidGroup = LidGroups[j]; + TLidList* lidList; + TLidUnit* lidUnit; + TLidList* nextLidUnit; + + if ( lidGroup == NULL ) return; + lidList = lidGroup->lidList; + while (lidList) + { + lidUnit = lidList->lidUnit; + if ( lidUnit->rptFile ) + { + if ( lidUnit->rptFile->file ) fclose(lidUnit->rptFile->file); + free(lidUnit->rptFile); + } + nextLidUnit = lidList->nextLidUnit; + free(lidUnit); + free(lidList); + lidList = nextLidUnit; + } + free(lidGroup); + LidGroups[j] = NULL; +} + +//============================================================================= + +int lid_readProcParams(char* toks[], int ntoks) +// +// Purpose: reads LID process information from line of input data file +// Input: toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format for first line that defines a LID process is: +// LID_ID LID_Type +// +// Followed by some combination of lines below depending on LID_Type: +// LID_ID SURFACE +// LID_ID PAVEMENT +// LID_ID SOIL +// LID_ID STORAGE +// LID_ID DRAIN +// LID_ID DRAINMAT +// LID_ID REMOVALS +// +{ + int j, m; + + // --- check for minimum number of tokens + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that LID exists in database + j = project_findObject(LID, toks[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, toks[0]); + + // --- assign ID if not done yet + if ( LidProcs[j].ID == NULL ) + LidProcs[j].ID = project_findID(LID, toks[0]); + + // --- check if second token is the type of LID + m = findmatch(toks[1], LidTypeWords); + if ( m >= 0 ) + { + LidProcs[j].lidType = m; + return 0; + } + + // --- check if second token is name of LID layer + else m = findmatch(toks[1], LidLayerWords); + + // --- read input parameters for the identified layer + switch (m) + { + case SURF: return readSurfaceData(j, toks, ntoks); + case SOIL: return readSoilData(j, toks, ntoks); + case STOR: return readStorageData(j, toks, ntoks); + case PAVE: return readPavementData(j, toks, ntoks); + case DRAIN: return readDrainData(j, toks, ntoks); + case DRAINMAT: return readDrainMatData(j, toks, ntoks); + case REMOVALS: return readRemovalsData(j, toks, ntoks); + } + return error_setInpError(ERR_KEYWORD, toks[1]); +} + +//============================================================================= + +int lid_readGroupParams(char* toks[], int ntoks) +// +// Purpose: reads input data for a LID unit placed in a subcatchment. +// Input: toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of input data line is: +// Subcatch_ID LID_ID Number Area Width InitSat FromImp ToPerv +// (RptFile DrainTo FromPerv) +// where: +// Subcatch_ID = name of subcatchment +// LID_ID = name of LID process +// Number (n) = number of replicate units +// Area (x[0]) = area of each unit +// Width (x[1]) = outflow width of each unit +// InitSat (x[2]) = % that LID is initially saturated +// FromImp (x[3]) = % of impervious runoff sent to LID +// ToPerv (x[4]) = 1 if outflow goes to pervious sub-area; 0 if not +// RptFile = name of detailed results file (optional) +// DrainTo = name of subcatch/node for drain flow (optional) +// FromPerv (x[5]) = % of pervious runoff sent to LID +// +{ + int i, j, k, n; + double x[6]; + char* fname = NULL; + int drainSubcatch = -1, drainNode = -1; + + //... check for valid number of input tokens + if ( ntoks < 8 ) return error_setInpError(ERR_ITEMS, ""); + + //... find subcatchment + j = project_findObject(SUBCATCH, toks[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, toks[0]); + + //... find LID process in list of LID processes + k = project_findObject(LID, toks[1]); + if ( k < 0 ) return error_setInpError(ERR_NAME, toks[1]); + + //... get number of replicates + n = atoi(toks[2]); + if ( n < 0 ) return error_setInpError(ERR_NUMBER, toks[2]); + if ( n == 0 ) return 0; + + //... convert next 4 tokens to doubles + for (i = 3; i <= 7; i++) + { + if ( ! getDouble(toks[i], &x[i-3]) || x[i-3] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + //... check for valid percentages on tokens 5 & 6 (x[2] & x[3]) + for (i = 2; i <= 3; i++) if ( x[i] > 100.0 ) + return error_setInpError(ERR_NUMBER, toks[i+3]); + + //... read optional report file name + if ( ntoks >= 9 && strcmp(toks[8], "*") != 0 ) fname = toks[8]; + + //... read optional underdrain outlet + if ( ntoks >= 10 && strcmp(toks[9], "*") != 0 ) + { + drainSubcatch = project_findObject(SUBCATCH, toks[9]); + if ( drainSubcatch < 0 ) + { + drainNode = project_findObject(NODE, toks[9]); + if ( drainNode < 0 ) return error_setInpError(ERR_NAME, toks[9]); + } + } + + //... read percent of pervious area treated by LID unit + x[5] = 0.0; + if (ntoks >= 11) + { + if (!getDouble(toks[10], &x[5]) || x[5] < 0.0 || x[5] > 100.0) + return error_setInpError(ERR_NUMBER, toks[10]); + } + + //... create a new LID unit and add it to the subcatchment's LID group + return addLidUnit(j, k, n, x, fname, drainSubcatch, drainNode); +} + +//============================================================================= + +int addLidUnit(int j, int k, int n, double x[], char* fname, + int drainSubcatch, int drainNode) +// +// Purpose: adds an LID unit to a subcatchment's LID group. +// Input: j = subcatchment index +// k = LID control index +// n = number of replicate units +// x = LID unit's parameters +// fname = name of detailed performance report file +// drainSubcatch = index of subcatchment receiving underdrain flow +// drainNode = index of node receiving underdrain flow +// Output: returns an error code +// +{ + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... create a LID group (pointer to an LidGroup struct) + // if one doesn't already exist + lidGroup = LidGroups[j]; + if ( !lidGroup ) + { + lidGroup = (struct LidGroup *) malloc(sizeof(struct LidGroup)); + if ( !lidGroup ) return error_setInpError(ERR_MEMORY, ""); + lidGroup->lidList = NULL; + LidGroups[j] = lidGroup; + } + + //... create a new LID unit to add to the group + lidUnit = (TLidUnit *) malloc(sizeof(TLidUnit)); + if ( !lidUnit ) return error_setInpError(ERR_MEMORY, ""); + lidUnit->rptFile = NULL; + + //... add the LID unit to the group + lidList = (TLidList *) malloc(sizeof(TLidList)); + if ( !lidList ) + { + free(lidUnit); + return error_setInpError(ERR_MEMORY, ""); + } + lidList->lidUnit = lidUnit; + lidList->nextLidUnit = lidGroup->lidList; + lidGroup->lidList = lidList; + + //... assign parameter values to LID unit + lidUnit->lidIndex = k; + lidUnit->number = n; + lidUnit->area = x[0] / SQR(UCF(LENGTH)); + lidUnit->fullWidth = x[1] / UCF(LENGTH); + lidUnit->initSat = x[2] / 100.0; + lidUnit->fromImperv = x[3] / 100.0; + lidUnit->toPerv = (x[4] > 0.0); + lidUnit->fromPerv = x[5] / 100.0; + lidUnit->drainSubcatch = drainSubcatch; + lidUnit->drainNode = drainNode; + + //... open report file if it was supplied + if ( fname != NULL ) + { + if ( !createLidRptFile(lidUnit, fname) ) + return error_setInpError(ERR_RPT_FILE, fname); + } + return 0; +} + +//============================================================================= + +int createLidRptFile(TLidUnit* lidUnit, char* fname) +{ + TLidRptFile* rptFile; + + rptFile = (TLidRptFile *) malloc(sizeof(TLidRptFile)); + if ( rptFile == NULL ) return 0; + lidUnit->rptFile = rptFile; + rptFile->file = fopen(fname, "wt"); + if ( rptFile->file == NULL ) return 0; + return 1; +} + +//============================================================================= + +int readSurfaceData(int j, char* toks[], int ntoks) +// +// Purpose: reads surface layer data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID SURFACE StorageHt VegVolFrac Roughness SurfSlope SideSlope +// +{ + int i; + double x[5]; + + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 2; i < 7; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + if ( x[1] >= 1.0 ) return error_setInpError(ERR_NUMBER, toks[3]); + if ( x[0] == 0.0 ) x[1] = 0.0; + + LidProcs[j].surface.thickness = x[0] / UCF(RAINDEPTH); + LidProcs[j].surface.voidFrac = 1.0 - x[1]; + LidProcs[j].surface.roughness = x[2]; + LidProcs[j].surface.surfSlope = x[3] / 100.0; + LidProcs[j].surface.sideSlope = x[4]; + return 0; +} + +//============================================================================= + +int readPavementData(int j, char* toks[], int ntoks) +// +// Purpose: reads pavement layer data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID PAVEMENT Thickness VoidRatio FracImperv Permeability ClogFactor +// (RegenDays RegenDegree) +// +{ + int i; + double x[7]; + + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 2; i < 7; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + // ... read optional clogging regeneration properties + x[5] = 0.0; + if (ntoks > 7) + { + if (!getDouble(toks[7], &x[5]) || x[5] < 0.0) + return error_setInpError(ERR_NUMBER, toks[7]); + } + x[6] = 0.0; + if (ntoks > 8) + { + if (!getDouble(toks[8], &x[6]) || x[6] < 0.0 || x[6] > 1.0) + return error_setInpError(ERR_NUMBER, toks[8]); + } + + //... convert void ratio to void fraction + x[1] = x[1]/(x[1] + 1.0); + + LidProcs[j].pavement.thickness = x[0] / UCF(RAINDEPTH); + LidProcs[j].pavement.voidFrac = x[1]; + LidProcs[j].pavement.impervFrac = x[2]; + LidProcs[j].pavement.kSat = x[3] / UCF(RAINFALL); + LidProcs[j].pavement.clogFactor = x[4]; + LidProcs[j].pavement.regenDays = x[5]; + LidProcs[j].pavement.regenDegree = x[6]; + return 0; +} + +//============================================================================= + +int readSoilData(int j, char* toks[], int ntoks) +// +// Purpose: reads soil layer data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID SOIL Thickness Porosity FieldCap WiltPt Ksat Kslope Suction +// +{ + int i; + double x[7]; + + if ( ntoks < 9 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 2; i < 9; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + LidProcs[j].soil.thickness = x[0] / UCF(RAINDEPTH); + LidProcs[j].soil.porosity = x[1]; + LidProcs[j].soil.fieldCap = x[2]; + LidProcs[j].soil.wiltPoint = x[3]; + LidProcs[j].soil.kSat = x[4] / UCF(RAINFALL); + LidProcs[j].soil.kSlope = x[5]; + LidProcs[j].soil.suction = x[6] / UCF(RAINDEPTH); + return 0; +} + +//============================================================================= + +int readStorageData(int j, char* toks[], int ntoks) +// +// Purpose: reads drainage layer data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID STORAGE Thickness VoidRatio Ksat ClogFactor (YES/NO) +// +{ + int i; + int covered = FALSE; + double x[6]; + + //... read numerical parameters + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 2; i < 6; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + //... check if rain barrel is covered + if (ntoks > 6) + { + if (match(toks[6], w_YES)) + covered = TRUE; + } + + //... convert void ratio to void fraction + x[1] = x[1]/(x[1] + 1.0); + + //... save parameters to LID storage layer structure + LidProcs[j].storage.thickness = x[0] / UCF(RAINDEPTH); + LidProcs[j].storage.voidFrac = x[1]; + LidProcs[j].storage.kSat = x[2] / UCF(RAINFALL); + LidProcs[j].storage.clogFactor = x[3]; + LidProcs[j].storage.covered = covered; + return 0; +} + +//============================================================================= + +int readDrainData(int j, char* toks[], int ntoks) +// +// Purpose: reads underdrain data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID DRAIN coeff expon offset delay hOpen hClose curve +// +{ + int i; + double x[6]; + + //... read numerical parameters + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 0; i < 6; i++) x[i] = 0.0; + for (i = 2; i < 8; i++) + { + if ( (ntoks > i) && (! getDouble(toks[i], &x[i-2]) || x[i-2]) < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + i = -1; + if ( ntoks >= 9 ) + { + i = project_findObject(CURVE, toks[8]); + if (i < 0) return error_setInpError(ERR_NAME, toks[8]); + } + + //... save parameters to LID drain layer structure + LidProcs[j].drain.coeff = x[0]; + LidProcs[j].drain.expon = x[1]; + LidProcs[j].drain.offset = x[2] / UCF(RAINDEPTH); + LidProcs[j].drain.delay = x[3] * 3600.0; + LidProcs[j].drain.hOpen = x[4] / UCF(RAINDEPTH); + LidProcs[j].drain.hClose = x[5] / UCF(RAINDEPTH); + LidProcs[j].drain.qCurve = i; + return 0; +} + +//============================================================================= + +int readDrainMatData(int j, char* toks[], int ntoks) +// +// Purpose: reads drainage mat data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID DRAINMAT thickness voidRatio roughness +// +{ + int i; + double x[3]; + + //... read numerical parameters + if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); + if ( LidProcs[j].lidType != GREEN_ROOF ) return 0; + for (i = 2; i < 5; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + //... save parameters to LID drain layer structure + LidProcs[j].drainMat.thickness = x[0] / UCF(RAINDEPTH);; + LidProcs[j].drainMat.voidFrac = x[1]; + LidProcs[j].drainMat.roughness = x[2]; + return 0; +} + +//============================================================================= + +int readRemovalsData(int j, char* toks[], int ntoks) +// +// Purpose: reads pollutant removal data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID REMOVALS pollut1 %removal1 pollut2 %removal2 ... +// +{ + int i = 2; + int p; + double rmvl; + + //... start with 3rd token + if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); + while (ntoks > i) + { + //... find pollutant index from its name + p = project_findObject(POLLUT, toks[i]); + if (p < 0) return error_setInpError(ERR_NAME, toks[i]); + + //... check that a next token exists + i++; + if (ntoks == i) return error_setInpError(ERR_ITEMS, ""); + + //... get the % removal value from the next token + if (!getDouble(toks[i], &rmvl) || rmvl < 0.0 || rmvl > 100.0) + return error_setInpError(ERR_NUMBER, toks[i]); + + //... save the pollutant removal for the LID process as a fraction + LidProcs[j].drainRmvl[p] = rmvl / 100.0; + i++; + } + return 0; +} +//============================================================================= + +void lid_writeSummary() +// +// Purpose: writes summary of LID processes used to report file. +// Input: none +// Output: none +// +{ + int j, k; + double pctArea; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + fprintf(Frpt.file, "\n"); + fprintf(Frpt.file, "\n"); + fprintf(Frpt.file, "\n *******************"); + fprintf(Frpt.file, "\n LID Control Summary"); + fprintf(Frpt.file, "\n *******************"); + + + fprintf(Frpt.file, +"\n No. of Unit Unit %% Area %% Imperv %% Perv"); //(5.1.013) + fprintf(Frpt.file, // +"\n Subcatchment LID Control Units Area Width Covered Treated Treated"); // + fprintf(Frpt.file, // +"\n ---------------------------------------------------------------------------------------------------"); // + + for (j = 0; j < GroupCount; j++) + { + lidGroup = LidGroups[j]; + if ( lidGroup == NULL ) continue; + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + pctArea = lidUnit->area * lidUnit->number / Subcatch[j].area * 100.0; + fprintf(Frpt.file, "\n %-16s %-16s", Subcatch[j].ID, LidProcs[k].ID); + fprintf(Frpt.file, "%6d %10.2f %10.2f %10.2f %10.2f %10.2f", + lidUnit->number, lidUnit->area * SQR(UCF(LENGTH)), + lidUnit->fullWidth * UCF(LENGTH), pctArea, + lidUnit->fromImperv*100.0, lidUnit->fromPerv*100.0); + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_validate() +// +// Purpose: validates LID process and group parameters. +// Input: none +// Output: none +// +{ + int j; + for (j = 0; j < LidCount; j++) validateLidProc(j); + for (j = 0; j < GroupCount; j++) validateLidGroup(j); +} + +//============================================================================= + +void validateLidProc(int j) +// +// Purpose: validates LID process parameters. +// Input: j = LID process index +// Output: none +// +{ + int layerMissing = FALSE; + + //... check that LID type was supplied + if ( LidProcs[j].lidType < 0 ) + { + report_writeErrorMsg(ERR_LID_TYPE, LidProcs[j].ID); + return; + } + + //... check that required layers were defined + switch (LidProcs[j].lidType) + { + case BIO_CELL: + case RAIN_GARDEN: + if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; + break; + case GREEN_ROOF: + if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; + if ( LidProcs[j].drainMat.thickness <= 0.0) layerMissing = TRUE; + break; + case POROUS_PAVEMENT: + if ( LidProcs[j].pavement.thickness <= 0.0 ) layerMissing = TRUE; + break; + case INFIL_TRENCH: + if ( LidProcs[j].storage.thickness <= 0.0 ) layerMissing = TRUE; + break; + } + if ( layerMissing ) + { + report_writeErrorMsg(ERR_LID_LAYER, LidProcs[j].ID); + return; + } + + //... check pavement layer parameters + if ( LidProcs[j].lidType == POROUS_PAVEMENT ) + { + if ( LidProcs[j].pavement.thickness <= 0.0 + || LidProcs[j].pavement.kSat <= 0.0 + || LidProcs[j].pavement.voidFrac <= 0.0 + || LidProcs[j].pavement.voidFrac > 1.0 + || LidProcs[j].pavement.impervFrac > 1.0 ) + + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_PAVE_LAYER, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... check soil layer parameters + if ( LidProcs[j].soil.thickness > 0.0 ) + { + if ( LidProcs[j].soil.porosity <= 0.0 + || LidProcs[j].soil.fieldCap >= LidProcs[j].soil.porosity + || LidProcs[j].soil.wiltPoint >= LidProcs[j].soil.fieldCap + || LidProcs[j].soil.kSat <= 0.0 + || LidProcs[j].soil.kSlope < 0.0 ) + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_SOIL_LAYER, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... check storage layer parameters + if ( LidProcs[j].storage.thickness > 0.0 ) + { + if ( LidProcs[j].storage.voidFrac <= 0.0 || + LidProcs[j].storage.voidFrac > 1.0 ) + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_STOR_LAYER, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... if no storage layer adjust void fraction and drain offset + else + { + LidProcs[j].storage.voidFrac = 1.0; + LidProcs[j].drain.offset = 0.0; + } + + //... check for invalid drain open/closed heads + if (LidProcs[j].drain.hOpen > 0.0 && + LidProcs[j].drain.hOpen <= LidProcs[j].drain.hClose) + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_DRAIN_HEADS, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + + //... compute the surface layer's overland flow constant (alpha) + if ( LidProcs[j].lidType == VEG_SWALE ) + { + if ( LidProcs[j].surface.roughness * + LidProcs[j].surface.surfSlope <= 0.0 || + LidProcs[j].surface.thickness == 0.0 + ) + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_SWALE_SURF, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + else LidProcs[j].surface.alpha = + 1.49 * sqrt(LidProcs[j].surface.surfSlope) / + LidProcs[j].surface.roughness; + } + else + { + //... compute surface overland flow coeff. + if ( LidProcs[j].surface.roughness > 0.0 ) + LidProcs[j].surface.alpha = 1.49 / LidProcs[j].surface.roughness * + sqrt(LidProcs[j].surface.surfSlope); + else LidProcs[j].surface.alpha = 0.0; + } + + //... compute drainage mat layer's flow coeff. + if ( LidProcs[j].drainMat.roughness > 0.0 ) + { + LidProcs[j].drainMat.alpha = 1.49 / LidProcs[j].drainMat.roughness * + sqrt(LidProcs[j].surface.surfSlope); + } + else LidProcs[j].drainMat.alpha = 0.0; + + + //... convert clogging factors to void volume basis + if ( LidProcs[j].pavement.thickness > 0.0 ) + { + LidProcs[j].pavement.clogFactor *= + LidProcs[j].pavement.thickness * LidProcs[j].pavement.voidFrac * + (1.0 - LidProcs[j].pavement.impervFrac); + } + if ( LidProcs[j].storage.thickness > 0.0 ) + { + LidProcs[j].storage.clogFactor *= + LidProcs[j].storage.thickness * LidProcs[j].storage.voidFrac; + } + else LidProcs[j].storage.clogFactor = 0.0; + + //... for certain LID types, immediate overflow of excess surface water + // occurs if either the surface roughness or slope is zero + LidProcs[j].surface.canOverflow = TRUE; + switch (LidProcs[j].lidType) + { + case ROOF_DISCON: LidProcs[j].surface.canOverflow = FALSE; break; + case INFIL_TRENCH: + case POROUS_PAVEMENT: + case BIO_CELL: + case RAIN_GARDEN: + case GREEN_ROOF: + if ( LidProcs[j].surface.alpha > 0.0 ) + LidProcs[j].surface.canOverflow = FALSE; + } + + //... rain barrels have 100% void space and impermeable bottom + if ( LidProcs[j].lidType == RAIN_BARREL ) + { + LidProcs[j].storage.voidFrac = 1.0; + LidProcs[j].storage.kSat = 0.0; + } + + //... set storage layer parameters of a green roof + if ( LidProcs[j].lidType == GREEN_ROOF ) + { + LidProcs[j].storage.thickness = LidProcs[j].drainMat.thickness; + LidProcs[j].storage.voidFrac = LidProcs[j].drainMat.voidFrac; + LidProcs[j].storage.clogFactor = 0.0; + LidProcs[j].storage.kSat = 0.0; + } +} + +//============================================================================= + +void validateLidGroup(int j) +// +// Purpose: validates properties of LID units grouped in a subcatchment. +// Input: j = subcatchment index +// Output: returns 1 if data are valid, 0 if not +// +{ + int k; + double p[3]; + double totalArea = Subcatch[j].area; + double totalLidArea = 0.0; + double fromImperv = 0.0; + double fromPerv = 0.0; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + lidGroup = LidGroups[j]; + if ( lidGroup == NULL ) return; + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + + //... update contributing fractions + totalLidArea += (lidUnit->area * lidUnit->number); + fromImperv += lidUnit->fromImperv; + fromPerv += lidUnit->fromPerv; + + //... assign biocell soil layer infiltration parameters + lidUnit->soilInfil.Ks = 0.0; + if ( LidProcs[k].soil.thickness > 0.0 ) + { + p[0] = LidProcs[k].soil.suction * UCF(RAINDEPTH); + p[1] = LidProcs[k].soil.kSat * UCF(RAINFALL); + p[2] = (LidProcs[k].soil.porosity - LidProcs[k].soil.wiltPoint) * + (1.0 - lidUnit->initSat); + if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) + { + sstrncpy(Msg, LidProcs[k].ID, MAXMSG); + sstrcat(Msg, ERR_SOIL_LAYER, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... assign vegetative swale infiltration parameters + if ( LidProcs[k].lidType == VEG_SWALE ) + { + if ( Subcatch[j].infilModel == GREEN_AMPT || + Subcatch[j].infilModel == MOD_GREEN_AMPT ) + { + grnampt_getParams(j, p); + if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) + { + sstrncpy(Msg, LidProcs[k].ID, MAXMSG); + sstrcat(Msg, ERR_GREEN_AMPT, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + if ( lidUnit->fullWidth <= 0.0 ) + { + sstrncpy(Msg, LidProcs[k].ID, MAXMSG); + sstrcat(Msg, ERR_SWALE_WIDTH, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... LID unit cannot send outflow back to subcatchment's + // pervious area if none exists + if ( Subcatch[j].fracImperv >= 0.999 ) lidUnit->toPerv = 0; + + //... assign drain outlet if not set by user + if ( lidUnit->drainNode == -1 && lidUnit->drainSubcatch == -1 ) + { + lidUnit->drainNode = Subcatch[j].outNode; + lidUnit->drainSubcatch = Subcatch[j].outSubcatch; + } + lidList = lidList->nextLidUnit; + } + + //... check contributing area fractions + if ( totalLidArea > 1.001 * totalArea ) + { + report_writeErrorMsg(ERR_LID_AREAS, Subcatch[j].ID); + } + if ( fromImperv > 1.001 || fromPerv > 1.001 ) + { + report_writeErrorMsg(ERR_LID_CAPTURE_AREA, Subcatch[j].ID); + } + + //... Make subcatchment LID area equal total area if the two are close + if ( totalLidArea > 0.999 * totalArea ) totalLidArea = totalArea; + Subcatch[j].lidArea = totalLidArea; +} + +//============================================================================= + +void lid_initState() +// +// Purpose: initializes the internal state of each LID in a subcatchment. +// Input: none +// Output: none +// +{ + int i, j, k; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + double initVol; + double initDryTime = StartDryDays * SECperDAY; + + HasWetLids = FALSE; + for (j = 0; j < GroupCount; j++) + { + //... check if group exists + lidGroup = LidGroups[j]; + if ( lidGroup == NULL ) continue; + + //... initialize group variables + lidGroup->pervArea = 0.0; + lidGroup->flowToPerv = 0.0; + lidGroup->oldDrainFlow = 0.0; + lidGroup->newDrainFlow = 0.0; + + //... examine each LID in the group + lidList = lidGroup->lidList; + while ( lidList ) + { + //... initialize depth & moisture content + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + lidUnit->surfaceDepth = 0.0; + lidUnit->storageDepth = 0.0; + lidUnit->soilMoisture = 0.0; + lidUnit->paveDepth = 0.0; + lidUnit->dryTime = initDryTime; + lidUnit->volTreated = 0.0; + lidUnit->nextRegenDay = LidProcs[k].pavement.regenDays; + initVol = 0.0; + if ( LidProcs[k].soil.thickness > 0.0 ) + { + lidUnit->soilMoisture = LidProcs[k].soil.wiltPoint + + lidUnit->initSat * (LidProcs[k].soil.porosity - + LidProcs[k].soil.wiltPoint); + initVol += lidUnit->soilMoisture * LidProcs[k].soil.thickness; + } + if ( LidProcs[k].storage.thickness > 0.0 ) + { + lidUnit->storageDepth = lidUnit->initSat * + LidProcs[k].storage.thickness; + initVol += lidUnit->storageDepth * LidProcs[k].storage.voidFrac; + } + if ( LidProcs[k].drainMat.thickness > 0.0 ) + { + lidUnit->storageDepth = lidUnit->initSat * + LidProcs[k].drainMat.thickness; + initVol += lidUnit->storageDepth * LidProcs[k].drainMat.voidFrac; + } + if ( lidUnit->initSat > 0.0 ) HasWetLids = TRUE; + + //... initialize water balance totals + lidproc_initWaterBalance(lidUnit, initVol); + lidUnit->volTreated = 0.0; + + //... initialize report file for the LID + if ( lidUnit->rptFile ) + { + initLidRptFile(Title[0], LidProcs[k].ID, Subcatch[j].ID, lidUnit); + } + + //... initialize drain flows + lidUnit->oldDrainFlow = 0.0; + lidUnit->newDrainFlow = 0.0; + + //... set previous flux rates to 0 + for (i = 0; i < MAX_LAYERS; i++) + { + lidUnit->oldFluxRates[i] = 0.0; + } + + //... initialize infiltration state variables + if ( lidUnit->soilInfil.Ks > 0.0 ) + grnampt_initState(&(lidUnit->soilInfil)); + + //... add contribution to pervious LID area + if ( isLidPervious(lidUnit->lidIndex) ) + lidGroup->pervArea += (lidUnit->area * lidUnit->number); + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_setOldGroupState(int j) +// +// Purpose: saves the current drain flow rate for the LIDs in a subcatchment. +// Input: j = subcatchment index +// Output: none +// +{ + TLidList* lidList; + if ( LidGroups[j] != NULL ) + { + LidGroups[j]->oldDrainFlow = LidGroups[j]->newDrainFlow; + LidGroups[j]->newDrainFlow = 0.0; + lidList = LidGroups[j]->lidList; + while (lidList) + { + lidList->lidUnit->oldDrainFlow = lidList->lidUnit->newDrainFlow; + lidList->lidUnit->newDrainFlow = 0.0; + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +int isLidPervious(int k) +// +// Purpose: determines if a LID process allows infiltration or not. +// Input: k = LID process index +// Output: returns 1 if process is pervious or 0 if not +// +{ + return ( LidProcs[k].storage.thickness == 0.0 || + LidProcs[k].storage.kSat > 0.0 ); +} + +//============================================================================= + +double getSurfaceDepth(int j) +// +// Purpose: computes the depth (volume per unit area) of ponded water on the +// surface of all LIDs within a subcatchment. +// Input: j = subcatchment index +// Output: returns volumetric depth of ponded water (ft) +// +{ + int k; + double depth = 0.0; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + lidGroup = LidGroups[j]; + if ( lidGroup == NULL ) return 0.0; + if ( Subcatch[j].lidArea == 0.0 ) return 0.0; + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + depth += lidUnit->surfaceDepth * LidProcs[k].surface.voidFrac * + lidUnit->area * lidUnit->number; + lidList = lidList->nextLidUnit; + } + return depth / Subcatch[j].lidArea; +} + +//============================================================================= + +double lid_getPervArea(int j) +// +// Purpose: retrieves amount of pervious LID area in a subcatchment. +// Input: j = subcatchment index +// Output: returns amount of pervious LID area (ft2) +// +{ + if ( LidGroups[j] ) return LidGroups[j]->pervArea; + else return 0.0; +} + +//============================================================================= + +double lid_getFlowToPerv(int j) +// +// Purpose: retrieves flow returned from LID treatment to pervious area of +// a subcatchment. +// Input: j = subcatchment index +// Output: returns flow returned to pervious area (cfs) +// +{ + if ( LidGroups[j] != NULL ) return LidGroups[j]->flowToPerv; + return 0.0; +} + +//============================================================================= + +double lid_getStoredVolume(int j) +// +// Purpose: computes stored volume of water for all LIDs +// grouped within a subcatchment. +// Input: j = subcatchment index +// Output: returns stored volume of water (ft3) +// +{ + double total = 0.0; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + lidGroup = LidGroups[j]; + if ( lidGroup == NULL || Subcatch[j].lidArea == 0.0 ) return 0.0; + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + total += lidUnit->waterBalance.finalVol * lidUnit->area * lidUnit->number; + lidList = lidList->nextLidUnit; + } + return total; +} + +//============================================================================= + +double lid_getDrainFlow(int j, int timePeriod) +// +// Purpose: returns flow from all of a subcatchment's LID drains for +// a designated time period +// Input: j = subcatchment index +// timePeriod = either PREVIOUS or CURRENT +// Output: total drain flow (cfs) from the subcatchment. +{ + if ( LidGroups[j] != NULL ) + { + if ( timePeriod == PREVIOUS ) return LidGroups[j]->oldDrainFlow; + else return LidGroups[j]->newDrainFlow; + } + return 0.0; +} + +//============================================================================= + +void lid_addDrainLoads(int j, double c[], double tStep) +// +// Purpose: adds pollutant loads routed from drains to system +// mass balance totals. +// Input: j = subcatchment index +// c = array of pollutant washoff concentrations (mass/L) +// tStep = time step (sec) +// Output: none. +// +{ + int isRunoffLoad; // true if drain becomes external runoff load + int p; // pollutant index + double r; // pollutant fractional removal + double w; // pollutant mass load (lb or kg) + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... check if LID group exists + lidGroup = LidGroups[j]; + if ( lidGroup != NULL ) + { + //... examine each LID unit in the group + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + + //... see if unit's drain flow becomes external runoff + isRunoffLoad = (lidUnit->drainNode >= 0 || + lidUnit->drainSubcatch == j); + + //... for each pollutant not routed back on to subcatchment surface + if (!lidUnit->toPerv) for (p = 0; p < Nobjects[POLLUT]; p++) + { + //... get mass load flowing through the drain + w = lidUnit->newDrainFlow * c[p] * tStep * LperFT3 * Pollut[p].mcf; + + //... get fractional removal for this load + r = LidProcs[lidUnit->lidIndex].drainRmvl[p]; + + //... update system mass balance totals + massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, r*w); + if (isRunoffLoad) + massbal_updateLoadingTotals(RUNOFF_LOAD, p, w*(1.0 - r)); + } + + // process next LID unit in the group + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_addDrainRunon(int j) +// +// Purpose: adds drain flows from LIDs in a given subcatchment to the +// subcatchments that were designated to receive them +// Input: j = index of subcatchment contributing underdrain flows +// Output: none. +// +{ + int i; // index of an LID unit's LID process + int k; // index of subcatchment receiving LID drain flow + int p; // pollutant index + double q; // drain flow rate (cfs) + double w; // mass of polllutant from drain flow + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... check if LID group exists + lidGroup = LidGroups[j]; + if ( lidGroup != NULL ) + { + //... examine each LID in the group + lidList = lidGroup->lidList; + while ( lidList ) + { + //... see if LID's drain discharges to another subcatchment + lidUnit = lidList->lidUnit; + i = lidUnit->lidIndex; + k = lidUnit->drainSubcatch; + if ( k >= 0 && k != j ) + { + //... distribute drain flow across subcatchment's areas + q = lidUnit->oldDrainFlow; + subcatch_addRunonFlow(k, q); + + //... add pollutant loads from drain to subcatchment + // (newQual[] contains loading rate (mass/sec) at this + // point which is converted later on to a concentration) + for (p = 0; p < Nobjects[POLLUT]; p++) + { + w = q * Subcatch[j].oldQual[p] * LperFT3; + w = w * (1.0 - LidProcs[i].drainRmvl[p]); + Subcatch[k].newQual[p] += w; + } + } + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_addDrainInflow(int j, double f) +// +// Purpose: adds LID drain flow to conveyance system nodes +// Input: j = subcatchment index +// f = time interval weighting factor +// Output: none. +// +// Note: this function updates the total lateral flow (Node[].newLatFlow) +// and pollutant mass (Node[].newQual[]) inflow seen by nodes that +// receive drain flow from the LID units in subcatchment j. +{ + int i, // LID process index + k, // node index + p; // pollutant index + double q, // drain flow (cfs) + w, w1, w2; // pollutant mass loads (mass/sec) + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... check if LID group exists + lidGroup = LidGroups[j]; + if ( lidGroup != NULL ) + { + //... examine each LID in the group + lidList = lidGroup->lidList; + while ( lidList ) + { + //... see if LID's drain discharges to conveyance system node + lidUnit = lidList->lidUnit; + i = lidUnit->lidIndex; + k = lidUnit->drainNode; + if ( k >= 0 ) + { + //... add drain flow to node's wet weather inflow + q = (1.0 - f) * lidUnit->oldDrainFlow + f * lidUnit->newDrainFlow; + Node[k].newLatFlow += q; + massbal_addInflowFlow(WET_WEATHER_INFLOW, q); + + //... add pollutant load, based on parent subcatchment quality + for (p = 0; p < Nobjects[POLLUT]; p++) + { + //... get previous & current drain loads + w1 = lidUnit->oldDrainFlow * Subcatch[j].oldQual[p]; + w2 = lidUnit->newDrainFlow * Subcatch[j].newQual[p]; + + //... add interpolated load to node's wet weather loading + w = (1.0 - f) * w1 + f * w2; + w = w * (1.0 - LidProcs[i].drainRmvl[p]); + Node[k].newQual[p] += w; + massbal_addInflowQual(WET_WEATHER_INFLOW, p, w); + } + } + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_getRunoff(int j, double tStep) +// +// Purpose: computes runoff and drain flows from the LIDs in a subcatchment. +// Input: j = subcatchment index +// tStep = time step (sec) +// Output: updates following global quantities after LID treatment applied: +// Vevap, Vpevap, VlidInfil, VlidIn, VlidOut, VlidDrain. +// +{ + TLidGroup theLidGroup; // group of LIDs placed in the subcatchment + TLidList* lidList; // list of LID units in the group + TLidUnit* lidUnit; // a member of the list of LID units + double lidArea; // area of an LID unit + double qImperv = 0.0; // runoff from impervious areas (cfs) + double qPerv = 0.0; // runoff from pervious areas (cfs) + double lidInflow = 0.0; // inflow to an LID unit (ft/s) + double qRunoff = 0.0; // surface runoff from all LID units (cfs) + double qDrain = 0.0; // drain flow from all LID units (cfs) + double qReturn = 0.0; // LID outflow returned to pervious area (cfs) + + //... return if there are no LID's + theLidGroup = LidGroups[j]; + if ( !theLidGroup ) return; + lidList = theLidGroup->lidList; + if ( !lidList ) return; + + //... determine if evaporation can occur + EvapRate = Evap.rate; + if ( Evap.dryOnly && Subcatch[j].rainfall > 0.0 ) EvapRate = 0.0; + + //... find subcatchment's infiltration rate into native soil + findNativeInfil(j, tStep); + + //... get impervious and pervious area runoff from non-LID + // portion of subcatchment (cfs) + if ( Subcatch[j].area > Subcatch[j].lidArea ) + { + qImperv = getImpervAreaRunoff(j); + qPerv = getPervAreaRunoff(j); + } + + //... evaluate performance of each LID unit placed in the subcatchment + while ( lidList ) + { + //... find area of the LID unit + lidUnit = lidList->lidUnit; + lidArea = lidUnit->area * lidUnit->number; + + //... if LID unit has area, evaluate its performance + if ( lidArea > 0.0 ) + { + //... find runoff from non-LID area treated by LID area (ft/sec) + lidInflow = (qImperv * lidUnit->fromImperv + + qPerv * lidUnit->fromPerv) / lidArea; + + //... update total runoff volume treated + VlidIn += lidInflow * lidArea * tStep; + + //... add rainfall onto LID inflow (ft/s) + lidInflow = lidInflow + getRainInflow(j, lidUnit); + + // ... add upstream runon only if LID occupies full subcatchment + if ( Subcatch[j].area == Subcatch[j].lidArea ) + { + lidInflow += Subcatch[j].runon; + } + + //... evaluate the LID unit's performance, updating the LID group's + // total surface runoff, drain flow, and flow returned to + // pervious area + evalLidUnit(j, lidUnit, lidArea, lidInflow, tStep, + &qRunoff, &qDrain, &qReturn); + } + lidList = lidList->nextLidUnit; + } + + //... save the LID group's total drain & return flows + theLidGroup->newDrainFlow = qDrain; + theLidGroup->flowToPerv = qReturn; + + //... save the LID group's total surface, drain and return flow volumes + VlidOut = qRunoff * tStep; + VlidDrain = qDrain * tStep; + VlidReturn = qReturn * tStep; +} + +//============================================================================= + +void findNativeInfil(int j, double tStep) +// +// Purpose: determines a subcatchment's current infiltration rate into +// its native soil. +// Input: j = subcatchment index +// tStep = time step (sec) +// Output: sets values for module-level variables NativeInfil +// +{ + double nonLidArea; + + //... subcatchment has non-LID pervious area + nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; + if ( nonLidArea > 0.0 && Subcatch[j].fracImperv < 1.0 ) + { + NativeInfil = Vinfil / nonLidArea / tStep; + } + + //... otherwise find infil. rate for the subcatchment's rainfall + runon + else + { + NativeInfil = infil_getInfil(j, tStep, + Subcatch[j].rainfall, + Subcatch[j].runon, + getSurfaceDepth(j)); + } + + //... see if there is any groundwater-imposed limit on infil. + if ( !IgnoreGwater && Subcatch[j].groundwater ) + { + MaxNativeInfil = Subcatch[j].groundwater->maxInfilVol / tStep; + } + else MaxNativeInfil = BIG; +} + +//============================================================================= + +double getRainInflow(int j, TLidUnit* lidUnit) +// +// Purpose: gets rainfall inflow to an LID unit. +// Input: j = subcatchment index +// lidUnit = ptr. to an LID unit +// Output: returns rainfall rate over the LID unit (ft/sec) +// +{ + TLidProc* lidProc = &LidProcs[lidUnit->lidIndex]; + + if (lidProc->lidType == RAIN_BARREL && + lidProc->storage.covered == TRUE) return 0.0; + return Subcatch[j].rainfall; +} + +//============================================================================= + +double getImpervAreaRunoff(int j) +// +// Purpose: computes runoff from impervious area of a subcatchment that +// is available for LID treatment. +// Input: j = subcatchment index +// Output: returns runoff flow rate (cfs) +// +{ + int i; + double q = 0.0, // runoff rate (ft/sec) + nonLidArea; // non-LID area (ft2) + + // --- runoff from impervious area w/ & w/o depression storage + for (i = IMPERV0; i <= IMPERV1; i++) + { + q += Subcatch[j].subArea[i].runoff * Subcatch[j].subArea[i].fArea; + } + + // --- adjust for any fraction of runoff sent to pervious area + if ( Subcatch[j].subArea[IMPERV0].routeTo == TO_PERV && + Subcatch[j].fracImperv < 1.0 ) + { + q *= Subcatch[j].subArea[IMPERV0].fOutlet; + } + nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; + return q * nonLidArea; +} + +//============================================================================= + +double getPervAreaRunoff(int j) +// +// Purpose: computes runoff from pervious area of a subcatchment that +// is available for LID treatment. +// Input: j = subcatchment index +// Output: returns runoff flow rate (cfs) +// +{ + double q = 0.0, // runoff rate (ft/sec) + nonLidArea; // non-LID area (ft2) + + // --- runoff from pervious area + q = Subcatch[j].subArea[PERV].runoff * Subcatch[j].subArea[PERV].fArea; + + // --- adjust for any fraction of runoff sent to impervious area + if (Subcatch[j].subArea[PERV].routeTo == TO_IMPERV && + Subcatch[j].fracImperv > 0.0) + { + q *= Subcatch[j].subArea[PERV].fOutlet; + } + nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; + return q * nonLidArea; +} + +//============================================================================= + +void evalLidUnit(int j, TLidUnit* lidUnit, double lidArea, double lidInflow, + double tStep, double *qRunoff, double *qDrain, double *qReturn) +// +// Purpose: evaluates performance of a specific LID unit over current time step. +// Input: j = subcatchment index +// lidUnit = ptr. to LID unit being evaluated +// lidArea = area of LID unit +// lidInflow = inflow to LID unit (ft/s) +// tStep = time step (sec) +// Output: qRunoff = sum of surface runoff from all LIDs (cfs) +// qDrain = sum of drain flows from all LIDs (cfs) +// qReturn = sum of LID flows returned to pervious area (cfs) +// +{ + TLidProc* lidProc; // LID process associated with lidUnit + double lidRunoff, // surface runoff from LID unit (cfs) + lidEvap, // evaporation rate from LID unit (ft/s) + lidInfil, // infiltration rate from LID unit (ft/s) + lidDrain; // drain flow rate from LID unit (ft/s & cfs) + + //... identify the LID process of the LID unit being analyzed + lidProc = &LidProcs[lidUnit->lidIndex]; + + //... initialize evap and infil losses + lidEvap = 0.0; + lidInfil = 0.0; + + //... find surface runoff from the LID unit (in cfs) + lidRunoff = lidproc_getOutflow(lidUnit, lidProc, lidInflow, EvapRate, + NativeInfil, MaxNativeInfil, tStep, + &lidEvap, &lidInfil, &lidDrain) * lidArea; + + //... convert drain flow to CFS + lidDrain *= lidArea; + + //... revise flows if LID outflow returned to pervious area + if ( lidUnit->toPerv && Subcatch[j].area > Subcatch[j].lidArea ) + { + //... surface runoff is always returned + *qReturn += lidRunoff; + lidRunoff = 0.0; + + //... drain flow returned if it has same outlet as subcatchment + if ( lidUnit->drainNode == Subcatch[j].outNode && + lidUnit->drainSubcatch == Subcatch[j].outSubcatch ) + { + *qReturn += lidDrain; + lidDrain = 0.0; + } + } + + //... update system flow balance if drain flow goes to a + // conveyance system node + if ( lidUnit->drainNode >= 0 ) + { + massbal_updateRunoffTotals(RUNOFF_DRAINS, lidDrain * tStep); + } + + //... save new drain outflow + lidUnit->newDrainFlow = lidDrain; + + //... update moisture losses (ft3) + Vevap += lidEvap * tStep * lidArea; + VlidInfil += lidInfil * tStep * lidArea; + if ( isLidPervious(lidUnit->lidIndex) ) + { + Vpevap += lidEvap * tStep * lidArea; + } + + //... update time since last rainfall (for Rain Barrel emptying) + if ( Subcatch[j].rainfall > MIN_RUNOFF ) lidUnit->dryTime = 0.0; + else lidUnit->dryTime += tStep; + + //... update LID water balance and save results + lidproc_saveResults(lidUnit, UCF(RAINFALL), UCF(RAINDEPTH)); + + //... update LID group totals + *qRunoff += lidRunoff; + *qDrain += lidDrain; +} + +//============================================================================= + +void lid_writeWaterBalance() +// +// Purpose: writes a LID performance summary table to the project's report file. +// Input: none +// Output: none +// +{ + int j; + int k = 0; + double ucf = UCF(RAINDEPTH); + double inflow; + double outflow; + double err; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... check that project has LIDs + for ( j = 0; j < GroupCount; j++ ) + { + if ( LidGroups[j] ) k++; + } + if ( k == 0 ) return; + + //... write table header + fprintf(Frpt.file, + "\n" + "\n ***********************" + "\n LID Performance Summary" + "\n ***********************\n"); + + fprintf(Frpt.file, +"\n --------------------------------------------------------------------------------------------------------------------" +"\n Total Evap Infil Surface Drain Initial Final Continuity" +"\n Inflow Loss Loss Outflow Outflow Storage Storage Error"); + if ( UnitSystem == US ) fprintf(Frpt.file, +"\n Subcatchment LID Control in in in in in in in %%"); + else fprintf(Frpt.file, +"\n Subcatchment LID Control mm mm mm mm mm mm mm %%"); + fprintf(Frpt.file, +"\n --------------------------------------------------------------------------------------------------------------------"); + + //... examine each LID unit in each subcatchment + for ( j = 0; j < GroupCount; j++ ) + { + lidGroup = LidGroups[j]; + if ( !lidGroup || Subcatch[j].lidArea == 0.0 ) continue; + lidList = lidGroup->lidList; + while ( lidList ) + { + //... write water balance components to report file + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + fprintf(Frpt.file, "\n %-16s %-16s", Subcatch[j].ID, + LidProcs[k].ID); + fprintf(Frpt.file, "%10.2f%10.2f%10.2f%10.2f%10.2f%10.2f%10.2f", + lidUnit->waterBalance.inflow*ucf, + lidUnit->waterBalance.evap*ucf, + lidUnit->waterBalance.infil*ucf, + lidUnit->waterBalance.surfFlow*ucf, + lidUnit->waterBalance.drainFlow*ucf, + lidUnit->waterBalance.initVol*ucf, + lidUnit->waterBalance.finalVol*ucf); + + //... compute flow balance error + inflow = lidUnit->waterBalance.initVol + + lidUnit->waterBalance.inflow; + outflow = lidUnit->waterBalance.finalVol + + lidUnit->waterBalance.evap + + lidUnit->waterBalance.infil + + lidUnit->waterBalance.surfFlow + + lidUnit->waterBalance.drainFlow; + if ( inflow > 0.0 ) err = (inflow - outflow) / inflow; + else err = 1.0; + fprintf(Frpt.file, " %10.2f", err*100.0); + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void initLidRptFile(char* title, char* lidID, char* subcatchID, TLidUnit* lidUnit) +// +// Purpose: initializes the report file used for a specific LID unit +// Input: title = project's title +// lidID = LID process name +// subcatchID = subcatchment ID name +// lidUnit = ptr. to LID unit +// Output: none +// +{ + static int colCount = 14; + static char* head1[] = { + "\n \t", " Elapsed\t", + " Total\t", " Total\t", " Surface\t", " Pavement\t", " Soil\t", + " Storage\t", " Surface\t", " Drain\t", " Surface\t", " Pavement\t", + " Soil\t", " Storage"}; + static char* head2[] = { + "\n \t", " Time\t", + " Inflow\t", " Evap\t", " Infil\t", " Perc\t", " Perc\t", + " Exfil\t", " Runoff\t", " OutFlow\t", " Level\t", " Level\t", + " Moisture\t", " Level"}; + static char* units1[] = { + "\nDate Time \t", " Hours\t", + " in/hr\t", " in/hr\t", " in/hr\t", " in/hr\t", " in/hr\t", + " in/hr\t", " in/hr\t", " in/hr\t", " inches\t", " inches\t", + " Content\t", " inches"}; + static char* units2[] = { + "\nDate Time \t", " Hours\t", + " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm/hr\t", + " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm\t", " mm\t", + " Content\t", " mm"}; + static char line9[] = " ---------"; + int i; + FILE* f = lidUnit->rptFile->file; + + //... check that file was opened + if ( f == NULL ) return; + + //... write title lines + fprintf(f, "SWMM5 LID Report File\n"); + fprintf(f, "\nProject: %s", title); + fprintf(f, "\nLID Unit: %s in Subcatchment %s\n", lidID, subcatchID); + + //... write column headings + for ( i = 0; i < colCount; i++) fprintf(f, "%s", head1[i]); + for ( i = 0; i < colCount; i++) fprintf(f, "%s", head2[i]); + if ( UnitSystem == US ) + { + for ( i = 0; i < colCount; i++) fprintf(f, "%s", units1[i]); + } + else for ( i = 0; i < colCount; i++) fprintf(f, "%s", units2[i]); + fprintf(f, "\n----------- --------"); + for ( i = 1; i < colCount; i++) fprintf(f, "\t%s", line9); + + //... initialize LID dryness state + lidUnit->rptFile->wasDry = 1; + sstrncpy(lidUnit->rptFile->results, "", 0); +} diff --git a/src/lid.h b/src/lid.h new file mode 100644 index 000000000..b250fcf3a --- /dev/null +++ b/src/lid.h @@ -0,0 +1,236 @@ +//----------------------------------------------------------------------------- +// lid.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Public interface for LID functions. +// +// Update History +// ============== +// Build 5.1.008: +// - Support added for Roof Disconnection LID. +// - Support added for separate routing of LID drain flows. +// - Detailed LID reporting modified. +// Build 5.1.011: +// - Water depth replaces moisture content for LID's pavement layer. +// - Arguments for lidproc_saveResults() modified. +// Build 5.1.012: +// - Redefined meaning of wasDry in TLidRptFile structure. +// Build 5.1.013: +// - New member fromPerv added to TLidUnit structure to allow LID +// units to also treat pervious area runoff. +// - New members hOpen and hClose addded to TDrainLayer to open/close +// drain when certain heads are reached. +// - New member qCurve added to TDrainLayer to allow underdrain flow to +// be adjusted by a curve of multiplier v. head. +// - New array drainRmvl added to TLidProc to allow for underdrain +// pollutant removal values. +// - New members added to TPavementLayer and TLidUnit to support +// unclogging permeable pavement at fixed intervals. +// Build 5.2.0: +// - Covered property added to RAIN_BARREL parameters +//----------------------------------------------------------------------------- + +#ifndef LID_H +#define LID_H + +#include +#include +#include +#include "infil.h" + +//----------------------------------------------------------------------------- +// Enumerations +//----------------------------------------------------------------------------- +enum LidTypes { + BIO_CELL, // bio-retention cell + RAIN_GARDEN, // rain garden + GREEN_ROOF, // green roof + INFIL_TRENCH, // infiltration trench + POROUS_PAVEMENT, // porous pavement + RAIN_BARREL, // rain barrel + VEG_SWALE, // vegetative swale + ROOF_DISCON}; // roof disconnection + +enum TimePeriod { + PREVIOUS, // previous time period + CURRENT}; // current time period + +//----------------------------------------------------------------------------- +// Data Structures +//----------------------------------------------------------------------------- +#define MAX_LAYERS 4 + +// LID Surface Layer +typedef struct +{ + double thickness; // depression storage or berm ht. (ft) + double voidFrac; // available fraction of storage volume + double roughness; // surface Mannings n + double surfSlope; // land surface slope (fraction) + double sideSlope; // swale side slope (run/rise) + double alpha; // slope/roughness term in Manning eqn. + char canOverflow; // 1 if immediate outflow of excess water +} TSurfaceLayer; + +// LID Pavement Layer +typedef struct +{ + double thickness; // layer thickness (ft) + double voidFrac; // void volume / total volume + double impervFrac; // impervious area fraction + double kSat; // permeability (ft/sec) + double clogFactor; // clogging factor + double regenDays; // clogging regeneration interval (days) + double regenDegree; // degree of clogging regeneration +} TPavementLayer; + +// LID Soil Layer +typedef struct +{ + double thickness; // layer thickness (ft) + double porosity; // void volume / total volume + double fieldCap; // field capacity + double wiltPoint; // wilting point + double suction; // suction head at wetting front (ft) + double kSat; // saturated hydraulic conductivity (ft/sec) + double kSlope; // slope of log(K) v. moisture content curve +} TSoilLayer; + +// LID Storage Layer +typedef struct +{ + double thickness; // layer thickness (ft) + double voidFrac; // void volume / total volume + double kSat; // saturated hydraulic conductivity (ft/sec) + double clogFactor; // clogging factor + int covered; // TRUE if rain barrel is covered +} TStorageLayer; + +// Underdrain System (part of Storage Layer) +typedef struct +{ + double coeff; // underdrain flow coeff. (in/hr or mm/hr) + double expon; // underdrain head exponent (for in or mm) + double offset; // offset height of underdrain (ft) + double delay; // rain barrel drain delay time (sec) + double hOpen; // head when drain opens (ft) + double hClose; // head when drain closes (ft) + int qCurve; // curve controlling flow rate (optional) +} TDrainLayer; + +// Drainage Mat Layer (for green roofs) +typedef struct +{ + double thickness; // layer thickness (ft) + double voidFrac; // void volume / total volume + double roughness; // Mannings n for green roof drainage mats + double alpha; // slope/roughness term in Manning equation +} TDrainMatLayer; + +// LID Process - generic LID design per unit of area +typedef struct +{ + char* ID; // identifying name + int lidType; // type of LID + TSurfaceLayer surface; // surface layer parameters + TPavementLayer pavement; // pavement layer parameters + TSoilLayer soil; // soil layer parameters + TStorageLayer storage; // storage layer parameters + TDrainLayer drain; // underdrain system parameters + TDrainMatLayer drainMat; // drainage mat layer + double* drainRmvl; // underdrain pollutant removals +} TLidProc; + +// Water Balance Statistics +typedef struct +{ + double inflow; // total inflow (ft) + double evap; // total evaporation (ft) + double infil; // total infiltration (ft) + double surfFlow; // total surface runoff (ft) + double drainFlow; // total underdrain flow (ft) + double initVol; // initial stored volume (ft) + double finalVol; // final stored volume (ft) +} TWaterBalance; + +// LID Report File +typedef struct +{ + FILE* file; // file pointer + int wasDry; // number of successive dry periods + char results[256]; // results for current time period +} TLidRptFile; + +// LID Unit - specific LID process applied over a given area +typedef struct +{ + int lidIndex; // index of LID process + int number; // number of replicate units + double area; // area of single replicate unit (ft2) + double fullWidth; // full top width of single unit (ft) + double botWidth; // bottom width of single unit (ft) + double initSat; // initial saturation of soil & storage layers + double fromImperv; // fraction of impervious area runoff treated + double fromPerv; // fraction of pervious area runoff treated + int toPerv; // 1 if outflow sent to pervious area; 0 if not + int drainSubcatch; // subcatchment receiving drain flow + int drainNode; // node receiving drain flow + TLidRptFile* rptFile; // pointer to detailed report file + + TGrnAmpt soilInfil; // infil. object for biocell soil layer + double surfaceDepth; // depth of ponded water on surface layer (ft) + double paveDepth; // depth of water in porous pavement layer + double soilMoisture; // moisture content of biocell soil layer + double storageDepth; // depth of water in storage layer (ft) + + // net inflow - outflow from previous time step for each LID layer (ft/s) + double oldFluxRates[MAX_LAYERS]; + + double dryTime; // time since last rainfall (sec) + double oldDrainFlow; // previous drain flow (cfs) + double newDrainFlow; // current drain flow (cfs) + double volTreated; // total volume treated (ft) + double nextRegenDay; // next day when unit regenerated + TWaterBalance waterBalance; // water balance quantites +} TLidUnit; + +//----------------------------------------------------------------------------- +// LID Methods +//----------------------------------------------------------------------------- +void lid_create(int lidCount, int subcatchCount); +void lid_delete(void); + +int lid_readProcParams(char* tok[], int ntoks); +int lid_readGroupParams(char* tok[], int ntoks); + +void lid_validate(void); +void lid_initState(void); +void lid_setOldGroupState(int subcatch); + +double lid_getPervArea(int subcatch); +double lid_getFlowToPerv(int subcatch); +double lid_getDrainFlow(int subcatch, int timePeriod); +double lid_getStoredVolume(int subcatch); +void lid_addDrainLoads(int subcatch, double c[], double tStep); +void lid_addDrainRunon(int subcatch); +void lid_addDrainInflow(int subcatch, double f); +void lid_getRunoff(int subcatch, double tStep); +void lid_writeSummary(void); +void lid_writeWaterBalance(void); + +//----------------------------------------------------------------------------- + +void lidproc_initWaterBalance(TLidUnit *lidUnit, double initVol); + +double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, + double inflow, double evap, double infil, double maxInfil, + double tStep, double* lidEvap, double* lidInfil, double* lidDrain); + +void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, + double ucfRainDepth); + +#endif diff --git a/src/lidproc.c b/src/lidproc.c new file mode 100644 index 000000000..4dbd5b977 --- /dev/null +++ b/src/lidproc.c @@ -0,0 +1,1593 @@ +//----------------------------------------------------------------------------- +// lidproc.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// This module computes the hydrologic performance of an LID (Low Impact +// Development) unit at a given point in time. +// +// Update History +// ============== +// Build 5.1.007: +// - Euler integration now applied to all LID types except Vegetative +// Swale which continues to use successive approximation. +// - LID layer flux routines were re-written to more accurately model +// flooded conditions. +// Build 5.1.008: +// - MAX_STATE_VARS replaced with MAX_LAYERS. +// - Optional soil layer added to Porous Pavement LID. +// - Rooftop Disconnection added to types of LIDs. +// - Separate accounting of drain flows added. +// - Indicator for currently wet LIDs added. +// - Detailed reporting procedure fixed. +// - Possibile negative head on Bioretention Cell drain avoided. +// - Bug in computing flow through Green Roof drainage mat fixed. +// Build 5.1.009: +// - Fixed typo in net flux rate for vegetative swale LID. +// Build 5.1.010: +// - New modified version of Green-Ampt used for surface layer infiltration. +// Build 5.1.011: +// - Re-named STOR_INFIL to STOR_EXFIL and StorageInfil to StorageExfil to +// better reflect their meaning. +// - Evaporation rates from sub-surface layers reduced by fraction of +// surface that is pervious (applies to block paver systems) +// - Flux rate routines for LIDs with underdrains modified to produce more +// physically meaningful results. +// - Reporting of detailed results re-written. +// Build 5.1.012: +// - Modified upper limit for soil layer percolation. +// - Modified upper limit on surface infiltration into rain gardens. +// - Modified upper limit on drain flow for LIDs with storage layers. +// - Used re-defined wasDry variable for LID reports to fix duplicate lines. +// Build 5.1.013: +// - Support added for open/closed head levels and multiplier v. head curve +// to control underdrain flow. +// - Support added for regenerating pavement permeability at fixed intervals. +// Build 5.1.014: +// - Fixed failure to initialize all LID layer moisture volumes to 0 before +// computing LID unit performance in lidproc_getOutflow. +// Build 5.2.0: +// - Fixed failure to account for effect of Impervious Surface Fraction on +// pavement permeability for Permeable Pavement LID +// - Fixed units conversion for pavement depth in detailed report file. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "lid.h" +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +#define STOPTOL 0.00328 // integration error tolerance in ft (= 1 mm) +#define MINFLOW 2.3e-8 // flow cutoff for dry conditions (= 0.001 in/hr) + +//----------------------------------------------------------------------------- +// Enumerations +//----------------------------------------------------------------------------- +enum LidLayerTypes { + SURF, // surface layer + SOIL, // soil layer + STOR, // storage layer + PAVE, // pavement layer + DRAIN}; // underdrain system + +enum LidRptVars { + SURF_INFLOW, // inflow to surface layer + TOTAL_EVAP, // evaporation rate from all layers + SURF_INFIL, // infiltration into surface layer + PAVE_PERC, // percolation through pavement layer + SOIL_PERC, // percolation through soil layer + STOR_EXFIL, // exfiltration out of storage layer + SURF_OUTFLOW, // outflow from surface layer + STOR_DRAIN, // outflow from storage layer + SURF_DEPTH, // ponded depth on surface layer + PAVE_DEPTH, // water level in pavement layer + SOIL_MOIST, // moisture content of soil layer + STOR_DEPTH, // water level in storage layer + MAX_RPT_VARS}; + +//----------------------------------------------------------------------------- +// Imported variables +//----------------------------------------------------------------------------- +extern char HasWetLids; // TRUE if any LIDs are wet (declared in runoff.c) + +//----------------------------------------------------------------------------- +// Local Variables +//----------------------------------------------------------------------------- +static TLidUnit* theLidUnit; // ptr. to a subcatchment's LID unit +static TLidProc* theLidProc; // ptr. to a LID process + +static double Tstep; // current time step (sec) +static double EvapRate; // evaporation rate (ft/s) +static double MaxNativeInfil; // native soil infil. rate limit (ft/s) + +static double SurfaceInflow; // precip. + runon to LID unit (ft/s) +static double SurfaceInfil; // infil. rate from surface layer (ft/s) +static double SurfaceEvap; // evap. rate from surface layer (ft/s) +static double SurfaceOutflow; // outflow from surface layer (ft/s) +static double SurfaceVolume; // volume in surface storage (ft) + +static double PaveEvap; // evap. from pavement layer (ft/s) +static double PavePerc; // percolation from pavement layer (ft/s) +static double PaveVolume; // volume stored in pavement layer (ft) + +static double SoilEvap; // evap. from soil layer (ft/s) +static double SoilPerc; // percolation from soil layer (ft/s) +static double SoilVolume; // volume in soil/pavement storage (ft) + +static double StorageInflow; // inflow rate to storage layer (ft/s) +static double StorageExfil; // exfil. rate from storage layer (ft/s) +static double StorageEvap; // evap.rate from storage layer (ft/s) +static double StorageDrain; // underdrain flow rate layer (ft/s) +static double StorageVolume; // volume in storage layer (ft) + +static double Xold[MAX_LAYERS]; // previous moisture level in LID layers + +//----------------------------------------------------------------------------- +// External Functions (declared in lid.h) +//----------------------------------------------------------------------------- +// lidproc_initWaterBalance (called by lid_initState) +// lidproc_getOutflow (called by evalLidUnit in lid.c) +// lidproc_saveResults (called by evalLidUnit in lid.c) + +//----------------------------------------------------------------------------- +// Local Functions +//----------------------------------------------------------------------------- +static void barrelFluxRates(double x[], double f[]); +static void biocellFluxRates(double x[], double f[]); +static void greenRoofFluxRates(double x[], double f[]); +static void pavementFluxRates(double x[], double f[]); +static void trenchFluxRates(double x[], double f[]); +static void swaleFluxRates(double x[], double f[]); +static void roofFluxRates(double x[], double f[]); + +static double getSurfaceOutflowRate(double depth); +static double getSurfaceOverflowRate(double* surfaceDepth); +static double getPavementPermRate(void); +static double getSoilPercRate(double theta); +static double getStorageExfilRate(void); +static double getStorageDrainRate(double storageDepth, double soilTheta, + double paveDepth, double surfaceDepth); +static double getDrainMatOutflow(double depth); +static void getEvapRates(double surfaceVol, double paveVol, + double soilVol, double storageVol, double pervFrac); + +static void updateWaterBalance(TLidUnit *lidUnit, double inflow, + double evap, double infil, double surfFlow, + double drainFlow, double storage); + +static int modpuls_solve(int n, double* x, double* xOld, double* xPrev, + double* xMin, double* xMax, double* xTol, + double* qOld, double* q, double dt, double omega, + void (*derivs)(double*, double*)); + +//============================================================================= + +void lidproc_initWaterBalance(TLidUnit *lidUnit, double initVol) +// +// Purpose: initializes the water balance components of a LID unit. +// Input: lidUnit = a particular LID unit +// initVol = initial water volume stored in the unit (ft) +// Output: none +// +{ + lidUnit->waterBalance.inflow = 0.0; + lidUnit->waterBalance.evap = 0.0; + lidUnit->waterBalance.infil = 0.0; + lidUnit->waterBalance.surfFlow = 0.0; + lidUnit->waterBalance.drainFlow = 0.0; + lidUnit->waterBalance.initVol = initVol; + lidUnit->waterBalance.finalVol = initVol; +} + +//============================================================================= + +double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, double inflow, + double evap, double infil, double maxInfil, + double tStep, double* lidEvap, + double* lidInfil, double* lidDrain) +// +// Purpose: computes runoff outflow from a single LID unit. +// Input: lidUnit = ptr. to specific LID unit being analyzed +// lidProc = ptr. to generic LID process of the LID unit +// inflow = runoff rate captured by LID unit (ft/s) +// evap = potential evaporation rate (ft/s) +// infil = infiltration rate to native soil (ft/s) +// maxInfil = max. infiltration rate to native soil (ft/s) +// tStep = time step (sec) +// Output: lidEvap = evaporation rate for LID unit (ft/s) +// lidInfil = infiltration rate for LID unit (ft/s) +// lidDrain = drain flow for LID unit (ft/s) +// returns surface runoff rate from the LID unit (ft/s) +// +{ + int i; + double x[MAX_LAYERS]; // layer moisture levels + double xOld[MAX_LAYERS]; // work vector + double xPrev[MAX_LAYERS]; // work vector + double xMin[MAX_LAYERS]; // lower limit on moisture levels + double xMax[MAX_LAYERS]; // upper limit on moisture levels + double fOld[MAX_LAYERS]; // previously computed flux rates + double f[MAX_LAYERS]; // newly computed flux rates + + // convergence tolerance on moisture levels (ft, moisture fraction , ft) + double xTol[MAX_LAYERS] = {STOPTOL, STOPTOL, STOPTOL, STOPTOL}; + + double omega = 0.0; // integration time weighting + + //... define a pointer to function that computes flux rates through the LID + void (*fluxRates) (double *, double *) = NULL; + + //... save references to the LID process and LID unit + theLidProc = lidProc; + theLidUnit = lidUnit; + + //... save evap, max. infil. & time step to shared variables + EvapRate = evap; + MaxNativeInfil = maxInfil; + Tstep = tStep; + + //... store current moisture levels in vector x + x[SURF] = theLidUnit->surfaceDepth; + x[SOIL] = theLidUnit->soilMoisture; + x[STOR] = theLidUnit->storageDepth; + x[PAVE] = theLidUnit->paveDepth; + + //... initialize layer moisture volumes, flux rates and moisture limits + SurfaceVolume = 0.0; + PaveVolume = 0.0; + SoilVolume = 0.0; + StorageVolume = 0.0; + SurfaceInflow = inflow; + SurfaceInfil = 0.0; + SurfaceEvap = 0.0; + SurfaceOutflow = 0.0; + PaveEvap = 0.0; + PavePerc = 0.0; + SoilEvap = 0.0; + SoilPerc = 0.0; + StorageInflow = 0.0; + StorageExfil = 0.0; + StorageEvap = 0.0; + StorageDrain = 0.0; + for (i = 0; i < MAX_LAYERS; i++) + { + f[i] = 0.0; + fOld[i] = theLidUnit->oldFluxRates[i]; + xMin[i] = 0.0; + xMax[i] = BIG; + Xold[i] = x[i]; + } + + //... find Green-Ampt infiltration from surface layer + if ( theLidProc->lidType == POROUS_PAVEMENT ) SurfaceInfil = 0.0; + else if ( theLidUnit->soilInfil.Ks > 0.0 ) + { + SurfaceInfil = + grnampt_getInfil(&theLidUnit->soilInfil, Tstep, + SurfaceInflow, theLidUnit->surfaceDepth, + MOD_GREEN_AMPT); + } + else SurfaceInfil = infil; + + //... set moisture limits for soil & storage layers + if ( theLidProc->soil.thickness > 0.0 ) + { + xMin[SOIL] = theLidProc->soil.wiltPoint; + xMax[SOIL] = theLidProc->soil.porosity; + } + if ( theLidProc->pavement.thickness > 0.0 ) + { + xMax[PAVE] = theLidProc->pavement.thickness; + } + if ( theLidProc->storage.thickness > 0.0 ) + { + xMax[STOR] = theLidProc->storage.thickness; + } + if ( theLidProc->lidType == GREEN_ROOF ) + { + xMax[STOR] = theLidProc->drainMat.thickness; + } + + //... determine which flux rate function to use + switch (theLidProc->lidType) + { + case BIO_CELL: + case RAIN_GARDEN: fluxRates = &biocellFluxRates; break; + case GREEN_ROOF: fluxRates = &greenRoofFluxRates; break; + case INFIL_TRENCH: fluxRates = &trenchFluxRates; break; + case POROUS_PAVEMENT: fluxRates = &pavementFluxRates; break; + case RAIN_BARREL: fluxRates = &barrelFluxRates; break; + case ROOF_DISCON: fluxRates = &roofFluxRates; break; + case VEG_SWALE: fluxRates = &swaleFluxRates; + omega = 0.5; + break; + default: return 0.0; + } + + //... update moisture levels and flux rates over the time step + i = modpuls_solve(MAX_LAYERS, x, xOld, xPrev, xMin, xMax, xTol, + fOld, f, tStep, omega, fluxRates); + +/** For debugging only ******************************************** + if (i == 0) + { + fprintf(Frpt.file, + "\n WARNING 09: integration failed to converge at %s %s", + theDate, theTime); + fprintf(Frpt.file, + "\n for LID %s placed in subcatchment %s.", + theLidProc->ID, theSubcatch->ID); + } +*******************************************************************/ + + //... add any surface overflow to surface outflow + if ( theLidProc->surface.canOverflow || theLidUnit->fullWidth == 0.0 ) + { + SurfaceOutflow += getSurfaceOverflowRate(&x[SURF]); + } + + //... save updated results + theLidUnit->surfaceDepth = x[SURF]; + theLidUnit->paveDepth = x[PAVE]; + theLidUnit->soilMoisture = x[SOIL]; + theLidUnit->storageDepth = x[STOR]; + for (i = 0; i < MAX_LAYERS; i++) theLidUnit->oldFluxRates[i] = f[i]; + + //... assign values to LID unit evaporation, infiltration & drain flow + *lidEvap = SurfaceEvap + PaveEvap + SoilEvap + StorageEvap; + *lidInfil = StorageExfil; + *lidDrain = StorageDrain; + + //... return surface outflow (per unit area) from unit + return SurfaceOutflow; +} + +//============================================================================= + +void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, double ucfRainDepth) +// +// Purpose: updates the mass balance for an LID unit and saves +// current flux rates to the LID report file. +// Input: lidUnit = ptr. to LID unit +// ucfRainfall = units conversion factor for rainfall rate +// ucfDepth = units conversion factor for rainfall depth +// Output: none +// +{ + double ucf; // units conversion factor + double totalEvap; // total evaporation rate (ft/s) + double totalVolume; // total volume stored in LID (ft) + double rptVars[MAX_RPT_VARS]; // array of reporting variables + int isDry = FALSE; // true if current state of LID is dry + char timeStamp[TIME_STAMP_SIZE + 1]; // date/time stamp + double elapsedHrs; // elapsed hours + + //... find total evap. rate and stored volume + totalEvap = SurfaceEvap + PaveEvap + SoilEvap + StorageEvap; + totalVolume = SurfaceVolume + PaveVolume + SoilVolume + StorageVolume; + + //... update mass balance totals + updateWaterBalance(theLidUnit, SurfaceInflow, totalEvap, StorageExfil, + SurfaceOutflow, StorageDrain, totalVolume); + + //... check if dry-weather conditions hold + if ( SurfaceInflow < MINFLOW && + SurfaceOutflow < MINFLOW && + StorageDrain < MINFLOW && + StorageExfil < MINFLOW && + totalEvap < MINFLOW + ) isDry = TRUE; + + //... update status of HasWetLids + if ( !isDry ) HasWetLids = TRUE; + + //... write results to LID report file + if ( lidUnit->rptFile ) + { + //... convert rate results to original units (in/hr or mm/hr) + ucf = ucfRainfall; + rptVars[SURF_INFLOW] = SurfaceInflow*ucf; + rptVars[TOTAL_EVAP] = totalEvap*ucf; + rptVars[SURF_INFIL] = SurfaceInfil*ucf; + rptVars[PAVE_PERC] = PavePerc*ucf; + rptVars[SOIL_PERC] = SoilPerc*ucf; + rptVars[STOR_EXFIL] = StorageExfil*ucf; + rptVars[SURF_OUTFLOW] = SurfaceOutflow*ucf; + rptVars[STOR_DRAIN] = StorageDrain*ucf; + + //... convert storage results to original units (in or mm) + ucf = ucfRainDepth; + rptVars[SURF_DEPTH] = theLidUnit->surfaceDepth*ucf; + rptVars[PAVE_DEPTH] = theLidUnit->paveDepth*ucf; + rptVars[SOIL_MOIST] = theLidUnit->soilMoisture; + rptVars[STOR_DEPTH] = theLidUnit->storageDepth*ucf; + + //... if the current LID state is wet but the previous state was dry + // for more than one period then write the saved previous results + // to the report file thus marking the end of a dry period + if ( !isDry && theLidUnit->rptFile->wasDry > 1) + { + fprintf(theLidUnit->rptFile->file, "%s", + theLidUnit->rptFile->results); + } + + //... write the current results to a string which is saved between + // reporting periods + elapsedHrs = NewRunoffTime / 1000.0 / 3600.0; + datetime_getTimeStamp( + M_D_Y, getDateTime(NewRunoffTime), TIME_STAMP_SIZE, timeStamp); + snprintf(theLidUnit->rptFile->results, sizeof(theLidUnit->rptFile->results), + "\n%20s\t %8.3f\t %8.3f\t %8.4f\t %8.3f\t %8.3f\t %8.3f\t %8.3f\t" + "%8.3f\t %8.3f\t %8.3f\t %8.3f\t %8.3f\t %8.3f", + timeStamp, elapsedHrs, rptVars[0], rptVars[1], rptVars[2], + rptVars[3], rptVars[4], rptVars[5], rptVars[6], rptVars[7], + rptVars[8], rptVars[9], rptVars[10], rptVars[11]); + + //... if the current LID state is dry + if ( isDry ) + { + //... if the previous state was wet then write the current + // results to file marking the start of a dry period + if ( theLidUnit->rptFile->wasDry == 0 ) + { + fprintf(theLidUnit->rptFile->file, "%s", + theLidUnit->rptFile->results); + } + + //... increment the number of successive dry periods + theLidUnit->rptFile->wasDry++; + } + + //... if the current LID state is wet + else + { + //... write the current results to the report file + fprintf(theLidUnit->rptFile->file, "%s", + theLidUnit->rptFile->results); + + //... re-set the number of successive dry periods to 0 + theLidUnit->rptFile->wasDry = 0; + } + } +} + +//============================================================================= + +void roofFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates for roof disconnection. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + double surfaceDepth = x[SURF]; + + getEvapRates(surfaceDepth, 0.0, 0.0, 0.0, 1.0); + SurfaceVolume = surfaceDepth; + SurfaceInfil = 0.0; + if ( theLidProc->surface.alpha > 0.0 ) + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + else getSurfaceOverflowRate(&surfaceDepth); + StorageDrain = MIN(theLidProc->drain.coeff/UCF(RAINFALL), SurfaceOutflow); + SurfaceOutflow -= StorageDrain; + f[SURF] = (SurfaceInflow - SurfaceEvap - StorageDrain - SurfaceOutflow); +} + +//============================================================================= + +void greenRoofFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates from the layers of a green roof. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + // Moisture level variables + double surfaceDepth; + double soilTheta; + double storageDepth; + + // Intermediate variables + double availVolume; + double maxRate; + + // Green roof properties + double soilThickness = theLidProc->soil.thickness; + double storageThickness = theLidProc->storage.thickness; + double soilPorosity = theLidProc->soil.porosity; + double storageVoidFrac = theLidProc->storage.voidFrac; + double soilFieldCap = theLidProc->soil.fieldCap; + double soilWiltPoint = theLidProc->soil.wiltPoint; + + //... retrieve moisture levels from input vector + surfaceDepth = x[SURF]; + soilTheta = x[SOIL]; + storageDepth = x[STOR]; + + //... convert moisture levels to volumes + SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; + SoilVolume = soilTheta * soilThickness; + StorageVolume = storageDepth * storageVoidFrac; + + //... get ET rates + availVolume = SoilVolume - soilWiltPoint * soilThickness; + getEvapRates(SurfaceVolume, 0.0, availVolume, StorageVolume, 1.0); + if ( soilTheta >= soilPorosity ) StorageEvap = 0.0; + + //... soil layer perc rate + SoilPerc = getSoilPercRate(soilTheta); + + //... limit perc rate by available water + availVolume = (soilTheta - soilFieldCap) * soilThickness; + maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; + SoilPerc = MIN(SoilPerc, maxRate); + SoilPerc = MAX(SoilPerc, 0.0); + + //... storage (drain mat) outflow rate + StorageExfil = 0.0; + StorageDrain = getDrainMatOutflow(storageDepth); + + //... unit is full + if ( soilTheta >= soilPorosity && storageDepth >= storageThickness ) + { + //... outflow from both layers equals limiting rate + maxRate = MIN(SoilPerc, StorageDrain); + SoilPerc = maxRate; + StorageDrain = maxRate; + + //... adjust inflow rate to soil layer + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... unit not full + else + { + //... limit drainmat outflow by available storage volume + maxRate = storageDepth * storageVoidFrac / Tstep - StorageEvap; + if ( storageDepth >= storageThickness ) maxRate += SoilPerc; + maxRate = MAX(maxRate, 0.0); + StorageDrain = MIN(StorageDrain, maxRate); + + //... limit soil perc inflow by unused storage volume + maxRate = (storageThickness - storageDepth) * storageVoidFrac / Tstep + + StorageDrain + StorageEvap; + SoilPerc = MIN(SoilPerc, maxRate); + + //... adjust surface infil. so soil porosity not exceeded + maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + + SoilPerc + SoilEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + // ... find surface outflow rate + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + + // ... compute overall layer flux rates + f[SURF] = (SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow) / + theLidProc->surface.voidFrac; + f[SOIL] = (SurfaceInfil - SoilEvap - SoilPerc) / + theLidProc->soil.thickness; + f[STOR] = (SoilPerc - StorageEvap - StorageDrain) / + theLidProc->storage.voidFrac; +} + +//============================================================================= + +void biocellFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates from the layers of a bio-retention cell LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + // Moisture level variables + double surfaceDepth; + double soilTheta; + double storageDepth; + + // Intermediate variables + double availVolume; + double maxRate; + + // LID layer properties + double soilThickness = theLidProc->soil.thickness; + double soilPorosity = theLidProc->soil.porosity; + double soilFieldCap = theLidProc->soil.fieldCap; + double soilWiltPoint = theLidProc->soil.wiltPoint; + double storageThickness = theLidProc->storage.thickness; + double storageVoidFrac = theLidProc->storage.voidFrac; + + //... retrieve moisture levels from input vector + surfaceDepth = x[SURF]; + soilTheta = x[SOIL]; + storageDepth = x[STOR]; + + //... convert moisture levels to volumes + SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; + SoilVolume = soilTheta * soilThickness; + StorageVolume = storageDepth * storageVoidFrac; + + //... get ET rates + availVolume = SoilVolume - soilWiltPoint * soilThickness; + getEvapRates(SurfaceVolume, 0.0, availVolume, StorageVolume, 1.0); + if ( soilTheta >= soilPorosity ) StorageEvap = 0.0; + + //... soil layer perc rate + SoilPerc = getSoilPercRate(soilTheta); + + //... limit perc rate by available water + availVolume = (soilTheta - soilFieldCap) * soilThickness; + maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; + SoilPerc = MIN(SoilPerc, maxRate); + SoilPerc = MAX(SoilPerc, 0.0); + + //... exfiltration rate out of storage layer + StorageExfil = getStorageExfilRate(); + + //... underdrain flow rate + StorageDrain = 0.0; + if ( theLidProc->drain.coeff > 0.0 ) + { + StorageDrain = getStorageDrainRate(storageDepth, soilTheta, 0.0, + surfaceDepth); + } + + //... special case of no storage layer present + if ( storageThickness == 0.0 ) + { + StorageEvap = 0.0; + maxRate = MIN(SoilPerc, StorageExfil); + SoilPerc = maxRate; + StorageExfil = maxRate; + + //... limit surface infil. by unused soil volume + maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + + SoilPerc + SoilEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + + } + + //... storage & soil layers are full + else if ( soilTheta >= soilPorosity && storageDepth >= storageThickness ) + { + //... limiting rate is smaller of soil perc and storage outflow + maxRate = StorageExfil + StorageDrain; + if ( SoilPerc < maxRate ) + { + maxRate = SoilPerc; + if ( maxRate > StorageExfil ) StorageDrain = maxRate - StorageExfil; + else + { + StorageExfil = maxRate; + StorageDrain = 0.0; + } + } + else SoilPerc = maxRate; + + //... apply limiting rate to surface infil. + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... either layer not full + else if ( storageThickness > 0.0 ) + { + //... limit storage exfiltration by available storage volume + maxRate = SoilPerc - StorageEvap + storageDepth*storageVoidFrac/Tstep; + StorageExfil = MIN(StorageExfil, maxRate); + StorageExfil = MAX(StorageExfil, 0.0); + + //... limit underdrain flow by volume above drain offset + if ( StorageDrain > 0.0 ) + { + maxRate = -StorageExfil - StorageEvap; + if ( storageDepth >= storageThickness) maxRate += SoilPerc; + if ( theLidProc->drain.offset <= storageDepth ) + { + maxRate += (storageDepth - theLidProc->drain.offset) * + storageVoidFrac/Tstep; + } + maxRate = MAX(maxRate, 0.0); + StorageDrain = MIN(StorageDrain, maxRate); + } + + //... limit soil perc by unused storage volume + maxRate = StorageExfil + StorageDrain + StorageEvap + + (storageThickness - storageDepth) * + storageVoidFrac/Tstep; + SoilPerc = MIN(SoilPerc, maxRate); + + //... limit surface infil. by unused soil volume + maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + + SoilPerc + SoilEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... find surface layer outflow rate + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + + //... compute overall layer flux rates + f[SURF] = (SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow) / + theLidProc->surface.voidFrac; + f[SOIL] = (SurfaceInfil - SoilEvap - SoilPerc) / + theLidProc->soil.thickness; + if ( storageThickness == 0.0 ) f[STOR] = 0.0; + else f[STOR] = (SoilPerc - StorageEvap - StorageExfil - StorageDrain) / + theLidProc->storage.voidFrac; +} + +//============================================================================= + +void trenchFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates from the layers of an infiltration trench LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + // Moisture level variables + double surfaceDepth; + double storageDepth; + + // Intermediate variables + double availVolume; + double maxRate; + + // Storage layer properties + double storageThickness = theLidProc->storage.thickness; + double storageVoidFrac = theLidProc->storage.voidFrac; + + //... retrieve moisture levels from input vector + surfaceDepth = x[SURF]; + storageDepth = x[STOR]; + + //... convert moisture levels to volumes + SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; + SoilVolume = 0.0; + StorageVolume = storageDepth * storageVoidFrac; + + //... get ET rates + availVolume = (storageThickness - storageDepth) * storageVoidFrac; + getEvapRates(SurfaceVolume, 0.0, 0.0, StorageVolume, 1.0); + + //... no storage evap if surface ponded + if ( surfaceDepth > 0.0 ) StorageEvap = 0.0; + + //... nominal storage inflow + StorageInflow = SurfaceInflow + SurfaceVolume / Tstep; + + //... exfiltration rate out of storage layer + StorageExfil = getStorageExfilRate(); + + //... underdrain flow rate + StorageDrain = 0.0; + if ( theLidProc->drain.coeff > 0.0 ) + { + StorageDrain = getStorageDrainRate(storageDepth, 0.0, 0.0, surfaceDepth); + } + + //... limit storage exfiltration by available storage volume + maxRate = StorageInflow - StorageEvap + storageDepth*storageVoidFrac/Tstep; + StorageExfil = MIN(StorageExfil, maxRate); + StorageExfil = MAX(StorageExfil, 0.0); + + //... limit underdrain flow by volume above drain offset + if ( StorageDrain > 0.0 ) + { + maxRate = -StorageExfil - StorageEvap; + if (storageDepth >= storageThickness ) maxRate += StorageInflow; + if ( theLidProc->drain.offset <= storageDepth ) + { + maxRate += (storageDepth - theLidProc->drain.offset) * + storageVoidFrac/Tstep; + } + maxRate = MAX(maxRate, 0.0); + StorageDrain = MIN(StorageDrain, maxRate); + } + + //... limit storage inflow to not exceed storage layer capacity + maxRate = (storageThickness - storageDepth)*storageVoidFrac/Tstep + + StorageExfil + StorageEvap + StorageDrain; + StorageInflow = MIN(StorageInflow, maxRate); + + //... equate surface infil to storage inflow + SurfaceInfil = StorageInflow; + + //... find surface outflow rate + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + + // ... find net fluxes for each layer + f[SURF] = SurfaceInflow - SurfaceEvap - StorageInflow - SurfaceOutflow / + theLidProc->surface.voidFrac;; + f[STOR] = (StorageInflow - StorageEvap - StorageExfil - StorageDrain) / + theLidProc->storage.voidFrac; + f[SOIL] = 0.0; +} + +//============================================================================= + +void pavementFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates for the layers of a porous pavement LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + //... Moisture level variables + double surfaceDepth; + double paveDepth; + double soilTheta; + double storageDepth; + + //... Intermediate variables + double pervFrac = (1.0 - theLidProc->pavement.impervFrac); + double storageInflow; // inflow rate to storage layer (ft/s) + double availVolume; + double maxRate; + + //... LID layer properties + double paveVoidFrac = theLidProc->pavement.voidFrac * pervFrac; + double paveThickness = theLidProc->pavement.thickness; + double soilThickness = theLidProc->soil.thickness; + double soilPorosity = theLidProc->soil.porosity; + double soilFieldCap = theLidProc->soil.fieldCap; + double soilWiltPoint = theLidProc->soil.wiltPoint; + double storageThickness = theLidProc->storage.thickness; + double storageVoidFrac = theLidProc->storage.voidFrac; + + //... retrieve moisture levels from input vector + surfaceDepth = x[SURF]; + paveDepth = x[PAVE]; + soilTheta = x[SOIL]; + storageDepth = x[STOR]; + + //... convert moisture levels to volumes + SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; + PaveVolume = paveDepth * paveVoidFrac; + SoilVolume = soilTheta * soilThickness; + StorageVolume = storageDepth * storageVoidFrac; + + //... get ET rates + availVolume = SoilVolume - soilWiltPoint * soilThickness; + getEvapRates(SurfaceVolume, PaveVolume, availVolume, StorageVolume, + pervFrac); + + //... no storage evap if soil or pavement layer saturated + if ( paveDepth >= paveThickness || + ( soilThickness > 0.0 && soilTheta >= soilPorosity ) + ) StorageEvap = 0.0; + + //... find nominal rate of surface infiltration into pavement layer + SurfaceInfil = SurfaceInflow + (SurfaceVolume / Tstep); + + //... find perc rate out of pavement layer + PavePerc = getPavementPermRate() * pervFrac; + + //... surface infiltration can't exceed pavement permeability + SurfaceInfil = MIN(SurfaceInfil, PavePerc); + + //... limit pavement perc by available water + maxRate = PaveVolume/Tstep + SurfaceInfil - PaveEvap; + maxRate = MAX(maxRate, 0.0); + PavePerc = MIN(PavePerc, maxRate); + + //... find soil layer perc rate + if ( soilThickness > 0.0 ) + { + SoilPerc = getSoilPercRate(soilTheta); + availVolume = (soilTheta - soilFieldCap) * soilThickness; + maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; + SoilPerc = MIN(SoilPerc, maxRate); + SoilPerc = MAX(SoilPerc, 0.0); + } + else SoilPerc = PavePerc; + + //... exfiltration rate out of storage layer + StorageExfil = getStorageExfilRate(); + + //... underdrain flow rate + StorageDrain = 0.0; + if ( theLidProc->drain.coeff > 0.0 ) + { + StorageDrain = getStorageDrainRate(storageDepth, soilTheta, paveDepth, + surfaceDepth); + } + + //... check for adjacent saturated layers + + //... no soil layer, pavement & storage layers are full + if ( soilThickness == 0.0 && + storageDepth >= storageThickness && + paveDepth >= paveThickness ) + { + //... pavement outflow can't exceed storage outflow + maxRate = StorageEvap + StorageDrain + StorageExfil; + if ( PavePerc > maxRate ) PavePerc = maxRate; + + //... storage outflow can't exceed pavement outflow + else + { + //... use up available exfiltration capacity first + StorageExfil = MIN(StorageExfil, PavePerc); + StorageDrain = PavePerc - StorageExfil; + } + + //... set soil perc to pavement perc + SoilPerc = PavePerc; + + //... limit surface infil. by pavement perc + SurfaceInfil = MIN(SurfaceInfil, PavePerc); + } + + //... pavement, soil & storage layers are full + else if ( soilThickness > 0 && + storageDepth >= storageThickness && + soilTheta >= soilPorosity && + paveDepth >= paveThickness ) + { + //... find which layer has limiting flux rate + maxRate = StorageExfil + StorageDrain; + if ( SoilPerc < maxRate) maxRate = SoilPerc; + else maxRate = MIN(maxRate, PavePerc); + + //... use up available storage exfiltration capacity first + if ( maxRate > StorageExfil ) StorageDrain = maxRate - StorageExfil; + else + { + StorageExfil = maxRate; + StorageDrain = 0.0; + } + SoilPerc = maxRate; + PavePerc = maxRate; + + //... limit surface infil. by pavement perc + SurfaceInfil = MIN(SurfaceInfil, PavePerc); + } + + //... storage & soil layers are full + else if ( soilThickness > 0.0 && + storageDepth >= storageThickness && + soilTheta >= soilPorosity ) + { + //... soil perc can't exceed storage outflow + maxRate = StorageDrain + StorageExfil; + if ( SoilPerc > maxRate ) SoilPerc = maxRate; + + //... storage outflow can't exceed soil perc + else + { + //... use up available exfiltration capacity first + StorageExfil = MIN(StorageExfil, SoilPerc); + StorageDrain = SoilPerc - StorageExfil; + } + + //... limit surface infil. by available pavement volume + availVolume = (paveThickness - paveDepth) * paveVoidFrac; + maxRate = availVolume / Tstep + PavePerc + PaveEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... soil and pavement layers are full + else if ( soilThickness > 0.0 && + paveDepth >= paveThickness && + soilTheta >= soilPorosity ) + { + PavePerc = MIN(PavePerc, SoilPerc); + SoilPerc = PavePerc; + SurfaceInfil = MIN(SurfaceInfil,PavePerc); + } + + //... no adjoining layers are full + else + { + //... limit storage exfiltration by available storage volume + // (if no soil layer, SoilPerc is same as PavePerc) + maxRate = SoilPerc - StorageEvap + StorageVolume / Tstep; + maxRate = MAX(0.0, maxRate); + StorageExfil = MIN(StorageExfil, maxRate); + + //... limit underdrain flow by volume above drain offset + if ( StorageDrain > 0.0 ) + { + maxRate = -StorageExfil - StorageEvap; + if (storageDepth >= storageThickness ) maxRate += SoilPerc; + if ( theLidProc->drain.offset <= storageDepth ) + { + maxRate += (storageDepth - theLidProc->drain.offset) * + storageVoidFrac/Tstep; + } + maxRate = MAX(maxRate, 0.0); + StorageDrain = MIN(StorageDrain, maxRate); + } + + //... limit soil & pavement outflow by unused storage volume + availVolume = (storageThickness - storageDepth) * storageVoidFrac; + maxRate = availVolume/Tstep + StorageEvap + StorageDrain + StorageExfil; + maxRate = MAX(maxRate, 0.0); + if ( soilThickness > 0.0 ) + { + SoilPerc = MIN(SoilPerc, maxRate); + maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + + SoilPerc; + } + PavePerc = MIN(PavePerc, maxRate); + + //... limit surface infil. by available pavement volume + availVolume = (paveThickness - paveDepth) * paveVoidFrac; + maxRate = availVolume / Tstep + PavePerc + PaveEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... surface outflow + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + + //... compute overall layer flux rates + f[SURF] = SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow; + f[PAVE] = (SurfaceInfil - PaveEvap - PavePerc) / paveVoidFrac; + if ( theLidProc->soil.thickness > 0.0) + { + f[SOIL] = (PavePerc - SoilEvap - SoilPerc) / soilThickness; + storageInflow = SoilPerc; + } + else + { + f[SOIL] = 0.0; + storageInflow = PavePerc; + SoilPerc = 0.0; + } + f[STOR] = (storageInflow - StorageEvap - StorageExfil - StorageDrain) / + storageVoidFrac; +} + +//============================================================================= + +void swaleFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates from a vegetative swale LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + double depth; // depth of surface water in swale (ft) + double topWidth; // top width of full swale (ft) + double botWidth; // bottom width of swale (ft) + double length; // length of swale (ft) + double surfInflow; // inflow rate to swale (cfs) + double surfWidth; // top width at current water depth (ft) + double surfArea; // surface area of current water depth (ft2) + double flowArea; // x-section flow area (ft2) + double lidArea; // surface area of full swale (ft2) + double hydRadius; // hydraulic radius for current depth (ft) + double slope; // slope of swale side wall (run/rise) + double volume; // swale volume at current water depth (ft3) + double dVdT; // change in volume w.r.t. time (cfs) + double dStore; // depression storage depth (ft) + double xDepth; // depth above depression storage (ft) + + //... retrieve state variable from work vector + depth = x[SURF]; + depth = MIN(depth, theLidProc->surface.thickness); + + //... depression storage depth + dStore = 0.0; + + //... get swale's bottom width + // (0.5 ft minimum to avoid numerical problems) + slope = theLidProc->surface.sideSlope; + topWidth = theLidUnit->fullWidth; + topWidth = MAX(topWidth, 0.5); + botWidth = topWidth - 2.0 * slope * theLidProc->surface.thickness; + if ( botWidth < 0.5 ) + { + botWidth = 0.5; + slope = 0.5 * (topWidth - 0.5) / theLidProc->surface.thickness; + } + + //... swale's length + lidArea = theLidUnit->area; + length = lidArea / topWidth; + + //... top width, surface area and flow area of current ponded depth + surfWidth = botWidth + 2.0 * slope * depth; + surfArea = length * surfWidth; + flowArea = (depth * (botWidth + slope * depth)) * + theLidProc->surface.voidFrac; + + //... wet volume and effective depth + volume = length * flowArea; + + //... surface inflow into swale (cfs) + surfInflow = SurfaceInflow * lidArea; + + //... ET rate in cfs + SurfaceEvap = EvapRate * surfArea; + SurfaceEvap = MIN(SurfaceEvap, volume/Tstep); + + //... infiltration rate to native soil in cfs + StorageExfil = SurfaceInfil * surfArea; + + //... no surface outflow if depth below depression storage + xDepth = depth - dStore; + if ( xDepth <= ZERO ) SurfaceOutflow = 0.0; + + //... otherwise compute a surface outflow + else + { + //... modify flow area to remove depression storage, + flowArea -= (dStore * (botWidth + slope * dStore)) * + theLidProc->surface.voidFrac; + if ( flowArea < ZERO ) SurfaceOutflow = 0.0; + else + { + //... compute hydraulic radius + botWidth = botWidth + 2.0 * dStore * slope; + hydRadius = botWidth + 2.0 * xDepth * sqrt(1.0 + slope*slope); + hydRadius = flowArea / hydRadius; + + //... use Manning Eqn. to find outflow rate in cfs + SurfaceOutflow = theLidProc->surface.alpha * flowArea * + pow(hydRadius, 2./3.); + } + } + + //... net flux rate (dV/dt) in cfs + dVdT = surfInflow - SurfaceEvap - StorageExfil - SurfaceOutflow; + + //... when full, any net positive inflow becomes spillage + if ( depth == theLidProc->surface.thickness && dVdT > 0.0 ) + { + SurfaceOutflow += dVdT; + dVdT = 0.0; + } + + //... convert flux rates to ft/s + SurfaceEvap /= lidArea; + StorageExfil /= lidArea; + SurfaceOutflow /= lidArea; + f[SURF] = dVdT / surfArea; + f[SOIL] = 0.0; + f[STOR] = 0.0; + + //... assign values to layer volumes + SurfaceVolume = volume / lidArea; + SoilVolume = 0.0; + StorageVolume = 0.0; +} + +//============================================================================= + +void barrelFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates for a rain barrel LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + double storageDepth = x[STOR]; + double head; + double maxValue; + + //... assign values to layer volumes + SurfaceVolume = 0.0; + SoilVolume = 0.0; + StorageVolume = storageDepth; + + //... initialize flows + SurfaceInfil = 0.0; + SurfaceOutflow = 0.0; + StorageDrain = 0.0; + + //... compute outflow if time since last rain exceeds drain delay + // (dryTime is updated in lid.evalLidUnit at each time step) + if ( theLidProc->drain.delay == 0.0 || + theLidUnit->dryTime >= theLidProc->drain.delay ) + { + head = storageDepth - theLidProc->drain.offset; + if ( head > 0.0 ) + { + StorageDrain = getStorageDrainRate(storageDepth, 0.0, 0.0, 0.0); + maxValue = (head/Tstep); + StorageDrain = MIN(StorageDrain, maxValue); + } + } + + //... limit inflow to available storage + StorageInflow = SurfaceInflow; + maxValue = (theLidProc->storage.thickness - storageDepth) / Tstep + + StorageDrain; + StorageInflow = MIN(StorageInflow, maxValue); + SurfaceInfil = StorageInflow; + + //... assign values to layer flux rates + f[SURF] = SurfaceInflow - StorageInflow; + f[STOR] = StorageInflow - StorageDrain; + f[SOIL] = 0.0; +} + +//============================================================================= + +double getSurfaceOutflowRate(double depth) +// +// Purpose: computes outflow rate from a LID's surface layer. +// Input: depth = depth of ponded water on surface layer (ft) +// Output: returns outflow from surface layer (ft/s) +// +// Note: this function should not be applied to swales or rain barrels. +// +{ + double delta; + double outflow; + + //... no outflow if ponded depth below storage depth + delta = depth - theLidProc->surface.thickness; + if ( delta < 0.0 ) return 0.0; + + //... compute outflow from overland flow Manning equation + outflow = theLidProc->surface.alpha * pow(delta, 5.0/3.0) * + theLidUnit->fullWidth / theLidUnit->area; + outflow = MIN(outflow, delta / Tstep); + return outflow; +} + +//============================================================================= + +double getPavementPermRate() +// +// Purpose: computes reduced permeability of a pavement layer due to +// clogging. +// Input: none +// Output: returns the reduced permeability of the pavement layer (ft/s). +// +{ + double permReduction = 0.0; + double clogFactor= theLidProc->pavement.clogFactor; + double regenDays = theLidProc->pavement.regenDays; + + // ... find permeability reduction due to clogging + if ( clogFactor > 0.0 ) + { + // ... see if permeability regeneration has occurred + // (regeneration is assumed to reduce the total + // volumetric loading that the pavement has received) + if ( regenDays > 0.0 ) + { + if ( OldRunoffTime / 1000.0 / SECperDAY >= theLidUnit->nextRegenDay ) + { + // ... reduce total volume treated by degree of regeneration + theLidUnit->volTreated *= + (1.0 - theLidProc->pavement.regenDegree); + + // ... update next day that regenration occurs + theLidUnit->nextRegenDay += regenDays; + } + } + + // ... find permeabiity reduction factor + permReduction = theLidUnit->volTreated / clogFactor; + permReduction = MIN(permReduction, 1.0); + } + + // ... return the effective pavement permeability + return theLidProc->pavement.kSat * (1.0 - permReduction); +} + +//============================================================================= + +double getSoilPercRate(double theta) +// +// Purpose: computes percolation rate of water through a LID's soil layer. +// Input: theta = moisture content (fraction) +// Output: returns percolation rate within soil layer (ft/s) +// +{ + double delta; // moisture deficit + + // ... no percolation if soil moisture <= field capacity + if ( theta <= theLidProc->soil.fieldCap ) return 0.0; + + // ... perc rate = unsaturated hydraulic conductivity + delta = theLidProc->soil.porosity - theta; + return theLidProc->soil.kSat * exp(-delta * theLidProc->soil.kSlope); + +} + +//============================================================================= + +double getStorageExfilRate() +// +// Purpose: computes exfiltration rate from storage zone into +// native soil beneath a LID. +// Input: depth = depth of water storage zone (ft) +// Output: returns infiltration rate (ft/s) +// +{ + double infil = 0.0; + double clogFactor = 0.0; + + if ( theLidProc->storage.kSat == 0.0 ) return 0.0; + if ( MaxNativeInfil == 0.0 ) return 0.0; + + //... reduction due to clogging + clogFactor = theLidProc->storage.clogFactor; + if ( clogFactor > 0.0 ) + { + clogFactor = theLidUnit->waterBalance.inflow / clogFactor; + clogFactor = MIN(clogFactor, 1.0); + } + + //... infiltration rate = storage Ksat reduced by any clogging + infil = theLidProc->storage.kSat * (1.0 - clogFactor); + + //... limit infiltration rate by any groundwater-imposed limit + return MIN(infil, MaxNativeInfil); +} + +//============================================================================= + +double getStorageDrainRate(double storageDepth, double soilTheta, + double paveDepth, double surfaceDepth) +// +// Purpose: computes underdrain flow rate in a LID's storage layer. +// Input: storageDepth = depth of water in storage layer (ft) +// soilTheta = moisture content of soil layer +// paveDepth = effective depth of water in pavement layer (ft) +// surfaceDepth = depth of ponded water on surface layer (ft) +// Output: returns flow in underdrain (ft/s) +// +// Note: drain eqn. is evaluated in user's units. +// Note: head on drain is water depth in storage layer plus the +// layers above it (soil, pavement, and surface in that order) +// minus the drain outlet offset. +{ + int curve = theLidProc->drain.qCurve; + double head = storageDepth; + double outflow = 0.0; + double paveThickness = theLidProc->pavement.thickness; + double soilThickness = theLidProc->soil.thickness; + double soilPorosity = theLidProc->soil.porosity; + double soilFieldCap = theLidProc->soil.fieldCap; + double storageThickness = theLidProc->storage.thickness; + + // --- storage layer is full + if ( storageDepth >= storageThickness ) + { + // --- a soil layer exists + if ( soilThickness > 0.0 ) + { + // --- increase head by fraction of soil layer saturated + if ( soilTheta > soilFieldCap ) + { + head += (soilTheta - soilFieldCap) / + (soilPorosity - soilFieldCap) * soilThickness; + + // --- soil layer is saturated, increase head by water + // depth in layer above it + if ( soilTheta >= soilPorosity ) + { + if ( paveThickness > 0.0 ) head += paveDepth; + else head += surfaceDepth; + } + } + } + + // --- no soil layer so increase head by water level in pavement + // layer and possibly surface layer + if ( paveThickness > 0.0 ) + { + head += paveDepth; + if ( paveDepth >= paveThickness ) head += surfaceDepth; + } + } + + // --- no outflow if: + // a) no prior outflow and head below open threshold + // b) prior outflow and head below closed threshold + if ( theLidUnit->oldDrainFlow == 0.0 && + head <= theLidProc->drain.hOpen ) return 0.0; + if ( theLidUnit->oldDrainFlow > 0.0 && + head <= theLidProc->drain.hClose ) return 0.0; + + // --- make head relative to drain offset + head -= theLidProc->drain.offset; + + // --- compute drain outflow from underdrain flow equation in user units + // (head in inches or mm, flow rate in in/hr or mm/hr) + if ( head > ZERO ) + { + // --- convert head to user units + head *= UCF(RAINDEPTH); + + // --- compute drain outflow in user units + outflow = theLidProc->drain.coeff * + pow(head, theLidProc->drain.expon); + + // --- apply user-supplied control curve to outflow + if (curve >= 0) outflow *= table_lookup(&Curve[curve], head); + + // --- convert outflow to ft/s + outflow /= UCF(RAINFALL); + } + return outflow; +} + +//============================================================================= + +double getDrainMatOutflow(double depth) +// +// Purpose: computes flow rate through a green roof's drainage mat. +// Input: depth = depth of water in drainage mat (ft) +// Output: returns flow in drainage mat (ft/s) +// +{ + //... default is to pass all inflow + double result = SoilPerc; + + //... otherwise use Manning eqn. if its parameters were supplied + if ( theLidProc->drainMat.alpha > 0.0 ) + { + result = theLidProc->drainMat.alpha * pow(depth, 5.0/3.0) * + theLidUnit->fullWidth / theLidUnit->area * + theLidProc->drainMat.voidFrac; + } + return result; +} + +//============================================================================= + +void getEvapRates(double surfaceVol, double paveVol, double soilVol, + double storageVol, double pervFrac) +// +// Purpose: computes surface, pavement, soil, and storage evaporation rates. +// Input: surfaceVol = volume/area of ponded water on surface layer (ft) +// paveVol = volume/area of water in pavement pores (ft) +// soilVol = volume/area of water in soil (or pavement) pores (ft) +// storageVol = volume/area of water in storage layer (ft) +// pervFrac = fraction of surface layer that is pervious +// Output: none +// +{ + double availEvap; + + //... surface evaporation flux + availEvap = EvapRate; + SurfaceEvap = MIN(availEvap, surfaceVol/Tstep); + SurfaceEvap = MAX(0.0, SurfaceEvap); + availEvap = MAX(0.0, (availEvap - SurfaceEvap)); + availEvap *= pervFrac; + + //... no subsurface evap if water is infiltrating + if ( SurfaceInfil > 0.0 ) + { + PaveEvap = 0.0; + SoilEvap = 0.0; + StorageEvap = 0.0; + } + else + { + //... pavement evaporation flux + PaveEvap = MIN(availEvap, paveVol / Tstep); + availEvap = MAX(0.0, (availEvap - PaveEvap)); + + //... soil evaporation flux + SoilEvap = MIN(availEvap, soilVol / Tstep); + availEvap = MAX(0.0, (availEvap - SoilEvap)); + + //... storage evaporation flux + StorageEvap = MIN(availEvap, storageVol / Tstep); + } +} + +//============================================================================= + +double getSurfaceOverflowRate(double* surfaceDepth) +// +// Purpose: finds surface overflow rate from a LID unit. +// Input: surfaceDepth = depth of water stored in surface layer (ft) +// Output: returns the overflow rate (ft/s) +// +{ + double delta = *surfaceDepth - theLidProc->surface.thickness; + if ( delta <= 0.0 ) return 0.0; + *surfaceDepth = theLidProc->surface.thickness; + return delta * theLidProc->surface.voidFrac / Tstep; +} + +//============================================================================= + +void updateWaterBalance(TLidUnit *lidUnit, double inflow, double evap, + double infil, double surfFlow, double drainFlow, double storage) +// +// Purpose: updates components of the water mass balance for a LID unit +// over the current time step. +// Input: lidUnit = a particular LID unit +// inflow = runon + rainfall to the LID unit (ft/s) +// evap = evaporation rate from the unit (ft/s) +// infil = infiltration out the bottom of the unit (ft/s) +// surfFlow = surface runoff from the unit (ft/s) +// drainFlow = underdrain flow from the unit +// storage = volume of water stored in the unit (ft) +// Output: none +// +{ + lidUnit->volTreated += inflow * Tstep; + lidUnit->waterBalance.inflow += inflow * Tstep; + lidUnit->waterBalance.evap += evap * Tstep; + lidUnit->waterBalance.infil += infil * Tstep; + lidUnit->waterBalance.surfFlow += surfFlow * Tstep; + lidUnit->waterBalance.drainFlow += drainFlow * Tstep; + lidUnit->waterBalance.finalVol = storage; +} + +//============================================================================= + +int modpuls_solve(int n, double* x, double* xOld, double* xPrev, + double* xMin, double* xMax, double* xTol, + double* qOld, double* q, double dt, double omega, + void (*derivs)(double*, double*)) +// +// Purpose: solves system of equations dx/dt = q(x) for x at end of time step +// dt using a modified Puls method. +// Input: n = number of state variables +// x = vector of state variables +// xOld = state variable values at start of time step +// xPrev = state variable values from previous iteration +// xMin = lower limits on state variables +// xMax = upper limits on state variables +// xTol = convergence tolerances on state variables +// qOld = flux rates at start of time step +// q = flux rates at end of time step +// dt = time step (sec) +// omega = time weighting parameter (use 0 for Euler method +// or 0.5 for modified Puls method) +// derivs = pointer to function that computes flux rates q as a +// function of state variables x +// Output: returns number of steps required for convergence (or 0 if +// process doesn't converge) +// +{ + int i; + int canStop; + int steps = 1; + int maxSteps = 20; + + //... initialize state variable values + for (i=0; i 0.0 && + fabs(x[i] - xPrev[i]) > xTol[i] ) canStop = 0; + xPrev[i] = x[i]; + } + + //... return if process converges + if (canStop) return steps; + steps++; + } + + //... no convergence so return 0 + return 0; +} diff --git a/src/link.c b/src/link.c new file mode 100644 index 000000000..6de3688d7 --- /dev/null +++ b/src/link.c @@ -0,0 +1,2675 @@ +//----------------------------------------------------------------------------- +// link.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// M. Tryby (EPA) +// +// Conveyance system link functions +// +// Update History +// ============== +// Build 5.1.007: +// - Optional surcharging of weirs introduced. +// Build 5.1.008: +// - Bug in finding flow through surcharged weir fixed. +// - Bug in finding if conduit is upstrm/dnstrm full fixed. +// - Monthly conductivity adjustment applied to conduit seepage. +// - Conduit seepage limited by conduit's flow rate. +// Build 5.1.010: +// - Support added for new ROADWAY_WEIR object. +// - Time of last setting change initialized for links. +// Build 5.1.011: +// - Crest elevation of regulator links raised to downstream invert. +// - Fixed converting roadWidth weir parameter to internal units. +// - Weir shape parameter deprecated. +// - Extra geometric parameters ignored for non-conduit open rectangular +// cross sections. +// Build 5.1.012: +// - Conduit seepage rate now based on flow width, not wetted perimeter. +// - Formula for side flow weir corrected. +// - Crest length contraction adjustments corrected. +// Build 5.1.013: +// - Maximum depth adjustments made for storage units that can surcharge. +// - Support added for head-dependent weir coefficient curves. +// - Adjustment of regulator link crest offset to match downstream node invert +// now only done for Dynamic Wave flow routing. +// Build 5.1.014: +// - Conduit evap. and seepage losses initialized to 0 in conduit_initState() +// and not allowed to exceed current flow rate in conduit_getLossRate(). +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for variable speed pumps. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" +#include "inlet.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double MIN_DELTA_Z = 0.001; // minimum elevation change for conduit + // slopes (ft) + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// link_readParams (called by parseLine in input.c) +// link_readXsectParams (called by parseLine in input.c) +// link_readLossParams (called by parseLine in input.c) +// link_validate (called by project_validate in project.c) +// link_initState (called by initObjects in swmm5.c) +// link_setOldHydState (called by routing_execute in routing.c) +// link_setOldQualState (called by routing_execute in routing.c) +// link_setTargetSetting (called by routing_execute in routing.c) +// link_setSetting (called by routing_execute in routing.c) +// link_getResults (called by output_saveLinkResults) +// link_getLength (called in dwflow.c, kinwave.c & flowrout.c) +// link_getFroude (called in dwflow.c) +// link_getInflow (called in flowrout.c & dynwave.c) +// link_setOutfallDepth (called in flowrout.c & dynwave.c) +// link_getYcrit (called by link_setOutfallDepth & in dwflow.c) +// link_getYnorm (called by conduit_initState, link_setOutfallDepth & in dwflow.c) +// link_getVelocity (called by link_getResults & stats_updateLinkStats) +// link_getPower (called by stats_updateLinkStats in stats.c) +// link_getLossRate (called in dwflow.c, kinwave.c & flowrout.c) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static void link_setParams(int j, int type, int n1, int n2, int k, double x[]); +static void link_convertOffsets(int j); +static double link_getOffsetHeight(int j, double offset, double elev); + +static int conduit_readParams(int j, int k, char* tok[], int ntoks); +static void conduit_validate(int j, int k); +static void conduit_initState(int j, int k); +static void conduit_reverse(int j, int k); +static double conduit_getLength(int j); +static double conduit_getLengthFactor(int j, int k, double roughness); +static double conduit_getSlope(int j); +static double conduit_getInflow(int j); +static double conduit_getLossRate(int j, double q); + +static int pump_readParams(int j, int k, char* tok[], int ntoks); +static void pump_validate(int j, int k); +static void pump_initState(int j, int k); +static double pump_getInflow(int j); + +static int orifice_readParams(int j, int k, char* tok[], int ntoks); +static void orifice_validate(int j, int k); +static void orifice_setSetting(int j, double tstep); +static double orifice_getWeirCoeff(int j, int k, double h); +static double orifice_getInflow(int j); +static double orifice_getFlow(int j, int k, double head, double f, + int hasFlapGate); + +static int weir_readParams(int j, int k, char* tok[], int ntoks); +static void weir_validate(int j, int k); +static void weir_setSetting(int j); +static double weir_getInflow(int j); +static double weir_getOpenArea(int j, double y); +static void weir_getFlow(int j, int k, double head, double dir, + int hasFlapGate, double* q1, double* q2); +static double weir_getOrificeFlow(int j, double head, double y, double cOrif); +static double weir_getdqdh(int k, double dir, double h, double q1, double q2); + +static int outlet_readParams(int j, int k, char* tok[], int ntoks); +static double outlet_getFlow(int k, double head); +static double outlet_getInflow(int j); + + +//============================================================================= + +int link_readParams(int j, int type, int k, char* tok[], int ntoks) +// +// Input: j = link index +// type = link type code +// k = link type index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads parameters for a specific type of link from a +// tokenized line of input data. +// +{ + switch ( type ) + { + case CONDUIT: return conduit_readParams(j, k, tok, ntoks); + case PUMP: return pump_readParams(j, k, tok, ntoks); + case ORIFICE: return orifice_readParams(j, k, tok, ntoks); + case WEIR: return weir_readParams(j, k, tok, ntoks); + case OUTLET: return outlet_readParams(j, k, tok, ntoks); + default: return 0; + } +} + +//============================================================================= + +int link_readXsectParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads a link's cross section parameters from a tokenized +// line of input data. +// Formats: +// Link Shape Geom1 Geom2 Geom3 Geom4 (Barrels Culvert) +// Link IRREGULAR TransectID +// Link STREET StreetID +// +{ + int i, j, k; + double x[4]; + + // --- check for minimum number of tokens + if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); + + // --- get index of link + j = project_findObject(LINK, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- get code of xsection shape + k = findmatch(tok[1], XsectTypeWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- assign default number of barrels to conduit + if ( Link[j].type == CONDUIT ) Conduit[Link[j].subIndex].barrels = 1; + + // --- assume link is not a culvert + Link[j].xsect.culvertCode = 0; + + // --- for irregular shape, find index of transect object + if ( k == IRREGULAR ) + { + i = project_findObject(TRANSECT, tok[2]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[2]); + Link[j].xsect.type = k; + Link[j].xsect.transect = i; + return 0; + } + + // --- for street cross section, find index of Street object + else if (k == STREET_XSECT) + { + i = project_findObject(STREET, tok[2]); + if (i < 0) return error_setInpError(ERR_NAME, tok[2]); + Link[j].xsect.type = k; + Link[j].xsect.transect = i; + return 0; + } + + else + { + // --- check that geometric parameters are present + if (ntoks < 6) return error_setInpError(ERR_ITEMS, ""); + + // --- parse max. depth & shape curve for a custom shape + if ( k == CUSTOM ) + { + if ( !getDouble(tok[2], &x[0]) || x[0] <= 0.0 ) + return error_setInpError(ERR_NUMBER, tok[2]); + i = project_findObject(CURVE, tok[3]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[3]); + Link[j].xsect.type = k; + Link[j].xsect.transect = i; + Link[j].xsect.yFull = x[0] / UCF(LENGTH); + } + + // --- parse and save geometric parameters + else for (i = 2; i <= 5; i++) + { + if ( !getDouble(tok[i], &x[i-2]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + + // --- ignore extra parameters for non-conduit open rectangular shapes + if ( Link[j].type != CONDUIT && k == RECT_OPEN ) + { + x[2] = 0.0; + x[3] = 0.0; + } + if ( !xsect_setParams(&Link[j].xsect, k, x, UCF(LENGTH)) ) + { + return error_setInpError(ERR_NUMBER, ""); + } + + // --- parse number of barrels if present + if ( Link[j].type == CONDUIT && ntoks >= 7 ) + { + i = atoi(tok[6]); + if ( i <= 0 ) return error_setInpError(ERR_NUMBER, tok[6]); + else Conduit[Link[j].subIndex].barrels = (char)i; + } + + // --- parse culvert code if present + if ( Link[j].type == CONDUIT && ntoks >= 8 ) + { + i = atoi(tok[7]); + if ( i < 0 ) return error_setInpError(ERR_NUMBER, tok[7]); + else Link[j].xsect.culvertCode = i; + } + } + return 0; +} + +//============================================================================= + +int link_readLossParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads local loss parameters for a link from a tokenized +// line of input data. +// +// Format: LinkID cInlet cOutlet cAvg FlapGate(YES/NO) SeepRate +// +{ + int i, j, k; + double x[3]; + double seepRate = 0.0; + + if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); + j = project_findObject(LINK, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + for (i=1; i<=3; i++) + { + if ( ! getDouble(tok[i], &x[i-1]) || x[i-1] < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + k = 0; + if ( ntoks >= 5 ) + { + k = findmatch(tok[4], NoYesWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); + } + if ( ntoks >= 6 ) + { + if ( ! getDouble(tok[5], &seepRate) ) + return error_setInpError(ERR_NUMBER, tok[5]); + } + Link[j].cLossInlet = x[0]; + Link[j].cLossOutlet = x[1]; + Link[j].cLossAvg = x[2]; + Link[j].hasFlapGate = k; + Link[j].seepRate = seepRate / UCF(RAINFALL); + return 0; +} + +//============================================================================= + +void link_setParams(int j, int type, int n1, int n2, int k, double x[]) +// +// Input: j = link index +// type = link type code +// n1 = index of upstream node +// n2 = index of downstream node +// k = index of link's sub-type +// x = array of parameter values +// Output: none +// Purpose: sets parameters for a link. +// +{ + Link[j].node1 = n1; + Link[j].node2 = n2; + Link[j].type = type; + Link[j].subIndex = k; + Link[j].offset1 = 0.0; + Link[j].offset2 = 0.0; + Link[j].q0 = 0.0; + Link[j].qFull = 0.0; + Link[j].setting = 1.0; + Link[j].targetSetting = 1.0; + Link[j].hasFlapGate = 0; + Link[j].qLimit = 0.0; // 0 means that no limit is defined + Link[j].direction = 1; + + switch (type) + { + case CONDUIT: + Conduit[k].length = x[0] / UCF(LENGTH); + Conduit[k].modLength = Conduit[k].length; + Conduit[k].roughness = x[1]; + Link[j].offset1 = x[2] / UCF(LENGTH); + Link[j].offset2 = x[3] / UCF(LENGTH); + Link[j].q0 = x[4] / UCF(FLOW); + Link[j].qLimit = x[5] / UCF(FLOW); + break; + + case PUMP: + Pump[k].pumpCurve = (int)x[0]; + Link[j].hasFlapGate = FALSE; + Pump[k].initSetting = x[1]; + Pump[k].yOn = x[2] / UCF(LENGTH); + Pump[k].yOff = x[3] / UCF(LENGTH); + Pump[k].xMin = 0.0; + Pump[k].xMax = 0.0; + break; + + case ORIFICE: + Orifice[k].type = (int)x[0]; + Link[j].offset1 = x[1] / UCF(LENGTH); + Link[j].offset2 = Link[j].offset1; + Orifice[k].cDisch = x[2]; + Link[j].hasFlapGate = (x[3] > 0.0) ? 1 : 0; + Orifice[k].orate = x[4] * 3600.0; + break; + + case WEIR: + Weir[k].type = (int)x[0]; + Link[j].offset1 = x[1] / UCF(LENGTH); + Link[j].offset2 = Link[j].offset1; + Weir[k].cDisch1 = x[2]; + Link[j].hasFlapGate = (x[3] > 0.0) ? 1 : 0; + Weir[k].endCon = x[4]; + Weir[k].cDisch2 = x[5]; + Weir[k].canSurcharge = (int)x[6]; + Weir[k].roadWidth = x[7] / UCF(LENGTH); + Weir[k].roadSurface = (int)x[8]; + Weir[k].cdCurve = (int)x[9]; + break; + + case OUTLET: + Link[j].offset1 = x[0] / UCF(LENGTH); + Link[j].offset2 = Link[j].offset1; + Outlet[k].qCoeff = x[1]; + Outlet[k].qExpon = x[2]; + Outlet[k].qCurve = (int)x[3]; + Link[j].hasFlapGate = (x[4] > 0.0) ? 1 : 0; + Outlet[k].curveType = (int)x[5]; + + xsect_setParams(&Link[j].xsect, DUMMY, NULL, 0.0); + break; + + } +} + +//============================================================================= + +void link_validate(int j) +// +// Input: j = link index +// Output: none +// Purpose: validates a link's properties. +// +{ + int n; + + if ( LinkOffsets == ELEV_OFFSET ) link_convertOffsets(j); + switch ( Link[j].type ) + { + case CONDUIT: conduit_validate(j, Link[j].subIndex); break; + case PUMP: pump_validate(j, Link[j].subIndex); break; + case ORIFICE: orifice_validate(j, Link[j].subIndex); break; + case WEIR: weir_validate(j, Link[j].subIndex); break; + } + + // --- check if crest of regulator opening < invert of downstream node + switch ( Link[j].type ) + { + case ORIFICE: + case WEIR: + case OUTLET: + if ( Node[Link[j].node1].invertElev + Link[j].offset1 < + Node[Link[j].node2].invertElev ) + { + if (RouteModel == DW) + { + Link[j].offset1 = Node[Link[j].node2].invertElev - + Node[Link[j].node1].invertElev; + report_writeWarningMsg(WARN10b, Link[j].ID); + } + else report_writeWarningMsg(WARN10a, Link[j].ID); + } + } + + // --- force max. depth of end nodes to be >= link crown height + // at non-storage nodes + + // --- skip pumps and bottom orifices + if ( Link[j].type == PUMP || + (Link[j].type == ORIFICE && + Orifice[Link[j].subIndex].type == BOTTOM_ORIFICE) ) return; + + // --- extend upstream node's full depth to link's crown elevation + n = Link[j].node1; + if ( Node[n].type != STORAGE || Node[n].surDepth > 0.0 ) + { + Node[n].fullDepth = MAX(Node[n].fullDepth, + Link[j].offset1 + Link[j].xsect.yFull); + } + + // --- do same for downstream node only for conduit links + n = Link[j].node2; + if ( (Node[n].type != STORAGE || Node[n].surDepth > 0.0) && + Link[j].type == CONDUIT ) + { + Node[n].fullDepth = MAX(Node[n].fullDepth, + Link[j].offset2 + Link[j].xsect.yFull); + } +} + +//============================================================================= + +void link_convertOffsets(int j) +// +// Input: j = link index +// Output: none +// Purpose: converts offset elevations to offset heights for a link. +// +{ + double elev; + + elev = Node[Link[j].node1].invertElev; + Link[j].offset1 = link_getOffsetHeight(j, Link[j].offset1, elev); + if ( Link[j].type == CONDUIT ) + { + elev = Node[Link[j].node2].invertElev; + Link[j].offset2 = link_getOffsetHeight(j, Link[j].offset2, elev); + } + else Link[j].offset2 = Link[j].offset1; +} + +//============================================================================= + +double link_getOffsetHeight(int j, double offset, double elev) +// +// Input: j = link index +// offset = link elevation offset (ft) +// elev = node invert elevation (ft) +// Output: returns offset distance above node invert (ft) +// Purpose: finds offset height for one end of a link. +// +{ + if ( offset <= MISSING || Link[j].type == PUMP) return 0.0; + offset -= elev; + if ( offset >= 0.0 ) return offset; + if ( offset >= -MIN_DELTA_Z ) return 0.0; + report_writeWarningMsg(WARN03, Link[j].ID); + return 0.0; +} + +//============================================================================= + +void link_initState(int j) +// +// Input: j = link index +// Output: none +// Purpose: initializes a link's state variables at start of simulation. +// +{ + int p; + + // --- initialize hydraulic state + Link[j].oldFlow = Link[j].q0; + Link[j].newFlow = Link[j].q0; + Link[j].oldDepth = 0.0; + Link[j].newDepth = 0.0; + Link[j].oldVolume = 0.0; + Link[j].newVolume = 0.0; + Link[j].setting = 1.0; + Link[j].targetSetting = 1.0; + Link[j].timeLastSet = StartDate; + Link[j].inletControl = FALSE; + Link[j].normalFlow = FALSE; + if ( Link[j].type == CONDUIT ) conduit_initState(j, Link[j].subIndex); + if ( Link[j].type == PUMP ) pump_initState(j, Link[j].subIndex); + + // --- initialize water quality state + for (p = 0; p < Nobjects[POLLUT]; p++) + { + Link[j].oldQual[p] = 0.0; + Link[j].newQual[p] = 0.0; + Link[j].totalLoad[p] = 0.0; + } +} + +//============================================================================= + +double link_getInflow(int j) +// +// Input: j = link index +// Output: returns link flow rate (cfs) +// Purpose: finds total flow entering a link during current time step. +// +{ + if ( Link[j].setting == 0 ) return 0.0; + switch ( Link[j].type ) + { + case CONDUIT: return conduit_getInflow(j); + case PUMP: return pump_getInflow(j); + case ORIFICE: return orifice_getInflow(j); + case WEIR: return weir_getInflow(j); + case OUTLET: return outlet_getInflow(j); + default: return node_getOutflow(Link[j].node1, j); + } +} + +//============================================================================= + +void link_setOldHydState(int j) +// +// Input: j = link index +// Output: none +// Purpose: replaces link's old hydraulic state values with current ones. +// +{ + int k; + + Link[j].oldDepth = Link[j].newDepth; + Link[j].oldFlow = Link[j].newFlow; + Link[j].oldVolume = Link[j].newVolume; + + if ( Link[j].type == CONDUIT ) + { + k = Link[j].subIndex; + Conduit[k].q1Old = Conduit[k].q1; + Conduit[k].q2Old = Conduit[k].q2; + } +} + +//============================================================================= + +void link_setOldQualState(int j) +// +// Input: j = link index +// Output: none +// Purpose: replaces link's old water quality state values with current ones. +// +{ + int p; + for (p = 0; p < Nobjects[POLLUT]; p++) + { + Link[j].oldQual[p] = Link[j].newQual[p]; + Link[j].newQual[p] = 0.0; + } +} + +//============================================================================= + +void link_setTargetSetting(int j) +// +// Input: j = link index +// Output: none +// Purpose: updates a link's target setting. +// +{ + int k, n1; + if ( Link[j].type == PUMP ) + { + k = Link[j].subIndex; + n1 = Link[j].node1; + Link[j].targetSetting = Link[j].setting; + if ( Pump[k].yOff > 0.0 && + Link[j].setting > 0.0 && + Node[n1].newDepth < Pump[k].yOff ) Link[j].targetSetting = 0.0; + if ( Pump[k].yOn > 0.0 && + Link[j].setting == 0.0 && + Node[n1].newDepth > Pump[k].yOn ) Link[j].targetSetting = 1.0; + } +} + +//============================================================================= + +void link_setSetting(int j, double tstep) +// +// Input: j = link index +// tstep = time step over which setting is adjusted +// Output: none +// Purpose: updates a link's setting as a result of a control action. +// +{ + if ( Link[j].type == ORIFICE ) orifice_setSetting(j, tstep); + else if ( Link[j].type == WEIR ) weir_setSetting(j); + else Link[j].setting = Link[j].targetSetting; +} + +//============================================================================= + +int link_setFlapGate(int j, int n1, int n2, double q) +// +// Input: j = link index +// n1 = index of node on upstream end of link +// n2 = index of node on downstream end of link +// q = signed flow value (value and units don't matter) +// Output: returns TRUE if there is reverse flow through a flap gate +// associated with the link. +// Purpose: based on the sign of the flow, determines if a flap gate +// associated with the link should close or not. +// +{ + int n = -1; + + // --- check for reverse flow through link's flap gate + if ( Link[j].hasFlapGate ) + { + if ( q * (double)Link[j].direction < 0.0 ) return TRUE; + } + + // --- check for Outfall with flap gate node on inflow end of link + if ( q < 0.0 ) n = n2; + if ( q > 0.0 ) n = n1; + if ( n >= 0 && + Node[n].type == OUTFALL && + Outfall[Node[n].subIndex].hasFlapGate ) return TRUE; + return FALSE; +} + +//============================================================================= + +void link_getResults(int j, double f, float x[]) +// +// Input: j = link index +// f = time weighting factor +// Output: x = array of weighted results +// Purpose: retrieves time-weighted average of old and new results for a link. +// +{ + int p; // pollutant index + double y, // depth + q, // flow + u, // velocity + v, // volume + c; // capacity, setting or concentration + double f1 = 1.0 - f; + + y = f1*Link[j].oldDepth + f*Link[j].newDepth; + q = f1*Link[j].oldFlow + f*Link[j].newFlow; + v = f1*Link[j].oldVolume + f*Link[j].newVolume; + u = link_getVelocity(j, q, y); + c = 0.0; + if (Link[j].type == CONDUIT) + { + if (Link[j].xsect.type != DUMMY) + c = xsect_getAofY(&Link[j].xsect, y) / Link[j].xsect.aFull; + } + else c = Link[j].setting; + + // --- override time weighting for pump flow between on/off states + if (Link[j].type == PUMP && Link[j].oldFlow*Link[j].newFlow == 0.0) + { + if ( f >= f1 ) q = Link[j].newFlow; + else q = Link[j].oldFlow; + } + + y *= UCF(LENGTH); + v *= UCF(VOLUME); + q *= UCF(FLOW) * (double)Link[j].direction; + u *= UCF(LENGTH) * (double)Link[j].direction; + x[LINK_DEPTH] = (float)y; + x[LINK_FLOW] = (float)q; + x[LINK_VELOCITY] = (float)u; + x[LINK_VOLUME] = (float)v; + x[LINK_CAPACITY] = (float)c; + + if ( !IgnoreQuality ) for (p = 0; p < Nobjects[POLLUT]; p++) + { + c = f1*Link[j].oldQual[p] + f*Link[j].newQual[p]; + x[LINK_QUAL+p] = (float)c; + } +} + +//============================================================================= + +void link_setOutfallDepth(int j) +// +// Input: j = link index +// Output: none +// Purpose: sets depth at outfall node connected to link j. +// +{ + int k; // conduit index + int n; // outfall node index + double z; // invert offset height (ft) + double q; // flow rate (cfs) + double yCrit = 0.0; // critical flow depth (ft) + double yNorm = 0.0; // normal flow depth (ft) + + // --- find which end node of link is an outfall + if ( Node[Link[j].node2].type == OUTFALL ) + { + n = Link[j].node2; + z = Link[j].offset2; + } + else if ( Node[Link[j].node1].type == OUTFALL ) + { + n = Link[j].node1; + z = Link[j].offset1; + } + else return; + + // --- find both normal & critical depth for current flow + if ( Link[j].type == CONDUIT ) + { + k = Link[j].subIndex; + q = fabs(Link[j].newFlow / Conduit[k].barrels); + yNorm = link_getYnorm(j, q); + yCrit = link_getYcrit(j, q); + } + + // --- set new depth at node + node_setOutletDepth(n, yNorm, yCrit, z); +} + +//============================================================================= + +double link_getYcrit(int j, double q) +// +// Input: j = link index +// q = link flow rate (cfs) +// Output: returns critical depth (ft) +// Purpose: computes critical depth for given flow rate. +// +{ + return xsect_getYcrit(&Link[j].xsect, q); +} + +//============================================================================= + +double link_getYnorm(int j, double q) +// +// Input: j = link index +// q = link flow rate (cfs) +// Output: returns normal depth (ft) +// Purpose: computes normal depth for given flow rate. +// +{ + int k; + double s, a, y; + + if ( Link[j].type != CONDUIT ) return 0.0; + if ( Link[j].xsect.type == DUMMY ) return 0.0; + q = fabs(q); + k = Link[j].subIndex; + if ( q > Conduit[k].qMax ) q = Conduit[k].qMax; + if ( q <= 0.0 ) return 0.0; + s = q / Conduit[k].beta; + a = xsect_getAofS(&Link[j].xsect, s); + y = xsect_getYofA(&Link[j].xsect, a); + return y; +} + +//============================================================================= + +double link_getLength(int j) +// +// Input: j = link index +// Output: returns length (ft) +// Purpose: finds true length of a link. +// +{ + if ( Link[j].type == CONDUIT ) return conduit_getLength(j); + return 0.0; +} + +//============================================================================= + +double link_getVelocity(int j, double flow, double depth) +// +// Input: j = link index +// flow = link flow rate (cfs) +// depth = link flow depth (ft) +// Output: returns flow velocity (fps) +// Purpose: finds flow velocity given flow and depth. +// +{ + double area; + double veloc = 0.0; + int k; + + if ( depth <= 0.01 ) return 0.0; + if ( Link[j].type == CONDUIT ) + { + k = Link[j].subIndex; + flow /= Conduit[k].barrels; + area = xsect_getAofY(&Link[j].xsect, depth); + if (area > FUDGE ) veloc = flow / area; + } + return veloc; +} + +//============================================================================= + +double link_getFroude(int j, double v, double y) +// +// Input: j = link index +// v = flow velocity (fps) +// y = flow depth (ft) +// Output: returns Froude Number +// Purpose: computes Froude Number for given velocity and flow depth +// +{ + TXsect* xsect = &Link[j].xsect; + + // --- return 0 if link is not a conduit + if ( Link[j].type != CONDUIT ) return 0.0; + + // --- return 0 if link empty or closed conduit is full + if ( y <= FUDGE ) return 0.0; + if ( !xsect_isOpen(xsect->type) && + xsect->yFull - y <= FUDGE ) return 0.0; + + // --- compute hydraulic depth + y = xsect_getAofY(xsect, y) / xsect_getWofY(xsect, y); + + // --- compute Froude No. + return fabs(v) / sqrt(GRAVITY * y); +} + +//============================================================================= + +double link_getPower(int j) +// +// Input: j = link index +// Output: returns power consumed by link in kwatts +// Purpose: computes power consumed by head loss (or head gain) of +// water flowing through a link +// +{ + int n1 = Link[j].node1; + int n2 = Link[j].node2; + double dh = (Node[n1].invertElev + Node[n1].newDepth) - + (Node[n2].invertElev + Node[n2].newDepth); + double q = fabs(Link[j].newFlow); + return fabs(dh) * q / 8.814 * KWperHP; +} + +//============================================================================= + +double link_getLossRate(int j, double q) +// +// Input: j = link index +// q = flow rate (ft3/sec) +// tstep = time step (sec) +// Output: returns uniform loss rate in link (ft3/sec) +// Purpose: computes rate at which flow volume is lost in a link due to +// evaporation and seepage. +// +{ + if ( Link[j].type == CONDUIT ) return conduit_getLossRate(j, q); + else return 0.0; +} + +//============================================================================= + +char link_getFullState(double a1, double a2, double aFull) +// +// Input: a1 = upstream link area (ft2) +// a2 = downstream link area (ft2) +// aFull = area of full conduit +// Output: returns fullness state of a link +// Purpose: determines if a link is upstream, downstream or completely full. +// +{ + if ( a1 >= aFull ) + { + if ( a2 >= aFull ) return ALL_FULL; + else return UP_FULL; + } + if ( a2 >= aFull ) return DN_FULL; + return 0; +} + +//============================================================================= +// C O N D U I T M E T H O D S +//============================================================================= + +int conduit_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = conduit index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads conduit parameters from a tokenzed line of input. +// +{ + int n1, n2; + double x[6]; + char* id; + + // --- check for valid ID and end node IDs + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); // link ID + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); // upstrm. node + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); // dwnstrm. node + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- parse length & Mannings N + if ( !getDouble(tok[3], &x[0]) ) + return error_setInpError(ERR_NUMBER, tok[3]); + if ( !getDouble(tok[4], &x[1]) ) + return error_setInpError(ERR_NUMBER, tok[4]); + + // --- parse offsets + if ( LinkOffsets == ELEV_OFFSET && *tok[5] == '*' ) x[2] = MISSING; + else if ( !getDouble(tok[5], &x[2]) ) + return error_setInpError(ERR_NUMBER, tok[5]); + if ( LinkOffsets == ELEV_OFFSET && *tok[6] == '*' ) x[3] = MISSING; + else if ( !getDouble(tok[6], &x[3]) ) + return error_setInpError(ERR_NUMBER, tok[6]); + + // --- parse optional parameters + x[4] = 0.0; // init. flow + if ( ntoks >= 8 ) + { + if ( !getDouble(tok[7], &x[4]) ) + return error_setInpError(ERR_NUMBER, tok[7]); + } + x[5] = 0.0; + if ( ntoks >= 9 ) + { + if ( !getDouble(tok[8], &x[5]) ) + return error_setInpError(ERR_NUMBER, tok[8]); + } + + // --- add parameters to data base + Link[j].ID = id; + link_setParams(j, CONDUIT, n1, n2, k, x); + return 0; +} + +//============================================================================= + +void conduit_validate(int j, int k) +// +// Input: j = link index +// k = conduit index +// Output: none +// Purpose: validates a conduit's properties. +// +{ + double aa; + double lengthFactor, roughness, slope; + + // --- a storage node cannot have a dummy outflow link + if ( Link[j].xsect.type == DUMMY && RouteModel == DW ) + { + if ( Node[Link[j].node1].type == STORAGE ) + { + report_writeErrorMsg(ERR_DUMMY_LINK, Node[Link[j].node1].ID); + return; + } + } + + // --- if custom xsection, then set its parameters + if ( Link[j].xsect.type == CUSTOM ) + xsect_setCustomXsectParams(&Link[j].xsect); + + // --- if irreg. xsection, assign transect roughness to conduit + if ( Link[j].xsect.type == IRREGULAR ) + { + xsect_setIrregXsectParams(&Link[j].xsect); + Conduit[k].roughness = Transect[Link[j].xsect.transect].roughness; + } + + // --- if street xsection, then set its parameters + if (Link[j].xsect.type == STREET_XSECT) + { + xsect_setStreetXsectParams(&Link[j].xsect); + Conduit[k].roughness = Street[Link[j].xsect.transect].roughness; + } + + // --- if force main xsection, adjust units on D-W roughness height + if ( Link[j].xsect.type == FORCE_MAIN ) + { + if ( ForceMainEqn == D_W ) Link[j].xsect.rBot /= UCF(RAINDEPTH); + if ( Link[j].xsect.rBot <= 0.0 ) + report_writeErrorMsg(ERR_XSECT, Link[j].ID); + } + + // --- check for valid length & roughness + if ( Conduit[k].length <= 0.0 ) + report_writeErrorMsg(ERR_LENGTH, Link[j].ID); + if ( Conduit[k].roughness <= 0.0 ) + report_writeErrorMsg(ERR_ROUGHNESS, Link[j].ID); + if ( Conduit[k].barrels <= 0 ) + report_writeErrorMsg(ERR_BARRELS, Link[j].ID); + + // --- check for valid xsection + if ( Link[j].xsect.type != DUMMY ) + { + if ( Link[j].xsect.type < 0 ) + report_writeErrorMsg(ERR_NO_XSECT, Link[j].ID); + else if ( Link[j].xsect.aFull <= 0.0 ) + report_writeErrorMsg(ERR_XSECT, Link[j].ID); + } + if ( ErrorCode ) return; + + // --- check for negative offsets + if ( Link[j].offset1 < 0.0 ) + { + report_writeWarningMsg(WARN03, Link[j].ID); + Link[j].offset1 = 0.0; + } + if ( Link[j].offset2 < 0.0 ) + { + report_writeWarningMsg(WARN03, Link[j].ID); + Link[j].offset2 = 0.0; + } + + // --- adjust conduit offsets for partly filled circular xsection + if ( Link[j].xsect.type == FILLED_CIRCULAR ) + { + Link[j].offset1 += Link[j].xsect.yBot; + Link[j].offset2 += Link[j].xsect.yBot; + } + + // --- compute conduit slope + slope = conduit_getSlope(j); + Conduit[k].slope = slope; + + // --- reverse orientation of conduit if using dynamic wave routing + // and slope is negative + if ( RouteModel == DW && + slope < 0.0 && + Link[j].xsect.type != DUMMY ) + { + conduit_reverse(j, k); + } + + // --- get equivalent Manning roughness for Force Mains + // for use when pipe is partly full + roughness = Conduit[k].roughness; + if ( RouteModel == DW && Link[j].xsect.type == FORCE_MAIN ) + { + roughness = forcemain_getEquivN(j, k); + } + + // --- adjust roughness for meandering natural channels + if ( Link[j].xsect.type == IRREGULAR ) + { + lengthFactor = Transect[Link[j].xsect.transect].lengthFactor; + roughness *= sqrt(lengthFactor); + } + + // --- lengthen conduit if lengthening option is in effect + lengthFactor = 1.0; + if ( RouteModel == DW && + LengtheningStep > 0.0 && + Link[j].xsect.type != DUMMY ) + { + lengthFactor = conduit_getLengthFactor(j, k, roughness); + } + + if ( lengthFactor != 1.0 ) + { + Conduit[k].modLength = lengthFactor * conduit_getLength(j); + slope /= lengthFactor; + roughness = roughness / sqrt(lengthFactor); + } + + // --- compute roughness factor used when computing friction + // slope term in Dynamic Wave flow routing + + // --- special case for non-Manning Force Mains + // (roughness factor for full flow is saved in xsect.sBot) + if ( RouteModel == DW && Link[j].xsect.type == FORCE_MAIN ) + { + Link[j].xsect.sBot = + forcemain_getRoughFactor(j, lengthFactor); + } + Conduit[k].roughFactor = GRAVITY * SQR(roughness/PHI); + + // --- compute full flow through cross section + if ( Link[j].xsect.type == DUMMY ) Conduit[k].beta = 0.0; + else Conduit[k].beta = PHI * sqrt(fabs(slope)) / roughness; + Link[j].qFull = Link[j].xsect.sFull * Conduit[k].beta; + Conduit[k].qMax = Link[j].xsect.sMax * Conduit[k].beta; + + // --- see if flow is supercritical most of time + // by comparing normal & critical velocities. + // (factor of 0.3 is for circular pipe 95% full) + // NOTE: this factor was used in the past for a modified version of + // Kinematic Wave routing but is now deprecated. + aa = Conduit[k].beta / sqrt(32.2) * + pow(Link[j].xsect.yFull, 0.1666667) * 0.3; + if ( aa >= 1.0 ) Conduit[k].superCritical = TRUE; + else Conduit[k].superCritical = FALSE; + + // --- set value of hasLosses flag + if ( Link[j].cLossInlet == 0.0 && + Link[j].cLossOutlet == 0.0 && + Link[j].cLossAvg == 0.0 + ) Conduit[k].hasLosses = FALSE; + else Conduit[k].hasLosses = TRUE; +} + +//============================================================================= + +void conduit_reverse(int j, int k) +// +// Input: j = link index +// k = conduit index +// Output: none +// Purpose: reverses direction of a conduit +// +{ + int i; + double z; + double cLoss; + + // --- reverse end nodes + i = Link[j].node1; + Link[j].node1 = Link[j].node2; + Link[j].node2 = i; + + // --- reverse node offsets + z = Link[j].offset1; + Link[j].offset1 = Link[j].offset2; + Link[j].offset2 = z; + + // --- reverse loss coeffs. + cLoss = Link[j].cLossInlet; + Link[j].cLossInlet = Link[j].cLossOutlet; + Link[j].cLossOutlet = cLoss; + + // --- reverse direction & slope + Conduit[k].slope = -Conduit[k].slope; + Link[j].direction *= (signed char)-1; + + // --- reverse initial flow value + Link[j].q0 = -Link[j].q0; +} + +//============================================================================= + +double conduit_getLength(int j) +// +// Input: j = link index +// Output: returns conduit's length (ft) +// Purpose: finds true length of a conduit. +// +// Note: for irregular natural channels, user inputs length of main +// channel (for FEMA purposes) but program should use length +// associated with entire flood plain. Transect.lengthFactor +// is the ratio of these two lengths. +// +{ + int k = Link[j].subIndex; + int t; + if ( Link[j].xsect.type != IRREGULAR ) return Conduit[k].length; + t = Link[j].xsect.transect; + if ( t < 0 || t >= Nobjects[TRANSECT] ) return Conduit[k].length; + return Conduit[k].length / Transect[t].lengthFactor; +} + +//============================================================================= + +double conduit_getLengthFactor(int j, int k, double roughness) +// +// Input: j = link index +// k = conduit index +// roughness = conduit Manning's n +// Output: returns factor by which a conduit should be lengthened +// Purpose: computes amount of conduit lengthing to improve numerical stability. +// +// The following form of the Courant criterion is used: +// L = t * v * (1 + Fr) / Fr +// where L = conduit length, t = time step, v = velocity, & Fr = Froude No. +// After substituting Fr = v / sqrt(gy), where y = flow depth, we get: +// L = t * ( sqrt(gy) + v ) +// +{ + double ratio; + double yFull; + double vFull; + double tStep; + + // --- evaluate flow depth and velocity at full normal flow condition + yFull = Link[j].xsect.yFull; + if ( xsect_isOpen(Link[j].xsect.type) ) + { + yFull = Link[j].xsect.aFull / xsect_getWofY(&Link[j].xsect, yFull); + } + vFull = PHI / roughness * Link[j].xsect.sFull * + sqrt(fabs(Conduit[k].slope)) / Link[j].xsect.aFull; + + // --- determine ratio of Courant length to actual length + if ( LengtheningStep == 0.0 ) tStep = RouteStep; + else tStep = MIN(RouteStep, LengtheningStep); + ratio = (sqrt(GRAVITY*yFull) + vFull) * tStep / conduit_getLength(j); + + // --- return max. of 1.0 and ratio + if ( ratio > 1.0 ) return ratio; + else return 1.0; +} + +//============================================================================= + +double conduit_getSlope(int j) +// +// Input: j = link index +// Output: returns conduit slope +// Purpose: computes conduit slope. +// +{ + double elev1, elev2, delta, slope; + double length = conduit_getLength(j); + + // --- check that elevation drop > minimum allowable drop + elev1 = Link[j].offset1 + Node[Link[j].node1].invertElev; + elev2 = Link[j].offset2 + Node[Link[j].node2].invertElev; + delta = fabs(elev1 - elev2); + if ( delta < MIN_DELTA_Z ) + { + report_writeWarningMsg(WARN04, Link[j].ID); + delta = MIN_DELTA_Z; + } + + // --- elevation drop cannot exceed conduit length + if ( delta >= length ) + { + report_writeWarningMsg(WARN08, Link[j].ID); + slope = delta / length; + } + + // --- slope = elev. drop / horizontal distance + else slope = delta / sqrt(SQR(length) - SQR(delta)); + + // -- check that slope exceeds minimum allowable slope + if ( MinSlope > 0.0 && slope < MinSlope ) + { + report_writeWarningMsg(WARN05, Link[j].ID); + slope = MinSlope; + // keep min. slope positive for SF or KW routing + if (RouteModel == SF || RouteModel == KW) return slope; + } + + // --- change sign for adverse slope + if ( elev1 < elev2 ) slope = -slope; + return slope; +} + +//============================================================================= + +void conduit_initState(int j, int k) +// +// Input: j = link index +// k = conduit index +// Output: none +// Purpose: sets initial conduit depth to normal depth of initial flow +// +{ + Link[j].newDepth = link_getYnorm(j, Link[j].q0 / Conduit[k].barrels); + Link[j].oldDepth = Link[j].newDepth; + Conduit[k].evapLossRate = 0.0; + Conduit[k].seepLossRate = 0.0; +} + +//============================================================================= + +double conduit_getInflow(int j) +// +// Input: j = link index +// Output: returns flow in link (cfs) +// Purpose: finds inflow to conduit from upstream node. +// +{ + double qIn = node_getOutflow(Link[j].node1, j); + if ( Link[j].qLimit > 0.0 ) qIn = MIN(qIn, Link[j].qLimit); + return qIn; +} + +//============================================================================= + +double conduit_getLossRate(int j, double q) +// +// Input: j = link index +// q = current link flow rate (cfs) +// Output: returns rate of evaporation & seepage losses (ft3/sec) +// Purpose: computes volumetric rate of water evaporation & seepage +// from a conduit (per barrel). +// +{ + TXsect *xsect; + double depth = 0.5 * (Link[j].oldDepth + Link[j].newDepth); + double length; + double topWidth; + double evapLossRate = 0.0, + seepLossRate = 0.0, + totalLossRate = 0.0; + + if ( depth > FUDGE ) + { + xsect = &Link[j].xsect; + length = conduit_getLength(j); + + // --- find evaporation rate for open conduits + if ( xsect_isOpen(xsect->type) && Evap.rate > 0.0 ) + { + topWidth = xsect_getWofY(xsect, depth); + evapLossRate = topWidth * length * Evap.rate; + } + + // --- compute seepage loss rate + if ( Link[j].seepRate > 0.0 ) + { + // limit depth to depth at max width + if ( depth >= xsect->ywMax ) depth = xsect->ywMax; + + // compute seepage loss rate across length of conduit + seepLossRate = Link[j].seepRate * xsect_getWofY(xsect, depth) * + length; + seepLossRate *= Adjust.hydconFactor; + } + + // --- compute total loss rate + totalLossRate = evapLossRate + seepLossRate; + + // --- total loss rate cannot exceed flow rate + q = ABS(q); + if (totalLossRate > q) + { + evapLossRate = evapLossRate * q / totalLossRate; + seepLossRate = seepLossRate * q / totalLossRate; + totalLossRate = q; + } + } + + Conduit[Link[j].subIndex].evapLossRate = evapLossRate; + Conduit[Link[j].subIndex].seepLossRate = seepLossRate; + return totalLossRate; +} + + +//============================================================================= +// P U M P M E T H O D S +//============================================================================= + +int pump_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = pump index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads pump parameters from a tokenized line of input. +// +{ + int m; + int n1, n2; + double x[4]; + char* id; + + // --- check for valid ID and end node IDs + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- parse curve name + x[0] = -1.; + if ( ntoks >= 4 ) + { + if ( !strcomp(tok[3],"*") ) + { + m = project_findObject(CURVE, tok[3]); + if ( m < 0 ) return error_setInpError(ERR_NAME, tok[3]); + x[0] = m; + } + } + + // --- parse init. status if present + x[1] = 1.0; + if ( ntoks >= 5 ) + { + m = findmatch(tok[4], OffOnWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); + x[1] = m; + } + + // --- parse startup/shutoff depths if present + x[2] = 0.0; + if ( ntoks >= 6 ) + { + if ( !getDouble(tok[5], &x[2]) || x[2] < 0.0) + return error_setInpError(ERR_NUMBER, tok[5]); + } + x[3] = 0.0; + if ( ntoks >= 7 ) + { + if ( !getDouble(tok[6], &x[3]) || x[3] < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[6]); + } + + // --- add parameters to pump object + Link[j].ID = id; + link_setParams(j, PUMP, n1, n2, k, x); + return 0; +} + +//============================================================================= + +void pump_validate(int j, int k) +// +// Input: j = link index +// k = pump index +// Output: none +// Purpose: validates a pump's properties +// +{ + int m, n1; + double x, y; + + Link[j].xsect.yFull = 0.0; + + // --- check for valid curve type + m = Pump[k].pumpCurve; + if ( m < 0 ) + { + Pump[k].type = IDEAL_PUMP; + } + else + { + if ( Curve[m].curveType < PUMP1_CURVE || + Curve[m].curveType > PUMP5_CURVE ) + report_writeErrorMsg(ERR_NO_CURVE, Link[j].ID); + + // --- store pump curve type with pump's parameters + else + { + Pump[k].type = Curve[m].curveType - PUMP1_CURVE; + if ( table_getFirstEntry(&Curve[m], &x, &y) ) + { + Link[j].qFull = y; + Pump[k].xMin = x; + Pump[k].xMax = x; + while ( table_getNextEntry(&Curve[m], &x, &y) ) + { + Link[j].qFull = MAX(y, Link[j].qFull); + Pump[k].xMax = x; + } + } + Link[j].qFull /= UCF(FLOW); + } + } + + // --- check that shutoff depth < startup depth + if ( Pump[k].yOn > 0.0 && Pump[k].yOn <= Pump[k].yOff ) + report_writeErrorMsg(ERR_PUMP_LIMITS, Link[j].ID); + + // --- assign wet well volume to inlet node of Type 1 pump + if ( Pump[k].type == TYPE1_PUMP ) + { + n1 = Link[j].node1; + if ( Node[n1].type != STORAGE ) + Node[n1].fullVolume = MAX(Node[n1].fullVolume, + Pump[k].xMax / UCF(VOLUME)); + } + +} + +//============================================================================= + +void pump_initState(int j, int k) +// +// Input: j = link index +// k = pump index +// Output: none +// Purpose: initializes pump conditions at start of a simulation +// +{ + Link[j].setting = Pump[k].initSetting; + Link[j].targetSetting = Pump[k].initSetting; +} + +//============================================================================= + +double pump_getInflow(int j) +// +// Input: j = link index +// Output: returns pump flow (cfs) +// Purpose: finds flow produced by a pump. +// +{ + int k, m; + int n1, n2; + double vol, depth, head; + double qIn, qIn1, dh = 0.001; + double s = 1.0; // speed setting + + k = Link[j].subIndex; + m = Pump[k].pumpCurve; + n1 = Link[j].node1; + n2 = Link[j].node2; + + // --- no flow if setting is closed + Link[j].flowClass = NO; + Link[j].setting = Link[j].targetSetting; + if ( Link[j].setting == 0.0 ) return 0.0; + + // --- pump flow = node inflow for IDEAL_PUMP + if ( Pump[k].type == IDEAL_PUMP ) + qIn = Node[n1].inflow + Node[n1].overflow; + + // --- pumping rate depends on pump curve type + else switch(Curve[m].curveType) + { + case PUMP1_CURVE: + vol = Node[n1].newVolume * UCF(VOLUME); + qIn = table_intervalLookup(&Curve[m], vol) / UCF(FLOW); + + // --- check if off of pump curve + if ( vol < Pump[k].xMin || vol > Pump[k].xMax ) + Link[j].flowClass = YES; + break; + + case PUMP2_CURVE: + depth = Node[n1].newDepth * UCF(LENGTH); + qIn = table_intervalLookup(&Curve[m], depth) / UCF(FLOW); + + // --- check if off of pump curve + if ( depth < Pump[k].xMin || depth > Pump[k].xMax ) + Link[j].flowClass = YES; + break; + + case PUMP3_CURVE: + case PUMP5_CURVE: + if (Curve[m].curveType == PUMP5_CURVE) s = Link[j].setting; + head = ((Node[n2].newDepth + Node[n2].invertElev) - + (Node[n1].newDepth + Node[n1].invertElev)) / s / s; + head = MAX(head, 0.0) * UCF(LENGTH); + qIn = table_lookup(&Curve[m], head) / UCF(FLOW); + + // --- compute dQ/dh (slope of pump curve) and + // reverse sign since flow decreases with increasing head + Link[j].dqdh = -table_getSlope(&Curve[m], head) * + UCF(LENGTH) / UCF(FLOW) / s; + + // --- check if off of pump curve + if (head < Pump[k].xMin || head > Pump[k].xMax) + Link[j].flowClass = YES; + break; + + case PUMP4_CURVE: + depth = Node[n1].newDepth; + qIn = table_lookup(&Curve[m], depth*UCF(LENGTH)) / UCF(FLOW); + + // --- compute dQ/dh (slope of pump curve) + qIn1 = table_lookup(&Curve[m], (depth+dh)*UCF(LENGTH)) / UCF(FLOW); + Link[j].dqdh = (qIn1 - qIn) / dh; + + // --- check if off of pump curve + depth *= UCF(LENGTH); + if ( depth < Pump[k].xMin ) Link[j].flowClass = DN_DRY; + if ( depth > Pump[k].xMax ) Link[j].flowClass = UP_DRY; + break; + + default: qIn = 0.0; + } + + // --- do not allow reverse flow through pump + if ( qIn < 0.0 ) qIn = 0.0; + return qIn * Link[j].setting; +} + + +//============================================================================= +// O R I F I C E M E T H O D S +//============================================================================= + +int orifice_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = orifice index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads orifice parameters from a tokenized line of input. +// +{ + int m; + int n1, n2; + double x[5]; + char* id; + + // --- check for valid ID and end node IDs + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- parse orifice parameters + m = findmatch(tok[3], OrificeTypeWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); + x[0] = m; // type + if ( LinkOffsets == ELEV_OFFSET && *tok[4] == '*' ) x[1] = MISSING; + else if ( ! getDouble(tok[4], &x[1]) ) // crest height + return error_setInpError(ERR_NUMBER, tok[4]); + if ( ! getDouble(tok[5], &x[2]) || x[2] < 0.0 ) // cDisch + return error_setInpError(ERR_NUMBER, tok[5]); + x[3] = 0.0; + if ( ntoks >= 7 ) + { + m = findmatch(tok[6], NoYesWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); + x[3] = m; // flap gate + } + x[4] = 0.0; + if ( ntoks >= 8 ) + { + if ( ! getDouble(tok[7], &x[4]) || x[4] < 0.0 ) // orate + return error_setInpError(ERR_NUMBER, tok[7]); + } + + // --- add parameters to orifice object + Link[j].ID = id; + link_setParams(j, ORIFICE, n1, n2, k, x); + return 0; +} + +//============================================================================= + +void orifice_validate(int j, int k) +// +// Input: j = link index +// k = orifice index +// Output: none +// Purpose: validates an orifice's properties +// +{ + int err = 0; + + // --- check for valid xsection + if ( Link[j].xsect.type != RECT_CLOSED + && Link[j].xsect.type != CIRCULAR ) err = ERR_REGULATOR_SHAPE; + if ( err > 0 ) + { + report_writeErrorMsg(err, Link[j].ID); + return; + } + + // --- check for negative offset + if ( Link[j].offset1 < 0.0 ) Link[j].offset1 = 0.0; + + // --- compute partial flow adjustment + orifice_setSetting(j, 0.0); + + // --- compute an equivalent length + Orifice[k].length = 2.0 * RouteStep * sqrt(GRAVITY * Link[j].xsect.yFull); + Orifice[k].length = MAX(200.0, Orifice[k].length); + Orifice[k].surfArea = 0.0; +} + +//============================================================================= + +void orifice_setSetting(int j, double tstep) +// +// Input: j = link index +// tstep = time step over which setting is adjusted (sec) +// Output: none +// Purpose: updates an orifice's setting as a result of a control action. +// +{ + int k = Link[j].subIndex; + double delta, step; + double h, f; + + // --- case where adjustment rate is instantaneous + if ( Orifice[k].orate == 0.0 || tstep == 0.0) + Link[j].setting = Link[j].targetSetting; + + // --- case where orifice setting depends on time step + else + { + delta = Link[j].targetSetting - Link[j].setting; + step = tstep / Orifice[k].orate; + if ( step + 0.001 >= fabs(delta) ) + Link[j].setting = Link[j].targetSetting; + else Link[j].setting += SGN(delta) * step; + } + + // --- find effective orifice discharge coeff. + h = Link[j].setting * Link[j].xsect.yFull; + f = xsect_getAofY(&Link[j].xsect, h) * sqrt(2.0 * GRAVITY); + Orifice[k].cOrif = Orifice[k].cDisch * f; + + // --- find equiv. discharge coeff. for when weir flow occurs + Orifice[k].cWeir = orifice_getWeirCoeff(j, k, h) * f; +} + +//============================================================================= + +double orifice_getWeirCoeff(int j, int k, double h) +// +// Input: j = link index +// k = orifice index +// h = height of orifice opening (ft) +// Output: returns a discharge coefficient (ft^1/2) +// Purpose: computes the discharge coefficient for an orifice +// at the critical depth where weir flow begins. +// +{ + double w, aOverL; + + // --- this is for bottom orifices + if ( Orifice[k].type == BOTTOM_ORIFICE ) + { + // --- find critical height above opening where orifice flow + // turns into weir flow. It equals (Co/Cw)*(Area/Length) + // where Co is the orifice coeff., Cw is the weir coeff/sqrt(2g), + // Area is the area of the opening, and Length = circumference + // of the opening. For a basic sharp crested weir, Cw = 0.414. + if (Link[j].xsect.type == CIRCULAR) aOverL = h / 4.0; + else + { + w = Link[j].xsect.wMax; + aOverL = (h*w) / (2.0*(h+w)); + } + h = Orifice[k].cDisch / 0.414 * aOverL; + Orifice[k].hCrit = h; + } + + // --- this is for side orifices + else + { + // --- critical height is simply height of opening + Orifice[k].hCrit = h; + + // --- head on orifice is distance to center line + h = h / 2.0; + } + + // --- return a coefficient for the critical depth + return Orifice[k].cDisch * sqrt(h); +} + +//============================================================================= + +double orifice_getInflow(int j) +// +// Input: j = link index +// Output: returns orifice flow rate (cfs) +// Purpose: finds the flow through an orifice. +// +{ + int k, n1, n2; + double head, h1, h2, y1, dir; + double f; + double hcrest = 0.0; + double hcrown = 0.0; + double hmidpt; + double q, ratio; + + // --- get indexes of end nodes and link's orifice + n1 = Link[j].node1; + n2 = Link[j].node2; + k = Link[j].subIndex; + + // --- find heads at upstream & downstream nodes + if ( RouteModel == DW ) + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + } + else + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n1].invertElev; + } + dir = (h1 >= h2) ? +1.0 : -1.0; + + // --- exchange h1 and h2 for reverse flow + y1 = Node[n1].newDepth; + if ( dir < 0.0 ) + { + head = h1; + h1 = h2; + h2 = head; + y1 = Node[n2].newDepth; + } + + // --- orifice is a bottom orifice (oriented in horizontal plane) + if ( Orifice[k].type == BOTTOM_ORIFICE ) + { + // --- compute crest elevation + hcrest = Node[n1].invertElev + Link[j].offset1; + + // --- compute head on orifice + if (h1 < hcrest) head = 0.0; + else if (h2 > hcrest) head = h1 - h2; + else head = h1 - hcrest; + + // --- find fraction of critical height for which weir flow occurs + f = head / Orifice[k].hCrit; + f = MIN(f, 1.0); + } + + // --- otherwise orifice is a side orifice (oriented in vertical plane) + else + { + // --- compute elevations of orifice crest and crown + hcrest = Node[n1].invertElev + Link[j].offset1; + hcrown = hcrest + Link[j].xsect.yFull * Link[j].setting; + hmidpt = (hcrest + hcrown) / 2.0; + + // --- compute degree of inlet submergence + if ( h1 < hcrown && hcrown > hcrest ) + f = (h1 - hcrest) / (hcrown - hcrest); + else f = 1.0; + + // --- compute head on orifice + if ( f < 1.0 ) head = h1 - hcrest; + else if ( h2 < hmidpt ) head = h1 - hmidpt; + else head = h1 - h2; + } + + // --- return if head is negligible or flap gate closed + if ( head <= FUDGE || y1 <= FUDGE || + link_setFlapGate(j, n1, n2, dir) ) + { + Link[j].newDepth = 0.0; + Link[j].flowClass = DRY; + Orifice[k].surfArea = FUDGE * Orifice[k].length; + Link[j].dqdh = 0.0; + return 0.0; + } + + // --- determine flow class + Link[j].flowClass = SUBCRITICAL; + if ( hcrest > h2 ) + { + if ( dir == 1.0 ) Link[j].flowClass = DN_CRITICAL; + else Link[j].flowClass = UP_CRITICAL; + } + + // --- compute flow depth and surface area + y1 = Link[j].xsect.yFull * Link[j].setting; + if ( Orifice[k].type == SIDE_ORIFICE ) + { + Link[j].newDepth = y1 * f; + Orifice[k].surfArea = + xsect_getWofY(&Link[j].xsect, Link[j].newDepth) * + Orifice[k].length; + } + else + { + Link[j].newDepth = y1; + Orifice[k].surfArea = xsect_getAofY(&Link[j].xsect, y1); + } + + // --- find flow through the orifice + q = dir * orifice_getFlow(j, k, head, f, Link[j].hasFlapGate); + + // --- apply Villemonte eqn. to correct for submergence + if ( f < 1.0 && h2 > hcrest ) + { + ratio = (h2 - hcrest) / (h1 - hcrest); + q *= pow( (1.0 - pow(ratio, 1.5)), 0.385); + } + return q; +} + +//============================================================================= + +double orifice_getFlow(int j, int k, double head, double f, int hasFlapGate) +// +// Input: j = link index +// k = orifice index +// head = head across orifice +// f = fraction of critical depth filled +// hasFlapGate = flap gate indicator +// Output: returns flow through an orifice +// Purpose: computes flow through an orifice as a function of head. +// +{ + double area, q; + double veloc, hLoss; + + // --- case where orifice is closed + if ( head == 0.0 || f <= 0.0 ) + { + Link[j].dqdh = 0.0; + return 0.0; + } + + // --- case where inlet depth is below critical depth; + // orifice behaves as a weir + else if ( f < 1.0 ) + { + q = Orifice[k].cWeir * pow(f, 1.5); + Link[j].dqdh = 1.5 * q / (f * Orifice[k].hCrit); + } + + // --- case where normal orifice flow applies + else + { + q = Orifice[k].cOrif * sqrt(head); + Link[j].dqdh = q / (2.0 * head); + } + + // --- apply ARMCO adjustment for headloss from flap gate + if ( hasFlapGate ) + { + // --- compute velocity for current orifice flow + area = xsect_getAofY(&Link[j].xsect, + Link[j].setting * Link[j].xsect.yFull); + veloc = q / area; + + // --- compute head loss from gate + hLoss = (4.0 / GRAVITY) * veloc * veloc * + exp(-1.15 * veloc / sqrt(head) ); + + // --- update head (for orifice flow) + // or critical depth fraction (for weir flow) + if ( f < 1.0 ) + { + f = f - hLoss/Orifice[k].hCrit; + if ( f < 0.0 ) f = 0.0; + } + else + { + head = head - hLoss; + if ( head < 0.0 ) head = 0.0; + } + + // --- make recursive call to this function, with hasFlapGate + // set to false, to find flow values at adjusted head value + q = orifice_getFlow(j, k, head, f, FALSE); + } + return q; +} + +//============================================================================= +// W E I R M E T H O D S +//============================================================================= + +int weir_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = weir index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads weir parameters from a tokenized line of input. +// +{ + int m; + int n1, n2; + double x[10]; + char* id; + + // --- check for valid ID and end node IDs + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- parse weir parameters + m = findmatch(tok[3], WeirTypeWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); + x[0] = m; // type + if ( LinkOffsets == ELEV_OFFSET && *tok[4] == '*' ) x[1] = MISSING; + else if ( ! getDouble(tok[4], &x[1]) ) // height + return error_setInpError(ERR_NUMBER, tok[4]); + if ( ! getDouble(tok[5], &x[2]) || x[2] < 0.0 ) // cDisch1 + return error_setInpError(ERR_NUMBER, tok[5]); + x[3] = 0.0; + x[4] = 0.0; + x[5] = 0.0; + x[6] = 1.0; + x[7] = 0.0; + x[8] = 0.0; + x[9] = -1.0; + if ( ntoks >= 7 && *tok[6] != '*' ) + { + m = findmatch(tok[6], NoYesWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); + x[3] = m; // flap gate + } + if ( ntoks >= 8 && *tok[7] != '*' ) + { + if ( ! getDouble(tok[7], &x[4]) || x[4] < 0.0 ) // endCon + return error_setInpError(ERR_NUMBER, tok[7]); + } + if ( ntoks >= 9 && *tok[8] != '*' ) + { + if ( ! getDouble(tok[8], &x[5]) || x[5] < 0.0 ) // cDisch2 + return error_setInpError(ERR_NUMBER, tok[8]); + } + + if ( ntoks >= 10 && *tok[9] != '*' ) + { + m = findmatch(tok[9], NoYesWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[9]); + x[6] = m; // canSurcharge + } + + if ( (m = (int)x[0]) == ROADWAY_WEIR ) + { + if ( ntoks >= 11 ) // road width + { + if ( ! getDouble(tok[10], &x[7]) || x[7] < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[10]); + } + if ( ntoks >= 12 ) // road surface + { + if ( strcomp(tok[11], "PAVED") ) x[8] = 1.0; + else if ( strcomp(tok[11], "GRAVEL") ) x[8] = 2.0; + } + } + + if (ntoks >= 13 && *tok[12] != '*') + { + m = project_findObject(CURVE, tok[12]); // coeff. curve + if (m < 0) return error_setInpError(ERR_NAME, tok[12]); + x[9] = m; + } + + // --- add parameters to weir object + Link[j].ID = id; + link_setParams(j, WEIR, n1, n2, k, x); + return 0; +} + +//============================================================================= + +void weir_validate(int j, int k) +// +// Input: j = link index +// k = weir index +// Output: none +// Purpose: validates a weir's properties +// +{ + int err = 0; + double q, q1, q2, head; + + // --- check for valid cross section + switch ( Weir[k].type) + { + case TRANSVERSE_WEIR: + case SIDEFLOW_WEIR: + case ROADWAY_WEIR: + if ( Link[j].xsect.type != RECT_OPEN ) err = ERR_REGULATOR_SHAPE; + Weir[k].slope = 0.0; + break; + + case VNOTCH_WEIR: + if ( Link[j].xsect.type != TRIANGULAR ) err = ERR_REGULATOR_SHAPE; + else + { + Weir[k].slope = Link[j].xsect.sBot; + } + break; + + case TRAPEZOIDAL_WEIR: + if ( Link[j].xsect.type != TRAPEZOIDAL ) err = ERR_REGULATOR_SHAPE; + else + { + Weir[k].slope = Link[j].xsect.sBot; + } + break; + } + if ( err > 0 ) + { + report_writeErrorMsg(err, Link[j].ID); + return; + } + + // --- check for negative offset + if ( Link[j].offset1 < 0.0 ) Link[j].offset1 = 0.0; + + // --- compute an equivalent length + Weir[k].length = 2.0 * RouteStep * sqrt(GRAVITY * Link[j].xsect.yFull); + Weir[k].length = MAX(200.0, Weir[k].length); + Weir[k].surfArea = 0.0; + + // --- find flow through weir when water level equals weir height + head = Link[j].xsect.yFull; + weir_getFlow(j, k, head, 1.0, FALSE, &q1, &q2); + q = q1 + q2; + + // --- compute equivalent orifice coeff. (for CFS flow units) + head = head / 2.0; // head seen by equivalent orifice + Weir[k].cSurcharge = q / sqrt(head); +} + +//============================================================================= + +void weir_setSetting(int j) +// +// Input: j = link index +// Output: none +// Purpose: updates a weir's setting as a result of a control action. +// +{ + int k = Link[j].subIndex; + double h, q, q1, q2; + + // --- adjust weir setting + Link[j].setting = Link[j].targetSetting; + if ( !Weir[k].canSurcharge ) return; + if ( Weir[k].type == ROADWAY_WEIR ) return; + + // --- find orifice coeff. for surcharged flow + if ( Link[j].setting == 0.0 ) Weir[k].cSurcharge = 0.0; + else + { + // --- find flow through weir when water level equals weir height + h = Link[j].setting * Link[j].xsect.yFull; + weir_getFlow(j, k, h, 1.0, FALSE, &q1, &q2); + q = q1 + q2; + + // --- compute equivalent orifice coeff. (for CFS flow units) + h = h / 2.0; // head seen by equivalent orifice + Weir[k].cSurcharge = q / sqrt(h); + } +} + +//============================================================================= + +double weir_getInflow(int j) +// +// Input: j = link index +// Output: returns weir flow rate (cfs) +// Purpose: finds the flow over a weir. +// +{ + int n1; // index of upstream node + int n2; // index of downstream node + int k; // index of weir + double q1; // flow through central part of weir (cfs) + double q2; // flow through end sections of weir (cfs) + double head; // head on weir (ft) + double h1; // upstrm nodal head (ft) + double h2; // downstrm nodal head (ft) + double hcrest; // head at weir crest (ft) + double hcrown; // head at weir crown (ft) + double y; // water depth in weir (ft) + double dir; // direction multiplier + double ratio; + double weirPower[] = {1.5, // transverse weir + 5./3., // side flow weir + 2.5, // v-notch weir + 1.5}; // trapezoidal weir + + n1 = Link[j].node1; + n2 = Link[j].node2; + k = Link[j].subIndex; + if ( RouteModel == DW ) + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + } + else + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n1].invertElev; + } + dir = (h1 > h2) ? +1.0 : -1.0; + + // --- exchange h1 and h2 for reverse flow + if ( dir < 0.0 ) + { + head = h1; + h1 = h2; + h2 = head; + } + + // --- find head of weir's crest and crown + hcrest = Node[n1].invertElev + Link[j].offset1; + hcrown = hcrest + Link[j].xsect.yFull; + + // --- treat a roadway weir as a special case + if ( Weir[k].type == ROADWAY_WEIR ) + return roadway_getInflow(j, dir, hcrest, h1, h2); + + // --- adjust crest ht. for partially open weir + hcrest += (1.0 - Link[j].setting) * Link[j].xsect.yFull; + + // --- compute head relative to weir crest + head = h1 - hcrest; + + // --- return if head is negligible or flap gate closed + Link[j].dqdh = 0.0; + if ( head <= FUDGE || hcrest >= hcrown || + link_setFlapGate(j, n1, n2, dir) ) + { + Link[j].newDepth = 0.0; + Link[j].flowClass = DRY; + return 0.0; + } + + // --- determine flow class + Link[j].flowClass = SUBCRITICAL; + if ( hcrest > h2 ) + { + if ( dir == 1.0 ) Link[j].flowClass = DN_CRITICAL; + else Link[j].flowClass = UP_CRITICAL; + } + + // --- compute new equivalent surface area + y = Link[j].xsect.yFull - (hcrown - MIN(h1, hcrown)); + Weir[k].surfArea = xsect_getWofY(&Link[j].xsect, y) * Weir[k].length; + + // --- head is above crown + if ( h1 >= hcrown ) + { + // --- use equivalent orifice if weir can surcharge + if ( Weir[k].canSurcharge ) + { + y = (hcrest + hcrown) / 2.0; + if ( h2 < y ) head = h1 - y; + else head = h1 - h2; + y = hcrown - hcrest; + q1 = weir_getOrificeFlow(j, head, y, Weir[k].cSurcharge); + Link[j].newDepth = y; + return dir * q1; + } + + // --- otherwise limit head to height of weir opening + else head = hcrown - hcrest; + } + + // --- use weir eqn. to find flows through central (q1) + // and end sections (q2) of weir + weir_getFlow(j, k, head, dir, Link[j].hasFlapGate, &q1, &q2); + + // --- apply Villemonte eqn. to correct for submergence + if ( h2 > hcrest ) + { + ratio = (h2 - hcrest) / (h1 - hcrest); + q1 *= pow( (1.0 - pow(ratio, weirPower[Weir[k].type])), 0.385); + if ( q2 > 0.0 ) + q2 *= pow( (1.0 - pow(ratio, weirPower[VNOTCH_WEIR])), 0.385); + } + + // --- return total flow through weir + Link[j].newDepth = MIN((h1 - hcrest), Link[j].xsect.yFull); + return dir * (q1 + q2); +} + +//============================================================================= + +void weir_getFlow(int j, int k, double head, double dir, int hasFlapGate, + double* q1, double* q2) +// +// Input: j = link index +// k = weir index +// head = head across weir (ft) +// dir = flow direction indicator +// hasFlapGate = flap gate indicator +// Output: q1 = flow through central portion of weir (cfs) +// q2 = flow through end sections of weir (cfs) +// Purpose: computes flow over weir given head. +// +{ + double length; + double h; + double y; + double hLoss; + double area; + double veloc; + int wType; + int cdCurve = Weir[k].cdCurve; + double cDisch1 = Weir[k].cDisch1; + + // --- q1 = flow through central portion of weir, + // q2 = flow through end sections of trapezoidal weir + *q1 = 0.0; + *q2 = 0.0; + Link[j].dqdh = 0.0; + if ( head <= 0.0 ) return; + + // --- convert weir length & head to original units + length = Link[j].xsect.wMax * UCF(LENGTH); + h = head * UCF(LENGTH); + + // --- lookup tabulated discharge coeff. + if ( cdCurve >= 0 ) cDisch1 = table_lookup(&Curve[cdCurve], h); + + // --- use appropriate formula for weir flow + wType = Weir[k].type; + if ( wType == VNOTCH_WEIR && + Link[j].setting < 1.0 ) wType = TRAPEZOIDAL_WEIR; + switch (wType) + { + case TRANSVERSE_WEIR: + + // --- reduce length when end contractions present + length -= 0.1 * Weir[k].endCon * h; + length = MAX(length, 0.0); + *q1 = cDisch1 * length * pow(h, 1.5); + break; + + case SIDEFLOW_WEIR: + + // --- reduce length when end contractions present + length -= 0.1 * Weir[k].endCon * h; + length = MAX(length, 0.0); + + // --- weir behaves as a transverse weir under reverse flow + if ( dir < 0.0 ) + *q1 = cDisch1 * length * pow(h, 1.5); + else + + // Corrected formula (see Metcalf & Eddy, Inc., + // Wastewater Engineering, McGraw-Hill, 1972 p. 164). + *q1 = cDisch1 * pow(length, 0.83) * pow(h, 1.67); + + break; + + case VNOTCH_WEIR: + *q1 = cDisch1 * Weir[k].slope * pow(h, 2.5); + break; + + case TRAPEZOIDAL_WEIR: + y = (1.0 - Link[j].setting) * Link[j].xsect.yFull; + length = xsect_getWofY(&Link[j].xsect, y) * UCF(LENGTH); + *q1 = cDisch1 * length * pow(h, 1.5); + *q2 = Weir[k].cDisch2 * Weir[k].slope * pow(h, 2.5); + } + + // --- convert CMS flows to CFS + if ( UnitSystem == SI ) + { + *q1 /= M3perFT3; + *q2 /= M3perFT3; + } + + // --- apply ARMCO adjustment for headloss from flap gate + if ( hasFlapGate ) + { + // --- compute flow area & velocity for current weir flow + area = weir_getOpenArea(j, head); + if ( area > TINY ) + { + veloc = (*q1 + *q2) / area; + + // --- compute headloss and subtract from original head + hLoss = (4.0 / GRAVITY) * veloc * veloc * + exp(-1.15 * veloc / sqrt(head) ); + head = head - hLoss; + if ( head < 0.0 ) head = 0.0; + + // --- make recursive call to this function, with hasFlapGate + // set to false, to find flow values at adjusted head value + weir_getFlow(j, k, head, dir, FALSE, q1, q2); + } + } + Link[j].dqdh = weir_getdqdh(k, dir, head, *q1, *q2); +} + +//============================================================================= + +double weir_getOrificeFlow(int j, double head, double y, double cOrif) +// +// Input: j = link index +// head = head across weir (ft) +// y = height of upstream water level above weir crest (ft) +// cOrif = orifice flow coefficient +// Output: returns flow through weir +// Purpose: finds flow through a surcharged weir using the orifice equation. +// +{ + double a, q, v, hloss; + + // --- evaluate the orifice flow equation + q = cOrif * sqrt(head); + + // --- apply Armco adjustment if weir has a flap gate + if ( Link[j].hasFlapGate ) + { + a = weir_getOpenArea(j, y); + if ( a > 0.0 ) + { + v = q / a; + hloss = (4.0 / GRAVITY) * v * v * exp(-1.15 * v / sqrt(y) ); + head -= hloss; + head = MAX(head, 0.0); + q = cOrif * sqrt(head); + } + } + if ( head > 0.0 ) Link[j].dqdh = q / (2.0 * head); + else Link[j].dqdh = 0.0; + return q; +} + +//============================================================================= + +double weir_getOpenArea(int j, double y) +// +// Input: j = link index +// y = depth of water above weir crest (ft) +// Output: returns area between weir crest and y (ft2) +// Purpose: finds flow area through a weir. +// +{ + double z, zy; + + // --- find offset of weir crest due to control setting + z = (1.0 - Link[j].setting) * Link[j].xsect.yFull; + + // --- ht. of crest + ht of water above crest + zy = z + y; + zy = MIN(zy, Link[j].xsect.yFull); + + // --- return difference between area of offset + water depth + // and area of just the offset + return xsect_getAofY(&Link[j].xsect, zy) - + xsect_getAofY(&Link[j].xsect, z); +} + +//============================================================================= + +double weir_getdqdh(int k, double dir, double h, double q1, double q2) +{ + double q1h; + double q2h; + + if ( fabs(h) < FUDGE ) return 0.0; + q1h = fabs(q1/h); + q2h = fabs(q2/h); + + switch (Weir[k].type) + { + case TRANSVERSE_WEIR: return 1.5 * q1h; + + case SIDEFLOW_WEIR: + // --- weir behaves as a transverse weir under reverse flow + if ( dir < 0.0 ) return 1.5 * q1h; + else return 1.67 * q1h; + + case VNOTCH_WEIR: + if ( q2h == 0.0 ) return 2.5 * q1h; // Fully open + else return 1.5 * q1h + 2.5 * q2h; // Partly open + + case TRAPEZOIDAL_WEIR: return 1.5 * q1h + 2.5 * q2h; + } + return 0.0; +} + + +//============================================================================= +// O U T L E T D E V I C E M E T H O D S +//============================================================================= + +int outlet_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = outlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads outlet parameters from a tokenized line of input. +// +{ + int i, m, n; + int n1, n2; + double x[6]; + char* id; + char* s; + + // --- check for valid ID and end node IDs + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- get height above invert + if ( LinkOffsets == ELEV_OFFSET && *tok[3] == '*' ) x[0] = MISSING; + else + { + if ( ! getDouble(tok[3], &x[0]) ) + return error_setInpError(ERR_NUMBER, tok[3]); + if ( LinkOffsets == DEPTH_OFFSET && x[0] < 0.0 ) x[0] = 0.0; + } + + // --- see if outlet flow relation is tabular or functional + m = findmatch(tok[4], RelationWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); + x[1] = 0.0; + x[2] = 0.0; + x[3] = -1.0; + x[4] = 0.0; + + // --- see if rating curve is head or depth based + x[5] = NODE_DEPTH; //default is depth-based + s = strtok(tok[4], "/"); //parse token for + s = strtok(NULL, "/"); // qualifier term + if ( strcomp(s, w_HEAD) ) x[5] = NODE_HEAD; //check if its "HEAD" + + // --- get params. for functional outlet device + if ( m == FUNCTIONAL ) + { + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + if ( ! getDouble(tok[5], &x[1]) ) + return error_setInpError(ERR_NUMBER, tok[5]); + if ( ! getDouble(tok[6], &x[2]) ) + return error_setInpError(ERR_NUMBER, tok[6]); + n = 7; + } + + // --- get name of outlet rating curve + else + { + i = project_findObject(CURVE, tok[5]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[5]); + x[3] = i; + n = 6; + } + + // --- check if flap gate specified + if ( ntoks > n) + { + i = findmatch(tok[n], NoYesWords); + if ( i < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); + x[4] = i; + } + + // --- add parameters to outlet object + Link[j].ID = id; + link_setParams(j, OUTLET, n1, n2, k, x); + return 0; +} + +//============================================================================= + +double outlet_getInflow(int j) +// +// Input: j = link index +// Output: outlet flow rate (cfs) +// Purpose: finds the flow through an outlet. +// +{ + int k, n1, n2; + double head, hcrest, h1, h2, y1, dir; + + // --- get indexes of end nodes + n1 = Link[j].node1; + n2 = Link[j].node2; + k = Link[j].subIndex; + + // --- find heads at upstream & downstream nodes + if ( RouteModel == DW ) + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + } + else + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n1].invertElev; + } + dir = (h1 >= h2) ? +1.0 : -1.0; + + // --- exchange h1 and h2 for reverse flow + y1 = Node[n1].newDepth; + if ( dir < 0.0 ) + { + y1 = h1; + h1 = h2; + h2 = y1; + y1 = Node[n2].newDepth; + } + + // --- for a NODE_DEPTH rating curve the effective head across the + // outlet is the depth above the crest elev. while for a NODE_HEAD + // curve it is the difference between upstream & downstream heads + hcrest = Node[n1].invertElev + Link[j].offset1; + if ( Outlet[k].curveType == NODE_HEAD && RouteModel == DW ) + head = h1 - MAX(h2, hcrest); + else head = h1 - hcrest; + + // --- no flow if either no effective head difference, + // no upstream water available, or closed flap gate + if ( head <= FUDGE || y1 <= FUDGE || + link_setFlapGate(j, n1, n2, dir) ) + { + Link[j].newDepth = 0.0; + Link[j].flowClass = DRY; + return 0.0; + } + + // --- otherwise use rating curve to compute flow + Link[j].newDepth = head; + Link[j].flowClass = SUBCRITICAL; + return dir * Link[j].setting * outlet_getFlow(k, head); +} + +//============================================================================= + +double outlet_getFlow(int k, double head) +// +// Input: k = outlet index +// head = head across outlet (ft) +// Output: returns outlet flow rate (cfs) +// Purpose: computes flow rate through an outlet given head. +// +{ + int m; + double h; + + // --- convert head to original units + h = head * UCF(LENGTH); + + // --- look-up flow in rating curve table if provided + m = Outlet[k].qCurve; + if ( m >= 0 ) return table_lookup(&Curve[m], h) / UCF(FLOW); + + // --- otherwise use function to find flow + else return Outlet[k].qCoeff * pow(h, Outlet[k].qExpon) / UCF(FLOW); +} diff --git a/src/macros.h b/src/macros.h new file mode 100644 index 000000000..c15749e4b --- /dev/null +++ b/src/macros.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// macros.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +//----------------------------------------------------------------------------- + +#ifndef MACROS_H +#define MACROS_H + + +//-------------------------------------------------- +// Macro to test for successful allocation of memory +//-------------------------------------------------- +#define MEMCHECK(x) (((x) == NULL) ? 101 : 0 ) + +//-------------------------------------------------- +// Macro to free a non-null pointer +//-------------------------------------------------- +#define FREE(x) { if (x) { free(x); x = NULL; } } + +//--------------------------------------------------- +// Conversion macros to be used in place of functions +//--------------------------------------------------- +#define ABS(x) (((x)<0) ? -(x) : (x)) /* absolute value of x */ +#define MIN(x,y) (((x)<=(y)) ? (x) : (y)) /* minimum of x and y */ +#define MAX(x,y) (((x)>=(y)) ? (x) : (y)) /* maximum of x and y */ +#define MOD(x,y) ((x)%(y)) /* x modulus y */ +#define LOG10(x) ((x) > 0.0 ? log10((x)) : (x)) /* safe log10 of x */ +#define SQR(x) ((x)*(x)) /* x-squared */ +#define SGN(x) (((x)<0) ? (-1) : (1)) /* sign of x */ +#define SIGN(x,y) ((y) >= 0.0 ? fabs(x) : -fabs(x)) +#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) + /* uppercase char of x */ +#define ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0])) /* length of array x */ + +//------------------------------------------------- +// Macro to evaluate function x with error checking +//------------------------------------------------- +#define CALL(x) (ErrorCode = ((ErrorCode>0) ? (ErrorCode) : (x))) + + +#endif //MACROS_H diff --git a/src/main.c b/src/main.c new file mode 100644 index 000000000..cc99865b6 --- /dev/null +++ b/src/main.c @@ -0,0 +1,104 @@ +//----------------------------------------------------------------------------- +// main.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 03/24/2021 +// Author: L. Rossman + +// Main stub for the command line version of EPA SWMM 5.2 +// to be run with swmm5.dll. + +#include +#include +#include +#include "swmm5.h" + +int main(int argc, char *argv[]) +// +// Input: argc = number of command line arguments +// argv = array of command line arguments +// Output: returns error status +// Purpose: runs the command line version of EPA SWMM 5.2. +// +// Command line is: runswmm f1 f2 f3 +// where f1 = name of input file, f2 = name of report file, and +// f3 = name of binary output file if saved (or blank if not saved). +// +{ + char *inputFile; + char *reportFile; + char *binaryFile; + char *arg1; + char blank[] = ""; + int version, vMajor, vMinor, vRelease; + char errMsg[128]; + int msgLen = 127; + time_t start; + double runTime; + + version = swmm_getVersion(); + vMajor = version / 10000; + vMinor = (version - 10000 * vMajor) / 1000; + vRelease = (version - 10000 * vMajor - 1000 * vMinor); + start = time(0); + + // --- check for proper number of command line arguments + if (argc == 1) + { + printf("\nNot Enough Arguments (See Help --help)\n\n"); + } + else if (argc == 2) + { + // --- extract first argument + arg1 = argv[1]; + + if (strcmp(arg1, "--help") == 0 || strcmp(arg1, "-h") == 0) + { + // Help + printf("\n\nSTORMWATER MANAGEMENT MODEL (SWMM) HELP\n\n"); + printf("COMMANDS:\n"); + printf("\t--help (-h) SWMM Help\n"); + printf("\t--version (-v) Build Version\n"); + printf("\nRUNNING A SIMULATION:\n"); + printf("\t runswmm

-// -// consists of: -// value / -// where is -// E.g.: Node 123 Depth > 4.5 -// Node 456 Depth < Node 123 Depth -// -// consists of: -// = setting -// E.g.: Pump abc status = OFF -// Weir xyz setting = 0.5 -// -// Update History -// ============== -// Build 5.1.008: -// - Support added for r.h.s. variables in rule premises. -// - Node volume added as a premise variable. -// Build 5.1.009: -// - Fixed problem with parsing a RHS premise variable. -// Build 5.1.010: -// - Support added for link TIMEOPEN & TIMECLOSED premises. -// Build 5.1.011: -// - Support added for DAYOFYEAR attribute. -// - Modulated controls no longer included in reported control actions. -// Build 5.2.0: -// - Additional attributes added to condition clauses. -// - Support added for named variables in condition clauses. -// - Support added for math expressions in condition clauses. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -enum RuleState {r_RULE, r_IF, r_AND, r_OR, r_THEN, r_ELSE, r_PRIORITY, - r_VARIABLE, r_EXPRESSION, r_ERROR}; -enum RuleObject {r_GAGE, r_NODE, r_LINK, r_CONDUIT, r_PUMP, r_ORIFICE, - r_WEIR, r_OUTLET, r_SIMULATION}; -enum RuleAttrib {r_DEPTH, r_MAXDEPTH, r_HEAD, r_VOLUME, r_INFLOW, - r_FLOW, r_FULLFLOW, r_FULLDEPTH, r_STATUS, r_SETTING, - r_LENGTH, r_SLOPE, r_VELOCITY, r_TIMEOPEN, r_TIMECLOSED, - r_TIME, r_DATE, r_CLOCKTIME, r_DAYOFYEAR, r_DAY, r_MONTH}; -enum RuleRelation {EQ, NE, LT, LE, GT, GE}; -enum RuleSetting {r_CURVE, r_TIMESERIES, r_PID, r_NUMERIC}; - -#define MAXVARNAME 32 - -static char* ObjectWords[] = - {"GAGE", "NODE", "LINK", "CONDUIT", "PUMP", "ORIFICE", "WEIR", "OUTLET", - "SIMULATION", NULL}; -static char* AttribWords[] = - {"DEPTH", "MAXDEPTH", "HEAD", "VOLUME", "INFLOW", - "FLOW", "FULLFLOW", "FULLDEPTH", "STATUS", "SETTING", - "LENGTH", "SLOPE", "VELOCITY", "TIMEOPEN", "TIMECLOSED", - "TIME", "DATE", "CLOCKTIME", "DAYOFYEAR", "DAY", "MONTH", NULL}; -static char* RelOpWords[] = {"=", "<>", "<", "<=", ">", ">=", NULL}; -static char* StatusWords[] = {"OFF", "ON", NULL}; -static char* ConduitWords[] = {"CLOSED", "OPEN", NULL}; -static char* SettingTypeWords[] = {"CURVE", "TIMESERIES", "PID", NULL}; -static char* IntensityWord = "INTENSITY"; - -//----------------------------------------------------------------------------- -// Data Structures -//----------------------------------------------------------------------------- -// Rule Premise Variable -struct TVariable -{ - int object; // type of object - int index; // index in object's array - int attribute; // object's attribute -}; - -// Named Variable -struct TNamedVariable -{ - struct TVariable variable; // a rule premise variable - char name[MAXVARNAME+1]; // name used in math expression -}; - -// Rule Premise Function -struct TExpression -{ - MathExpr* expression; // tokenized math expression - char name[MAXVARNAME+1]; // expression name -}; - -// Rule Premise Clause -struct TPremise -{ - int type; // clause type (IF/AND/OR) - int exprIndex; // expression index (-1 if N/A) - struct TVariable lhsVar; // left hand side variable - struct TVariable rhsVar; // right hand side variable - int relation; // relational operator (>, <, =, etc) - double value; // right hand side value - struct TPremise *next; // next premise clause of rule -}; - -// Rule Action Clause -struct TAction -{ - int rule; // index of rule that action belongs to - int link; // index of link being controlled - int attribute; // attribute of link being controlled - int curve; // index of curve for modulated control - int tseries; // index of time series for modulated control - double value; // control setting for link attribute - double kp, ki, kd; // coeffs. for PID modulated control - double e1, e2; // PID set point error from previous time steps - struct TAction *next; // next action clause of rule -}; - -// List of Control Actions -struct TActionList -{ - struct TAction* action; - struct TActionList* next; -}; - -// Control Rule -struct TRule -{ - char* ID; // rule ID - double priority; // priority level - struct TPremise* firstPremise; // pointer to first premise of rule - struct TPremise* lastPremise; // pointer to last premise of rule - struct TAction* thenActions; // linked list of actions if true - struct TAction* elseActions; // linked list of actions if false -}; - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -struct TRule* Rules; // array of control rules -struct TActionList* ActionList; // linked list of control actions -int InputState; // state of rule interpreter -int RuleCount; // total number of rules -double ControlValue; // value of controller variable -double SetPoint; // value of controller setpoint -DateTime CurrentDate; // current date in whole days -DateTime CurrentTime; // current time of day (decimal) - -int VariableCount; -int ExpressionCount; -int CurrentVariable; -int CurrentExpression; -struct TNamedVariable* NamedVariable; // array of named variables -struct TExpression* Expression; // array of math expressions - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// controls_create -// controls_delete -// controls_init -// controls_addToCount -// controls_addVariable -// controls_addExpression -// controls_addRuleClause -// controls_evaluate - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -int addPremise(int r, int type, char* Tok[], int nToks); -int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v); -int getPremiseValue(char* token, int attrib, double* value); -int addAction(int r, char* Tok[], int nToks); - -int evaluatePremise(struct TPremise* p, double tStep); -double getVariableValue(struct TVariable v); -int compareTimes(double lhsValue, int relation, double rhsValue, - double halfStep); -int compareValues(double lhsValue, int relation, double rhsValue); - -void updateActionList(struct TAction* a); -int executeActionList(DateTime currentTime); -void clearActionList(void); -void deleteActionList(void); -void deleteRules(void); - -int findExactMatch(char *s, char *keyword[]); -int setActionSetting(char* tok[], int nToks, int* curve, int* tseries, - int* attrib, double value[]); -void updateActionValue(struct TAction* a, DateTime currentTime, double dt); -double getPIDSetting(struct TAction* a, double dt); - -int getVariableIndex(char* varName); -double getNamedVariableValue(int varIndex); -int getExpressionIndex(char* exprName); -int getGageAttrib(char* token); -double getRainValue(struct TVariable v); - -//============================================================================= - -void controls_init() -// -// Input: none -// Output: none -// Purpose: initializes the control rule system. -// -{ - Rules = NULL; - NamedVariable = NULL; - Expression = NULL; - RuleCount = 0; - VariableCount = 0; - ExpressionCount = 0; -} - -//============================================================================= - -void controls_addToCount(char* s) -// -// Input: s = either VARIABLE or EXPRESSION -// Output: none -// Purpose: updates the number of named variables or math expressions used -// by control rules. -// -{ - if (match(s, w_VARIABLE)) VariableCount++; - else if (match(s, w_EXPRESSION)) ExpressionCount++; -} - -//============================================================================= - -int controls_create(int n) -// -// Input: n = total number of control rules -// Output: returns error code -// Purpose: creates an array of control rules. -// -{ - int r; - ActionList = NULL; - InputState = r_PRIORITY; - RuleCount = n; - if (RuleCount > 0) - { - Rules = (struct TRule *) calloc(RuleCount, sizeof(struct TRule)); - if (Rules == NULL) return ERR_MEMORY; - for ( r=0; r 0) - { - NamedVariable = (struct TNamedVariable *) calloc(VariableCount, - sizeof(struct TNamedVariable)); - if (NamedVariable == NULL) return ERR_MEMORY; - } - if (ExpressionCount > 0) - { - Expression = (struct TExpression *) calloc(ExpressionCount, - sizeof(struct TExpression)); - if (Expression == NULL) return ERR_MEMORY; - } - return 0; -} - -//============================================================================= - -void controls_delete(void) -// -// Input: none -// Output: none -// Purpose: deletes all control rules. -// -{ - int i; - - for (i = 0; i < ExpressionCount; i++) - { - mathexpr_delete(Expression[i].expression); - Expression[i].expression = NULL; - } - FREE(Expression); - FREE(NamedVariable); - - if ( RuleCount == 0 ) return; - deleteActionList(); - deleteRules(); -} - -//============================================================================= - -int controls_addVariable(char* tok[], int nToks) -// -// Input: tok = an array of string tokens -// n = the size of tok[] -// Output: returns error code -// Purpose: adds a named variable to the control rule system from a -// tokenized line of input with formats: -// VARIABLE name = Object id attribute -// VARIABLE name = SIMULATION attribute -// -{ - struct TVariable v1; - int k, err; - - CurrentVariable++; - if (nToks < 5) return ERR_ITEMS; - if (findExactMatch(tok[1], AttribWords) >= 0) - return error_setInpError(ERR_KEYWORD, tok[1]); - if (!match(tok[2], "=")) return error_setInpError(ERR_KEYWORD, tok[2]); - if (!match(tok[3], "SIMULATION") && nToks < 6) return ERR_ITEMS; - k = 3; - err = getPremiseVariable(tok, nToks, &k, &v1); - if (err > 0) return err; - k = CurrentVariable; - NamedVariable[k].variable = v1; - sstrncpy(NamedVariable[k].name, tok[1], MAXVARNAME); - return 0; -} - -//============================================================================= - -int controls_addExpression(char* tok[], int nToks) -// -// Input: tok = an array of string tokens -// n = number of tokens -// Output: returns error code -// Purpose: adds a math expression to the control rule system from a -// a tokenized line of input with format: -// EXPRESSION name = -// -{ - int i, k; - char s[MAXLINE + 1]; - MathExpr* expr; - - CurrentExpression++; - if (nToks < 4) return ERR_ITEMS; - k = CurrentExpression; - Expression[k].expression = NULL; - sstrncpy(Expression[k].name, tok[1], MAXVARNAME); - sstrncpy(s, tok[3], MAXLINE); - for (i = 4; i < nToks; i++) - { - sstrcat(s, " ", MAXLINE); - sstrcat(s, tok[i], MAXLINE); - } - - expr = mathexpr_create(s, getVariableIndex); - if (expr == NULL) - return error_setInpError(ERR_MATH_EXPR, ""); - - Expression[k].expression = expr; - return 0; -} - -//============================================================================= - -int getVariableIndex(char* varName) -// -// Input: varName = string containing a variable name -// Output: returns the index of the named variable or -1 if not found -// Purpose: finds the array index of a named variable. -// -{ - int i; - for (i = 0; i < VariableCount; i++) - { - if (match(varName, NamedVariable[i].name)) return i; - } - return -1; -} - -//============================================================================= - -double getNamedVariableValue(int varIndex) -// -// Input: varIndex = index of a named variable -// Output: returns the current value of the variable -// Purpose: finds the value of a named variable. -// -{ - return getVariableValue(NamedVariable[varIndex].variable); -} - -//============================================================================= - -int getExpressionIndex(char* exprName) -// -// Input: exprName = string containing an expression name -// Output: returns the index of the expression or -1 if not found -// Purpose: finds the array index of a math expression -// -{ - int i; - for (i = 0; i < ExpressionCount; i++) - { - if (match(exprName, Expression[i].name)) return i; - } - return -1; -} - -//============================================================================= - -int controls_addRuleClause(int r, int keyword, char* tok[], int nToks) -// -// Input: r = rule index -// keyword = the clause's keyword code (IF, THEN, etc.) -// tok = an array of string tokens that comprises the clause -// nToks = number of tokens -// Output: returns an error code -// Purpose: addd a new clause to a control rule. -// -{ - switch (keyword) - { - case r_RULE: - if ( Rules[r].ID == NULL ) - Rules[r].ID = project_findID(CONTROL, tok[1]); - InputState = r_RULE; - if ( nToks > 2 ) return ERR_RULE; - return 0; - - case r_IF: - if ( InputState != r_RULE ) return ERR_RULE; - InputState = r_IF; - return addPremise(r, r_AND, tok, nToks); - - case r_AND: - if ( InputState == r_IF ) return addPremise(r, r_AND, tok, nToks); - else if ( InputState == r_THEN || InputState == r_ELSE ) - return addAction(r, tok, nToks); - else return ERR_RULE; - - case r_OR: - if ( InputState != r_IF ) return ERR_RULE; - return addPremise(r, r_OR, tok, nToks); - - case r_THEN: - if ( InputState != r_IF ) return ERR_RULE; - InputState = r_THEN; - return addAction(r, tok, nToks); - - case r_ELSE: - if ( InputState != r_THEN ) return ERR_RULE; - InputState = r_ELSE; - return addAction(r, tok, nToks); - - case r_PRIORITY: - if ( InputState != r_THEN && InputState != r_ELSE ) return ERR_RULE; - InputState = r_PRIORITY; - if ( !getDouble(tok[1], &Rules[r].priority) ) return ERR_NUMBER; - if ( nToks > 2 ) return ERR_RULE; - return 0; - } - return 0; -} - -//============================================================================= - -int controls_evaluate(DateTime currentTime, DateTime elapsedTime, double tStep) -// -// Input: currentTime = current simulation date/time -// elapsedTime = decimal days since start of simulation -// tStep = simulation time step (days) -// Output: returns number of new actions taken -// Purpose: evaluates all control rules at current time of the simulation. -// -{ - int r; // control rule index - int result; // TRUE if rule premises satisfied - struct TPremise* p; // pointer to rule premise clause - struct TAction* a; // pointer to rule action clause - - // --- save date and time to shared variables - CurrentDate = floor(currentTime); - CurrentTime = currentTime - floor(currentTime); - ElapsedTime = elapsedTime; - - // --- evaluate each rule - if ( RuleCount == 0 ) return 0; - clearActionList(); - for (r=0; rtype == r_OR ) - { - if ( result == FALSE ) - result = evaluatePremise(p, tStep); - } - else - { - if ( result == FALSE ) break; - result = evaluatePremise(p, tStep); - } - p = p->next; - } - - // --- if premises true, add THEN clauses to action list - // else add ELSE clauses to action list - if ( result == TRUE ) a = Rules[r].thenActions; - else a = Rules[r].elseActions; - while (a) - { - updateActionValue(a, currentTime, tStep); - updateActionList(a); - a = a->next; - } - } - - // --- execute actions on action list - if ( ActionList ) return executeActionList(currentTime); - else return 0; -} - -//============================================================================= - -int addPremise(int r, int type, char* tok[], int nToks) -// -// Input: r = control rule index -// type = type of premise (IF, AND, OR) -// tok = array of string tokens containing premise statement -// nToks = number of string tokens -// Output: returns an error code -// Purpose: adds a new premise to a control rule. -// -{ - int relation, n, err = 0; - double value = MISSING; - struct TPremise* p; - struct TVariable v1; - struct TVariable v2; - int obj, exprIndex, varIndex = -1; - - // --- initialize LHS variable v1 - if (nToks < 4) return ERR_ITEMS; - v1.attribute = -1; - v1.object = -1; - v1.index = -1; - n = 1; - - // --- check if 2nd token is a math expression - exprIndex = getExpressionIndex(tok[1]); - - // --- if not then check if it's a named variable - if (exprIndex < 0) - { - varIndex = getVariableIndex(tok[n]); - if (varIndex >= 0) - { - v1 = NamedVariable[varIndex].variable; - } - - // otherwise parse object|index|attribute tokens - else - { - err = getPremiseVariable(tok, nToks, &n, &v1); - if ( err > 0 ) return err; - } - } - - // --- get relational operator - n++; - if ( n >= nToks ) return error_setInpError(ERR_ITEMS, ""); - relation = findExactMatch(tok[n], RelOpWords); - if ( relation < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); - - // --- initialize RHS variable v2 - v2.attribute = -1; - v2.object = -1; - v2.index = -1; - n++; - if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); - - // --- check for named RHS variable - varIndex = getVariableIndex(tok[n]); - if (varIndex >= 0) - { - v2 = NamedVariable[varIndex].variable; - } - - // --- check for object|index|attribute variable - else - { - obj = findmatch(tok[n], ObjectWords); - if (obj >= 0) - { - err = getPremiseVariable(tok, nToks, &n, &v2); - if ( err > 0 ) return ERR_RULE; - if (exprIndex < 0 && v1.attribute != v2.attribute) - report_writeWarningMsg(WARN11, Rules[r].ID); - } - - // --- check for a single RHS value - else - { - err = getPremiseValue(tok[n], v1.attribute, &value); - if ( err > 0 ) return err; - } - } - - // --- make sure another clause is not on same line - n++; - if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE; - - // --- create the premise object - p = (struct TPremise *) malloc(sizeof(struct TPremise)); - if ( !p ) return ERR_MEMORY; - p->type = type; - p->exprIndex = exprIndex; - p->lhsVar = v1; - p->rhsVar = v2; - p->relation = relation; - p->value = value; - p->next = NULL; - if ( Rules[r].firstPremise == NULL ) - { - Rules[r].firstPremise = p; - } - else - { - Rules[r].lastPremise->next = p; - } - Rules[r].lastPremise = p; - return 0; -} - -//============================================================================= - -int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v) -// -// Input: tok = array of string tokens -// nToks = number of tokens -// k = index of current token -// Output: returns an error code; updates k to new current token and -// places identity of specified variable in v -// Purpose: parses a variable (e.g., Node 123 Depth) used in a control rule. -// -{ - int n = *k; - int object = -1; - int index = -1; - int obj, attrib; - - // --- get object type - obj = findmatch(tok[n], ObjectWords); - if ( obj < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); - - // --- get object index from its name - n++; - if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); - switch (obj) - { - case r_GAGE: - index = project_findObject(GAGE, tok[n]); - if (index < 0) return error_setInpError(ERR_NAME, tok[n]); - object = r_GAGE; - break; - - case r_NODE: - index = project_findObject(NODE, tok[n]); - if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]); - object = r_NODE; - break; - - case r_LINK: - case r_CONDUIT: - case r_PUMP: - case r_ORIFICE: - case r_WEIR: - case r_OUTLET: - index = project_findObject(LINK, tok[n]); - if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]); - object = r_LINK; - break; - default: n--; - } - n++; - if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); - - // --- get attribute index from its name - if (object == r_GAGE) - attrib = getGageAttrib(tok[n]); - else - attrib = findmatch(tok[n], AttribWords); - if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); - - // --- check that attribute belongs to object type - if (obj == r_GAGE) - { - - } - - else if ( obj == r_NODE ) switch (attrib) - { - case r_DEPTH: - case r_MAXDEPTH: - case r_HEAD: - case r_VOLUME: - case r_INFLOW: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - - // --- check for link TIMEOPEN & TIMECLOSED attributes - else if ( object == r_LINK && index >= 0 && - ( (attrib == r_TIMEOPEN || attrib == r_TIMECLOSED) - )) - { - // nothing to do here - } - - else if ( obj == r_LINK || obj == r_CONDUIT ) switch (attrib) - { - case r_STATUS: - case r_DEPTH: - case r_FULLFLOW: - case r_FULLDEPTH: - case r_FLOW: - case r_LENGTH: - case r_SLOPE: - case r_VELOCITY: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - else if ( obj == r_PUMP ) switch (attrib) - { - case r_FLOW: - case r_SETTING: - case r_STATUS: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - else if ( obj == r_ORIFICE || obj == r_WEIR || - obj == r_OUTLET ) switch (attrib) - { - case r_FLOW: - case r_SETTING: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - else switch (attrib) - { - case r_TIME: - case r_DATE: - case r_CLOCKTIME: - case r_DAY: - case r_MONTH: - case r_DAYOFYEAR: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - - // --- populate variable structure - v->object = object; - v->index = index; - v->attribute = attrib; - *k = n; - return 0; -} - -//============================================================================= - -int getGageAttrib(char* token) -// -// Input: token = a string token -// Output: returns an attribute code or -1 if an error occurred -// Purpose: determines the atrribute code for a rain gage variable. -// Note: a valid token is INTENSITY for current rainfall intensity -// (attribute code = 0) or nHR_PRECIP for total rain depth -// over past n hours (attribute code = n). -// -{ - int attrib; - - // --- check if token is currrent rainfall intensity - if (match(token, IntensityWord)) - return 0; - - // --- token is past rain depth - read number of past hours - attrib = atoi(token); - - // --- check that number of hours is in allowable range - if (attrib < 1 || attrib > MAXPASTRAIN) - return -1; - return attrib; -} - -//============================================================================= - -int getPremiseValue(char* token, int attrib, double* value) -// -// Input: token = a string token -// attrib = index of a node/link attribute -// Output: value = attribute value; -// returns an error code; -// Purpose: parses the numerical value of a particular node/link attribute -// in the premise clause of a control rule. -// -{ - char strDate[25]; - switch (attrib) - { - case r_STATUS: - *value = findmatch(token, StatusWords); - if ( *value < 0.0 ) *value = findmatch(token, ConduitWords); - if ( *value < 0.0 ) return error_setInpError(ERR_KEYWORD, token); - break; - - case r_TIME: - case r_CLOCKTIME: - case r_TIMEOPEN: - case r_TIMECLOSED: - if ( !datetime_strToTime(token, value) ) - return error_setInpError(ERR_DATETIME, token); - break; - - case r_DATE: - if ( !datetime_strToDate(token, value) ) - return error_setInpError(ERR_DATETIME, token); - break; - - case r_DAY: - if ( !getDouble(token, value) ) - return error_setInpError(ERR_NUMBER, token); - if ( *value < 1.0 || *value > 7.0 ) - return error_setInpError(ERR_DATETIME, token); - break; - - case r_MONTH: - if ( !getDouble(token, value) ) - return error_setInpError(ERR_NUMBER, token); - if ( *value < 1.0 || *value > 12.0 ) - return error_setInpError(ERR_DATETIME, token); - break; - - case r_DAYOFYEAR: - sstrncpy(strDate, token, 6); - sstrcat(strDate, "/1947", 25); - if ( datetime_strToDate(strDate, value) ) - { - *value = datetime_dayOfYear(*value); - } - else if ( !getDouble(token, value) || *value < 1 || *value > 365 ) - return error_setInpError(ERR_DATETIME, token); - break; - - default: if ( !getDouble(token, value) ) - return error_setInpError(ERR_NUMBER, token); - } - return 0; -} - -//============================================================================= - -int addAction(int r, char* tok[], int nToks) -// -// Input: r = control rule index -// tok = array of string tokens containing action statement -// nToks = number of string tokens -// Output: returns an error code -// Purpose: adds a new action to a control rule. -// -{ - int obj, link, attrib; - int curve = -1, tseries = -1; - int n; - int err; - double values[] = {1.0, 0.0, 0.0}; - - struct TAction* a; - - // --- check for proper number of tokens - if ( nToks < 6 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check for valid object type - obj = findmatch(tok[1], ObjectWords); - if ( obj != r_LINK && obj != r_CONDUIT && obj != r_PUMP && - obj != r_ORIFICE && obj != r_WEIR && obj != r_OUTLET ) - return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- check that object name exists and is of correct type - link = project_findObject(LINK, tok[2]); - if ( link < 0 ) return error_setInpError(ERR_NAME, tok[2]); - switch (obj) - { - case r_CONDUIT: - if ( Link[link].type != CONDUIT ) - return error_setInpError(ERR_NAME, tok[2]); - break; - case r_PUMP: - if ( Link[link].type != PUMP ) - return error_setInpError(ERR_NAME, tok[2]); - break; - case r_ORIFICE: - if ( Link[link].type != ORIFICE ) - return error_setInpError(ERR_NAME, tok[2]); - break; - case r_WEIR: - if ( Link[link].type != WEIR ) - return error_setInpError(ERR_NAME, tok[2]); - break; - case r_OUTLET: - if ( Link[link].type != OUTLET ) - return error_setInpError(ERR_NAME, tok[2]); - break; - } - - // --- check for valid attribute name - attrib = findmatch(tok[3], AttribWords); - if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); - - // --- get control action setting - if ( obj == r_CONDUIT ) - { - if ( attrib == r_STATUS ) - { - values[0] = findmatch(tok[5], ConduitWords); - if ( values[0] < 0.0 ) - return error_setInpError(ERR_KEYWORD, tok[5]); - } - else return error_setInpError(ERR_KEYWORD, tok[3]); - } - - else if ( obj == r_PUMP ) - { - if ( attrib == r_STATUS ) - { - values[0] = findmatch(tok[5], StatusWords); - if ( values[0] < 0.0 ) - return error_setInpError(ERR_KEYWORD, tok[5]); - } - else if ( attrib == r_SETTING ) - { - err = setActionSetting(tok, nToks, &curve, &tseries, - &attrib, values); - if ( err > 0 ) return err; - } - else return error_setInpError(ERR_KEYWORD, tok[3]); - } - - else if ( obj == r_ORIFICE || obj == r_WEIR || obj == r_OUTLET ) - { - if ( attrib == r_SETTING ) - { - err = setActionSetting(tok, nToks, &curve, &tseries, - &attrib, values); - if ( err > 0 ) return err; - if ( attrib == r_SETTING - && (values[0] < 0.0 || values[0] > 1.0) ) - return error_setInpError(ERR_NUMBER, tok[5]); - } - else return error_setInpError(ERR_KEYWORD, tok[3]); - } - else return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- check if another clause is on same line - n = 6; - if ( curve >= 0 || tseries >= 0 ) n = 7; - if ( attrib == r_PID ) n = 9; - if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE; - - // --- create the action object - a = (struct TAction *) malloc(sizeof(struct TAction)); - if ( !a ) return ERR_MEMORY; - a->rule = r; - a->link = link; - a->attribute = attrib; - a->curve = curve; - a->tseries = tseries; - a->value = values[0]; - if ( attrib == r_PID ) - { - a->kp = values[0]; - a->ki = values[1]; - a->kd = values[2]; - a->e1 = 0.0; - a->e2 = 0.0; - } - if ( InputState == r_THEN ) - { - a->next = Rules[r].thenActions; - Rules[r].thenActions = a; - } - else - { - a->next = Rules[r].elseActions; - Rules[r].elseActions = a; - } - return 0; -} - -//============================================================================= - -int setActionSetting(char* tok[], int nToks, int* curve, int* tseries, - int* attrib, double values[]) -// -// Input: tok = array of string tokens containing action statement -// nToks = number of string tokens -// Output: curve = index of controller curve -// tseries = index of controller time series -// attrib = r_PID if PID controller used -// values = values of control settings -// returns an error code -// Purpose: identifies how control actions settings are determined. -// -{ - int k, m; - - // --- see if control action is determined by a Curve or Time Series - if (nToks < 6) return error_setInpError(ERR_ITEMS, ""); - k = findmatch(tok[5], SettingTypeWords); - if ( k >= 0 && nToks < 7 ) return error_setInpError(ERR_ITEMS, ""); - switch (k) - { - - // --- control determined by a curve - find curve index - case r_CURVE: - m = project_findObject(CURVE, tok[6]); - if ( m < 0 ) return error_setInpError(ERR_NAME, tok[6]); - *curve = m; - break; - - // --- control determined by a time series - find time series index - case r_TIMESERIES: - m = project_findObject(TSERIES, tok[6]); - if ( m < 0 ) return error_setInpError(ERR_NAME, tok[6]); - *tseries = m; - Tseries[m].refersTo = CONTROL; - break; - - // --- control determined by PID controller - case r_PID: - if (nToks < 9) return error_setInpError(ERR_ITEMS, ""); - for (m=6; m<=8; m++) - { - if ( !getDouble(tok[m], &values[m-6]) ) - return error_setInpError(ERR_NUMBER, tok[m]); - } - *attrib = r_PID; - break; - - // --- direct numerical control is used - default: - if ( !getDouble(tok[5], &values[0]) ) - return error_setInpError(ERR_NUMBER, tok[5]); - } - return 0; -} - -//============================================================================= - -void updateActionValue(struct TAction* a, DateTime currentTime, double dt) -// -// Input: a = an action object -// currentTime = current simulation date/time (days) -// dt = time step (days) -// Output: none -// Purpose: updates value of actions found from Curves or Time Series. -// -{ - if ( a->curve >= 0 ) - { - a->value = table_lookup(&Curve[a->curve], ControlValue); - } - else if ( a->tseries >= 0 ) - { - a->value = table_tseriesLookup(&Tseries[a->tseries], currentTime, TRUE); - } - else if ( a->attribute == r_PID ) - { - a->value = getPIDSetting(a, dt); - } -} - -//============================================================================= - -double getPIDSetting(struct TAction* a, double dt) -// -// Input: a = an action object -// dt = current time step (days) -// Output: returns a new link setting -// Purpose: computes a new setting for a link subject to a PID controller. -// -// Note: a->kp = gain coefficient, -// a->ki = integral time (minutes) -// a->k2 = derivative time (minutes) -// a->e1 = error from previous time step -// a->e2 = error from two time steps ago -{ - double e0, setting; - double p, i, d, update; - double tolerance = 0.0001; - - // --- convert time step from days to minutes - dt *= 1440.0; - - // --- determine relative error in achieving controller set point - e0 = SetPoint - ControlValue; - if ( fabs(e0) > TINY ) - { - if ( SetPoint != 0.0 ) e0 = e0/SetPoint; - else e0 = e0/ControlValue; - } - - // --- reset previous errors to 0 if controller gets stuck - if (fabs(e0 - a->e1) < tolerance) - { - a->e2 = 0.0; - a->e1 = 0.0; - } - - // --- use the recursive form of the PID controller equation to - // determine the new setting for the controlled link - p = (e0 - a->e1); - if ( a->ki == 0.0 ) i = 0.0; - else i = e0 * dt / a->ki; - d = a->kd * (e0 - 2.0*a->e1 + a->e2) / dt; - update = a->kp * (p + i + d); - if ( fabs(update) < tolerance ) update = 0.0; - setting = Link[a->link].targetSetting + update; - - // --- update previous errors - a->e2 = a->e1; - a->e1 = e0; - - // --- check that new setting lies within feasible limits - if ( setting < 0.0 ) setting = 0.0; - if (Link[a->link].type != PUMP && setting > 1.0 ) setting = 1.0; - return setting; -} - -//============================================================================= - -void updateActionList(struct TAction* a) -// -// Input: a = an action object -// Output: none -// Purpose: adds a new action to the list of actions to be taken. -// -{ - struct TActionList* listItem; - struct TAction* a1; - double priority = Rules[a->rule].priority; - - // --- check if link referred to in action is already listed - listItem = ActionList; - while ( listItem ) - { - a1 = listItem->action; - if ( !a1 ) break; - if ( a1->link == a->link ) - { - // --- replace old action if new action has higher priority - if ( priority > Rules[a1->rule].priority ) listItem->action = a; - return; - } - listItem = listItem->next; - } - - // --- action not listed so add it to ActionList - listItem = (struct TActionList *) malloc(sizeof(struct TActionList)); - if (listItem) - { - listItem->next = ActionList; - ActionList = listItem; - listItem->action = a; - } -} - -//============================================================================= - -int executeActionList(DateTime currentTime) -// -// Input: currentTime = current date/time of the simulation -// Output: returns number of new actions taken -// Purpose: executes all actions required by fired control rules. -// -{ - struct TActionList* listItem; - struct TActionList* nextItem; - struct TAction* a1; - int count = 0; - - listItem = ActionList; - while ( listItem ) - { - a1 = listItem->action; - if ( !a1 ) break; - if ( a1->link >= 0 ) - { - if ( Link[a1->link].targetSetting != a1->value ) - { - Link[a1->link].targetSetting = a1->value; - if ( RptFlags.controls && a1->curve < 0 - && a1->tseries < 0 && a1->attribute != r_PID ) - report_writeControlAction(currentTime, Link[a1->link].ID, - a1->value, Rules[a1->rule].ID); - count++; - } - } - nextItem = listItem->next; - listItem = nextItem; - } - return count; -} - -//============================================================================= - -int evaluatePremise(struct TPremise* p, double tStep) -// -// Input: p = a control rule premise condition -// tStep = current time step (days) -// Output: returns TRUE if the condition is true or FALSE otherwise -// Purpose: evaluates the truth of a control rule premise condition. -// -{ - double lhsValue, rhsValue; - int result = FALSE; - - // --- check if left hand side (lhs) of premise is an expression - if (p->exprIndex >= 0) - lhsValue = mathexpr_eval(Expression[p->exprIndex].expression, - getNamedVariableValue); - - // --- otherwise get value of the lhs variable - else - lhsValue = getVariableValue(p->lhsVar); - - // --- if right hand side (rhs) of premise is a variable then get its value - if ( p->value == MISSING ) rhsValue = getVariableValue(p->rhsVar); - else rhsValue = p->value; - if ( lhsValue == MISSING || rhsValue == MISSING ) return FALSE; - - // --- compare the lhs of the premise to the rhs - switch (p->lhsVar.attribute) - { - case r_TIME: - case r_CLOCKTIME: - return compareTimes(lhsValue, p->relation, rhsValue, tStep/2.0); - case r_TIMEOPEN: - case r_TIMECLOSED: - result = compareTimes(lhsValue, p->relation, rhsValue, tStep/2.0); - ControlValue = lhsValue * 24.0; // convert time from days to hours - return result; - default: - return compareValues(lhsValue, p->relation, rhsValue); - } -} - -//============================================================================= - -double getVariableValue(struct TVariable v) -{ - int i = -1; // a node index - int j = -1; // a link index - - if (v.object == r_GAGE) - return getRainValue(v); - if (v.object == r_NODE) i = v.index; - if (v.object == r_LINK) j = v.index; - - switch ( v.attribute ) - { - case r_TIME: - return ElapsedTime; - - case r_DATE: - return CurrentDate; - - case r_CLOCKTIME: - return CurrentTime; - - case r_DAY: - return datetime_dayOfWeek(CurrentDate); - - case r_MONTH: - return datetime_monthOfYear(CurrentDate); - - case r_DAYOFYEAR: - return datetime_dayOfYear(CurrentDate); - - case r_STATUS: - if ( j < 0 || - (Link[j].type != CONDUIT && Link[j].type != PUMP) ) return MISSING; - else return Link[j].setting; - - case r_SETTING: - if ( j < 0 || (Link[j].type != PUMP && - Link[j].type != ORIFICE && - Link[j].type != WEIR) ) - return MISSING; - else return Link[j].setting; - - case r_FLOW: - if ( j < 0 ) return MISSING; - else return Link[j].direction*Link[j].newFlow*UCF(FLOW); - - case r_FULLFLOW: - case r_FULLDEPTH: - case r_VELOCITY: - case r_LENGTH: - case r_SLOPE: - if ( j < 0 ) return MISSING; - else if (Link[j].type != CONDUIT) return MISSING; - switch (v.attribute) - { - case r_FULLFLOW: return Link[j].qFull * UCF(FLOW); - case r_FULLDEPTH: return Link[j].xsect.yFull * UCF(LENGTH); - case r_VELOCITY: - return link_getVelocity(j, Link[j].newFlow, Link[j].newDepth) - * UCF(LENGTH); - case r_LENGTH: return Conduit[Link[j].subIndex].length * UCF(LENGTH); - case r_SLOPE: return Conduit[Link[j].subIndex].slope; - default: return MISSING; - } - case r_DEPTH: - if ( j >= 0 ) return Link[j].newDepth*UCF(LENGTH); - else if ( i >= 0 ) - return Node[i].newDepth*UCF(LENGTH); - else return MISSING; - - case r_MAXDEPTH: - if (i >= 0) return Node[i].fullDepth*UCF(LENGTH); - else return MISSING; - - case r_HEAD: - if ( i < 0 ) return MISSING; - return (Node[i].newDepth + Node[i].invertElev) * UCF(LENGTH); - - case r_VOLUME: - if ( i < 0 ) return MISSING; - return (Node[i].newVolume * UCF(VOLUME)); - - case r_INFLOW: - if ( i < 0 ) return MISSING; - else return Node[i].newLatFlow*UCF(FLOW); - - case r_TIMEOPEN: - if ( j < 0 ) return MISSING; - if ( Link[j].setting <= 0.0 ) return MISSING; - return CurrentDate + CurrentTime - Link[j].timeLastSet; - - case r_TIMECLOSED: - if ( j < 0 ) return MISSING; - if ( Link[j].setting > 0.0 ) return MISSING; - return CurrentDate + CurrentTime - Link[j].timeLastSet; - - default: return MISSING; - } -} - -//============================================================================= - -double getRainValue(struct TVariable v) -// -// Input: v = a rule premise variable for a rain gage -// Output: returns current or past rainfall amount -// Purpose: retrieves either the current rainfall intensity or the past -// rainfall total for a rain gage. -// -{ - if (v.index < 0) return MISSING; - else if (Gage[v.index].isUsed == FALSE) return 0.0; - else if (v.attribute == 0) - return Gage[v.index].rainfall; - else return gage_getPastRain(v.index, v.attribute); -} - -//============================================================================= - -int compareTimes(double lhsValue, int relation, double rhsValue, double halfStep) -// -// Input: lhsValue = date/time value on left hand side of relation -// relation = relational operator code (see RuleRelation enumeration) -// rhsValue = date/time value on right hand side of relation -// halfStep = 1/2 the current time step (days) -// Output: returns TRUE if time relation is satisfied -// Purpose: evaluates the truth of a relation between two date/times. -// -{ - if ( relation == EQ ) - { - if ( lhsValue >= rhsValue - halfStep - && lhsValue < rhsValue + halfStep ) return TRUE; - return FALSE; - } - else if ( relation == NE ) - { - if ( lhsValue < rhsValue - halfStep - || lhsValue >= rhsValue + halfStep ) return TRUE; - return FALSE; - } - else return compareValues(lhsValue, relation, rhsValue); -} - -//============================================================================= - -int compareValues(double lhsValue, int relation, double rhsValue) -// Input: lhsValue = value on left hand side of relation -// relation = relational operator code (see RuleRelation enumeration) -// rhsValue = value on right hand side of relation -// Output: returns TRUE if relation is satisfied -// Purpose: evaluates the truth of a relation between two values. -{ - SetPoint = rhsValue; - ControlValue = lhsValue; - switch (relation) - { - case EQ: if ( lhsValue == rhsValue ) return TRUE; break; - case NE: if ( lhsValue != rhsValue ) return TRUE; break; - case LT: if ( lhsValue < rhsValue ) return TRUE; break; - case LE: if ( lhsValue <= rhsValue ) return TRUE; break; - case GT: if ( lhsValue > rhsValue ) return TRUE; break; - case GE: if ( lhsValue >= rhsValue ) return TRUE; break; - } - return FALSE; -} - -//============================================================================= - -void clearActionList(void) -// -// Input: none -// Output: none -// Purpose: clears the list of actions to be executed. -// -{ - struct TActionList* listItem; - listItem = ActionList; - while ( listItem ) - { - listItem->action = NULL; - listItem = listItem->next; - } -} - -//============================================================================= - -void deleteActionList(void) -// -// Input: none -// Output: none -// Purpose: frees the memory used to hold the list of actions to be executed. -// -{ - struct TActionList* listItem; - struct TActionList* nextItem; - listItem = ActionList; - while ( listItem ) - { - nextItem = listItem->next; - free(listItem); - listItem = nextItem; - } - ActionList = NULL; -} - -//============================================================================= - -void deleteRules(void) -// -// Input: none -// Output: none -// Purpose: frees the memory used for all of the control rules. -// -{ - struct TPremise* p; - struct TPremise* pnext; - struct TAction* a; - struct TAction* anext; - int r; - for (r=0; rnext; - free(p); - p = pnext; - } - a = Rules[r].thenActions; - while (a ) - { - anext = a->next; - free(a); - a = anext; - } - a = Rules[r].elseActions; - while (a ) - { - anext = a->next; - free(a); - a = anext; - } - } - FREE(Rules); - RuleCount = 0; -} - -//============================================================================= - -int findExactMatch(char *s, char *keyword[]) -// -// Input: s = character string -// keyword = array of keyword strings -// Output: returns index of keyword which matches s or -1 if no match found -// Purpose: finds exact match between string and array of keyword strings. -// -{ - int i = 0; - while (keyword[i] != NULL) - { - if ( strcomp(s, keyword[i]) ) return(i); - i++; - } - return(-1); -} - -//============================================================================= diff --git a/src/culvert.c b/src/culvert.c deleted file mode 100644 index 5f62877b7..000000000 --- a/src/culvert.c +++ /dev/null @@ -1,411 +0,0 @@ -//----------------------------------------------------------------------------- -// culvert.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Culvert equations for SWMM5 -// -// Computes flow reduction in a culvert-type conduit due to -// inlet control using equations from the FHWA HEC-5 circular. -// -// Update History -// ============== -// Build 5.1.013: -// - C parameter corrected for Arch, Corrugated Metal, Mitered culvert. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "findroot.h" -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -enum CulvertParam {FORM, K, M, C, Y}; -static const int MAX_CULVERT_CODE = 57; -static const double Params[58][5] = { - -// FORM K M C Y -//------------------------------------ - {0.0, 0.0, 0.0, 0.0, 0.00}, - - //Circular concrete - {1.0, 0.0098, 2.00, 0.0398, 0.67}, //Square edge w/headwall - {1.0, 0.0018, 2.00, 0.0292, 0.74}, //Groove end w/headwall - {1.0, 0.0045, 2.00, 0.0317, 0.69}, //Groove end projecting - - //Circular Corrugated Metal Pipe - {1.0, 0.0078, 2.00, 0.0379, 0.69}, //Headwall - {1.0, 0.0210, 1.33, 0.0463, 0.75}, //Mitered to slope - {1.0, 0.0340, 1.50, 0.0553, 0.54}, //Projecting - - //Circular Pipe, Beveled Ring Entrance - {1.0, 0.0018, 2.50, 0.0300, 0.74}, //Beveled ring, 45 deg bevels - {1.0, 0.0018, 2.50, 0.0243, 0.83}, //Beveled ring, 33.7 deg bevels - - //Rectangular Box with Flared Wingwalls - {1.0, 0.026, 1.0, 0.0347, 0.81}, //30-75 deg. wingwall flares - {1.0, 0.061, 0.75, 0.0400, 0.80}, //90 or 15 deg. wingwall flares - {1.0, 0.061, 0.75, 0.0423, 0.82}, //0 deg. wingwall flares (striaght sides) - - //Rectanglar Box with Flared Wingwalls & Top Edge Bevel - {2.0, 0.510, 0.667, 0.0309, 0.80}, //45 deg. flare; 0.43D top edge bevel - {2.0, 0.486, 0.667, 0.0249, 0.83}, //18-33.7 deg flare; 0.083D top edge bevel - - //Rectangular Box; 90-deg Headwall; Chamfered or Beveled Inlet Edges - {2.0, 0.515, 0.667, 0.0375, 0.79}, //chamfered 3/4-in - {2.0, 0.495, 0.667, 0.0314, 0.82}, //beveled 1/2-in/ft at 45 deg (1:1) - {2.0, 0.486, 0.667, 0.0252, 0.865}, //beveled 1-in/ft at 33.7 deg (1:1.5) - - //Rectangular Box; Skewed Headwall; Chamfered or Beveled Inlet Edges - {2.0, 0.545, 0.667, 0.04505,0.73}, //3/4" chamfered edge, 45 deg skewed headwall - {2.0, 0.533, 0.667, 0.0425, 0.705}, //3/4" chamfered edge, 30 deg skewed headwall - {2.0, 0.522, 0.667, 0.0402, 0.68}, //3/4" chamfered edge, 15 deg skewed headwall - {2.0, 0.498, 0.667, 0.0327, 0.75}, //45 deg beveled edge, 10-45 deg skewed headwall - - //Rectangular box, Non-offset Flared Wingwalls; 3/4" Chamfer at Top of Inlet - {2.0, 0.497, 0.667, 0.0339, 0.803}, //45 deg (1:1) wingwall flare - {2.0, 0.493, 0.667, 0.0361, 0.806}, //18.4 deg (3:1) wingwall flare - {2.0, 0.495, 0.667, 0.0386, 0.71}, //18.4 deg (3:1) wingwall flare, 30 deg inlet skew - - //Rectangular box, Offset Flared Wingwalls, Beveled Edge at Inlet Top - {2.0, 0.497, 0.667, 0.0302, 0.835}, //45 deg (1:1) flare, 0.042D top edge bevel - {2.0, 0.495, 0.667, 0.0252, 0.881}, //33.7 deg (1.5:1) flare, 0.083D top edge bevel - {2.0, 0.493, 0.667, 0.0227, 0.887}, //18.4 deg (3:1) flare, 0.083D top edge bevel - - // Corrugated Metal Box - {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall - {1.0, 0.0145, 1.75, 0.0419, 0.64}, //Thick wall projecting - {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Thin wall projecting - - // Horizontal Ellipse Concrete - {1.0, 0.0100, 2.00, 0.0398, 0.67}, //Square edge w/headwall - {1.0, 0.0018, 2.50, 0.0292, 0.74}, //Grooved end w/headwall - {1.0, 0.0045, 2.00, 0.0317, 0.69}, //Grooved end projecting - - // Vertical Ellipse Concrete - {1.0, 0.0100, 2.00, 0.0398, 0.67}, //Square edge w/headwall - {1.0, 0.0018, 2.50, 0.0292, 0.74}, //Grooved end w/headwall - {1.0, 0.0095, 2.00, 0.0317, 0.69}, //Grooved end projecting - - // Pipe Arch, 18" Corner Radius, Corrugated Metal - {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall - {1.0, 0.0300, 1.00, 0.0463, 0.75}, //Mitered to slope - {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Projecting - - // Pipe Arch, 18" Corner Radius, Corrugated Metal - {1.0, 0.0300, 1.50, 0.0496, 0.57}, //Projecting - {1.0, 0.0088, 2.00, 0.0368, 0.68}, //No bevels - {1.0, 0.0030, 2.00, 0.0269, 0.77}, //33.7 deg bevels - - // Pipe Arch, 31" Corner Radius, Corrugated Metal - {1.0, 0.0300, 1.50, 0.0496, 0.57}, //Projecting - {1.0, 0.0088, 2.00, 0.0368, 0.68}, //No bevels - {1.0, 0.0030, 2.00, 0.0269, 0.77}, //33.7 deg. bevels - - // Arch, Corrugated Metal - {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall - {1.0, 0.0300, 1.00, 0.0473, 0.75}, //Mitered to slope - {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Thin wall projecting - - // Circular Culvert - {2.0, 0.534, 0.555, 0.0196, 0.90}, //Smooth tapered inlet throat - {2.0, 0.519, 0.640, 0.0210, 0.90}, //Rough tapered inlet throat - - // Elliptical Inlet Face - {2.0, 0.536, 0.622, 0.0368, 0.83}, //Tapered inlet, beveled edges - {2.0, 0.5035,0.719, 0.0478, 0.80}, //Tapered inlet, square edges - {2.0, 0.547, 0.800, 0.0598, 0.75}, //Tapered inlet, thin edge projecting - - // Rectangular - {2.0, 0.475, 0.667, 0.0179, 0.97}, //Tapered inlet throat - - // Rectangular Concrete - {2.0, 0.560, 0.667, 0.0446, 0.85}, //Side tapered, less favorable edges - {2.0, 0.560, 0.667, 0.0378, 0.87}, //Side tapered, more favorable edges - - // Rectangular Concrete - {2.0, 0.500, 0.667, 0.0446, 0.65}, //Slope tapered, less favorable edges - {2.0, 0.500, 0.667, 0.0378, 0.71} //Slope tapered, more favorable edges - - }; - -//----------------------------------------------------------------------------- -// Culvert data structure -//----------------------------------------------------------------------------- -typedef struct -{ - double yFull; // full depth of culvert (ft) - double scf; // slope correction factor - double dQdH; // Derivative of flow w.r.t. head - double qc; // Unsubmerged critical flow - double kk; - double mm; // Coeffs. for unsubmerged flow - double ad; - double hPlus; // Intermediate terms - TXsect* xsect; // Pointer to culvert cross section -} TCulvert; - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// double culvert_getInflow - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static double getUnsubmergedFlow(int code, double h, TCulvert* culvert); -static double getSubmergedFlow(int code, double h, TCulvert* culvert); -static double getTransitionFlow(int code, double h, double h1, double h2, - TCulvert* culvert); -static double getForm1Flow(double h, TCulvert* culvert); -static double form1Eqn(double yc, void* p); -/* -static void report_CulvertControl(int j, double q0, double q, int condition, - double yRatio); //for debugging only -*/ - -//============================================================================= - -double culvert_getInflow(int j, double q0, double h) -// -// Input: j = link index -// q0 = unmodified flow rate (cfs) -// h = upstream head (ft) -// Output: returns modified flow rate through culvert (cfs) -// Purpose: uses FHWA HEC-5 equations to find flow through inlet -// controlled culverts -// -{ - int code, //culvert type code number - k, //conduit index - condition; //flow condition - double y, //current depth (ft) - y1, //unsubmerged depth limit (ft) - y2, //submerged depth limit (ft) - q; //inlet-controlled flow (cfs) - TCulvert culvert; //intermediate results - - // --- check that we have a culvert conduit - if ( Link[j].type != CONDUIT ) return q0; - culvert.xsect = &Link[j].xsect; - code = culvert.xsect->culvertCode; - if ( code <= 0 || code > MAX_CULVERT_CODE ) return q0; - - // --- compute often-used variables - k = Link[j].subIndex; - culvert.yFull = culvert.xsect->yFull; - culvert.ad = culvert.xsect->aFull * sqrt(culvert.yFull); - - // --- slope correction factor (-7 for mitered inlets, 0.5 for others) - switch (code) - { - case 5: - case 37: - case 46: culvert.scf = -7.0 * Conduit[k].slope; break; - default: culvert.scf = 0.5 * Conduit[k].slope; - } - - // --- find head relative to culvert's upstream invert - // (can be greater than yFull when inlet is submerged) - y = h - (Node[Link[j].node1].invertElev + Link[j].offset1); - - // --- check for submerged flow (based on FHWA criteria of Q/AD > 4) - y2 = culvert.yFull * (16.0 * Params[code][C] + Params[code][Y] - culvert.scf); - if ( y >= y2 ) - { - q = getSubmergedFlow(code, y, &culvert); - condition = 2; - } - else - { - // --- check for unsubmerged flow (based on arbitrary limit of 0.95 full) - y1 = 0.95 * culvert.yFull; - if ( y <= y1 ) - { - q = getUnsubmergedFlow(code, y, &culvert); - condition = 1; - } - // --- flow is in transition zone - else - { - q = getTransitionFlow(code, y, y1, y2, &culvert); - condition = 0; - } - } - - // --- check if inlet controls and replace conduit's value of dq/dh - if ( q < q0 ) - { - // --- for debugging only - //if ( RptFlags.controls ) report_CulvertControl(j, q0, q, condition, - // y / culvert.yFull); - - Link[j].inletControl = TRUE; - Link[j].dqdh = culvert.dQdH; - return q; - } - else return q0; -} - -//============================================================================= - -double getUnsubmergedFlow(int code, double h, TCulvert* culvert) -// -// Input: code = culvert type code number -// h = inlet water depth above culvert invert -// culvert = pointer to a culvert data structure -// Output: returns flow rate; -// computes value of variable Dqdh -// Purpose: computes flow rate and its derivative for unsubmerged -// culvert inlet. -// -{ - double arg; - double q; - - // --- assign shared variables - culvert->kk = Params[code][K]; - culvert->mm = Params[code][M]; - arg = h / culvert->yFull / culvert->kk; - - // --- evaluate correct equation form - if ( Params[code][FORM] == 1.0) - { - q = getForm1Flow(h, culvert); - } - else q = culvert->ad * pow(arg, 1.0/culvert->mm); - culvert->dQdH = q / h / culvert->mm; - return q; -} - -//============================================================================= - -double getSubmergedFlow(int code, double h, TCulvert* culvert) -// -// Input: code = culvert type code number -// h = inlet head (ft) -// culvert = pointer to a culvert data structure -// Output: returns flow rate; -// computes value of Dqdh -// Purpose: computes flow rate and its derivative for submerged -// culvert inlet. -// -{ - double cc = Params[code][C]; - double yy = Params[code][Y]; - double arg = (h/culvert->yFull - yy + culvert->scf) / cc ; - double q; - - if ( arg <= 0.0 ) - { - culvert->dQdH = 0.0; - return BIG; - } - q = sqrt(arg) * culvert->ad; - culvert->dQdH = 0.5 * q / arg / culvert->yFull / cc; - return q; -} - -//============================================================================= - -double getTransitionFlow(int code, double h, double h1, double h2, TCulvert* culvert) -// -// Input: code = culvert type code number -// h = inlet water depth above culvert invert (ft) -// h1 = head limit for unsubmerged condition (ft) -// h2 = head limit for submerged condition (ft) -// culvert = pointer to a culvert data structure -// Output: returns flow rate )cfs); -// computes value of Dqdh (cfs/ft) -// Purpose: computes flow rate and its derivative for inlet-controlled flow -// when inlet water depth lies in the transition range between -// submerged and unsubmerged conditions. -// -{ - double q1 = getUnsubmergedFlow(code, h1, culvert); - double q2 = getSubmergedFlow(code, h2, culvert); - double q = q1 + (q2 - q1) * (h - h1) / (h2 - h1); - culvert->dQdH = (q2 - q1) / (h2 - h1); - return q; -} - -//============================================================================= - -double getForm1Flow(double h, TCulvert* culvert) -// -// Input: h = inlet water depth above culvert invert -// culvert = pointer to a culvert data structure -// Output: returns inlet controlled flow rate -// Purpose: computes inlet-controlled flow rate for unsubmerged culvert -// using FHWA Equation Form1. -// -// See pages 195-196 of FHWA HEC-5 (2001) for details. -// -{ - // --- save re-used terms in culvert structure - culvert->hPlus = h / culvert->yFull + culvert->scf; - - // --- use Ridder's method to solve Equation Form 1 for critical depth - // between a range of 0.01h and h - findroot_Ridder(0.01*h, h, 0.001, form1Eqn, culvert); - - // --- return the flow value used in evaluating Equation Form 1 - return culvert->qc; -} - -//============================================================================= - -double form1Eqn(double yc, void* p) -// -// Input: yc = critical depth -// p = pointer to a TCulvert object -// Output: returns residual error -// Purpose: evaluates the error in satisfying FHWA culvert Equation Form1: -// -// h/yFull + 0.5*s = yc/yFull + yh/2/yFull + K[ac/aFull*sqrt(g*yh/yFull)]^M -// -// for a given value of critical depth yc where: -// h = inlet depth above culvert invert -// s = culvert slope -// yFull = full depth of culvert -// yh = hydraulic depth at critical depth -// ac = flow area at critical depth -// g = accel. of gravity -// K and M = coefficients -// -{ - double ac, wc, yh; - TCulvert* culvert = (TCulvert *)p; - - ac = xsect_getAofY(culvert->xsect, yc); - wc = xsect_getWofY(culvert->xsect, yc); - yh = ac/wc; - - culvert->qc = ac * sqrt(GRAVITY * yh); - return culvert->hPlus - yc/culvert->yFull - yh/2.0/culvert->yFull - - culvert->kk * pow(culvert->qc/culvert->ad, culvert->mm); -} - -//============================================================================= -/* -void report_CulvertControl(int j, double q0, double q, int condition, double yRatio) -// -// Used for debugging only -// -{ - static char* conditionTxt[] = {"transition", "unsubmerged", "submerged"}; - char theDate[12]; - char theTime[9]; - DateTime aDate = getDateTime(NewRoutingTime); - datetime_dateToStr(aDate, theDate); - datetime_timeToStr(aDate, theTime); - fprintf(Frpt.file, - "\n %11s: %8s Culvert %s flow reduced from %.3f to %.3f cfs for %s flow (%.2f).", - theDate, theTime, Link[j].ID, q0, q, conditionTxt[condition], yRatio); -} -*/ diff --git a/src/datetime.c b/src/datetime.c deleted file mode 100644 index d96ef7f3b..000000000 --- a/src/datetime.c +++ /dev/null @@ -1,528 +0,0 @@ -//----------------------------------------------------------------------------- -// datetime.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// DateTime functions. -// -// Update History -// ============== -// Build 5.1.011: -// - decodeTime() no longer rounds up. -// - New getTimeStamp function added. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include -#include "datetime.h" - -// Macro to convert charcter x to upper case -#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const char* MonthTxt[] = - {"JAN", "FEB", "MAR", "APR", - "MAY", "JUN", "JUL", "AUG", - "SEP", "OCT", "NOV", "DEC"}; -static const int DaysPerMonth[2][12] = // days per month - {{31, 28, 31, 30, 31, 30, // normal years - 31, 31, 30, 31, 30, 31}, - {31, 29, 31, 30, 31, 30, // leap years - 31, 31, 30, 31, 30, 31}}; -static const int DateDelta = 693594; // days since 01/01/00 -static const double SecsPerDay = 86400.; // seconds per day - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -static int DateFormat; - - -//============================================================================= - -void divMod(int n, int d, int* result, int* remainder) - -// Input: n = numerator -// d = denominator -// Output: result = integer part of n/d -// remainder = remainder of n/d -// Purpose: finds integer part and remainder of n/d. - -{ - if (d == 0) - { - *result = 0; - *remainder = 0; - } - else - { - *result = n/d; - *remainder = n - d*(*result); - } -} - -//============================================================================= - -int isLeapYear(int year) - -// Input: year = a year -// Output: returns 1 if year is a leap year, 0 if not -// Purpose: determines if year is a leap year. - -{ - if ((year % 4 == 0) - && ((year % 100 != 0) - || (year % 400 == 0))) return 1; - else return 0; -} - -//============================================================================= - -int datetime_findMonth(char* month) - -// Input: month = month of year as character string -// Output: returns: month of year as a number (1-12) -// Purpose: finds number (1-12) of month. - -{ - int i; - for (i = 0; i < 12; i++) - { - if (UCHAR(month[0]) == MonthTxt[i][0] - && UCHAR(month[1]) == MonthTxt[i][1] - && UCHAR(month[2]) == MonthTxt[i][2]) return i+1; - } - return 0; -} - -//============================================================================= - -DateTime datetime_encodeDate(int year, int month, int day) - -// Input: year = a year -// month = a month (1 to 12) -// day = a day of month -// Output: returns encoded value of year-month-day -// Purpose: encodes year-month-day to a DateTime value. - -{ - int i, j; - i = isLeapYear(year); - if ((year >= 1) - && (year <= 9999) - && (month >= 1) - && (month <= 12) - && (day >= 1) - && (day <= DaysPerMonth[i][month-1])) - { - for (j = 0; j < month-1; j++) day += DaysPerMonth[i][j]; - i = year - 1; - i = i*365 + i/4 - i/100 + i/400 + day - DateDelta; - return i; - } - else return -DateDelta; -} - -//============================================================================= - -DateTime datetime_encodeTime(int hour, int minute, int second) - -// Input: hour = hour of day (0-24) -// minute = minute of hour (0-60) -// second = seconds of minute (0-60) -// Output: returns time encoded as fractional part of a day -// Purpose: encodes hour:minute:second to a DateTime value - -{ - int s; - if ((hour >= 0) - && (minute >= 0) - && (second >= 0)) - { - s = (hour * 3600 + minute * 60 + second); - return (double)s/SecsPerDay; - } - else return 0.0; -} - -//============================================================================= - -void datetime_decodeDate(DateTime date, int* year, int* month, int* day) - -// Input: date = encoded date/time value -// Output: year = 4-digit year -// month = month of year (1-12) -// day = day of month -// Purpose: decodes DateTime value to year-month-day. - -{ - int D1, D4, D100, D400; - int y, m, d, i, k, t; - - D1 = 365; //365 - D4 = D1 * 4 + 1; //1461 - D100 = D4 * 25 - 1; //36524 - D400 = D100 * 4 + 1; //146097 - - t = (int)(floor (date)) + DateDelta; - if (t <= 0) - { - *year = 0; - *month = 1; - *day = 1; - } - else - { - t--; - y = 1; - while (t >= D400) - { - t -= D400; - y += 400; - } - divMod(t, D100, &i, &d); - if (i == 4) - { - i--; - d += D100; - } - y += i*100; - divMod(d, D4, &i, &d); - y += i*4; - divMod(d, D1, &i, &d); - if (i == 4) - { - i--; - d += D1; - } - y += i; - k = isLeapYear(y); - m = 1; - for (;;) - { - i = DaysPerMonth[k][m-1]; - if (d < i) break; - d -= i; - m++; - } - *year = y; - *month = m; - *day = d + 1; - } -} - -//============================================================================= - -void datetime_decodeTime(DateTime time, int* h, int* m, int* s) - -// Input: time = decimal fraction of a day -// Output: h = hour of day (0-23) -// m = minute of hour (0-59) -// s = second of minute (0-59) -// Purpose: decodes DateTime value to hour:minute:second. - -{ - int secs; - int mins; - double fracDay = (time - floor(time)) * SecsPerDay; - secs = (int)(floor(fracDay + 0.5)); - if ( secs >= 86400 ) secs = 86399; - divMod(secs, 60, &mins, s); - divMod(mins, 60, h, m); - if ( *h > 23 ) *h = 0; -} - -//============================================================================= - -void datetime_dateToStr(DateTime date, char* s) - -// Input: date = encoded date/time value -// Output: s = formatted date string -// Purpose: represents DateTime date value as a formatted string. - -{ - int y, m, d; - datetime_decodeDate(date, &y, &m, &d); - switch (DateFormat) - { - case Y_M_D: - snprintf(s, DATE_STR_SIZE, "%4d-%3s-%02d", y, MonthTxt[m-1], d); - break; - - case M_D_Y: - //sprintf(dateStr, "%3s-%02d-%4d", MonthTxt[m-1], d, y); - snprintf(s, DATE_STR_SIZE, "%02d/%02d/%04d", m, d, y); - break; - - default: - snprintf(s, DATE_STR_SIZE, "%02d-%3s-%4d", d, MonthTxt[m-1], y); - } -} - -void datetime_timeToStr(DateTime time, char* s) - -// Input: time = decimal fraction of a day -// Output: s = time in hr:min:sec format -// Purpose: represents DateTime time value as a formatted string. - -{ - int hr, min, sec; - datetime_decodeTime(time, &hr, &min, &sec); - snprintf(s, TIME_STR_SIZE, "%02d:%02d:%02d", hr, min, sec); -} - -//============================================================================= - -int datetime_strToDate(char* s, DateTime* d) - -// Input: s = date as string -// Output: d = encoded date; -// returns 1 if conversion successful, 0 if not -// Purpose: converts string date s to DateTime value. -// -{ - int yr = 0, mon = 0, day = 0, n; - char month[4]; - char sep1, sep2; - *d = -DateDelta; - if (strchr(s, '-') || strchr(s, '/')) - { - switch (DateFormat) - { - case Y_M_D: - n = sscanf(s, "%d%c%d%c%d", &yr, &sep1, &mon, &sep2, &day); - if ( n < 3 ) - { - mon = 0; - n = sscanf(s, "%d%c%3s%c%d", &yr, &sep1, month, &sep2, &day); - if ( n < 3 ) return 0; - } - break; - - case D_M_Y: - n = sscanf(s, "%d%c%d%c%d", &day, &sep1, &mon, &sep2, &yr); - if ( n < 3 ) - { - mon = 0; - n = sscanf(s, "%d%c%3s%c%d", &day, &sep1, month, &sep2, &yr); - if ( n < 3 ) return 0; - } - break; - - default: // M_D_Y - n = sscanf(s, "%d%c%d%c%d", &mon, &sep1, &day, &sep2, &yr); - if ( n < 3 ) - { - mon = 0; - n = sscanf(s, "%3s%c%d%c%d", month, &sep1, &day, &sep2, &yr); - if ( n < 3 ) return 0; - } - } - if (mon == 0) mon = datetime_findMonth(month); - *d = datetime_encodeDate(yr, mon, day); - } - if (*d == -DateDelta) return 0; - else return 1; -} - -//============================================================================= - -int datetime_strToTime(char* s, DateTime* t) - -// Input: s = time as string -// Output: t = encoded time, -// returns 1 if conversion successful, 0 if not -// Purpose: converts a string time to a DateTime value. -// Note: accepts time as hr:min:sec or as decimal hours. - -{ - int n, hr, min = 0, sec = 0; - char *endptr; - - // Attempt to read time as decimal hours - *t = strtod(s, &endptr); - if ( *endptr == 0 ) - { - *t /= 24.0; - return 1; - } - - // Read time in hr:min:sec format - *t = 0.0; - n = sscanf(s, "%d:%d:%d", &hr, &min, &sec); - if ( n == 0 ) return 0; - *t = datetime_encodeTime(hr, min, sec); - if ( (hr >= 0) && (min >= 0) && (sec >= 0) ) return 1; - else return 0; -} - -//============================================================================= - -void datetime_setDateFormat(int fmt) - -// Input: fmt = date format code -// Output: none -// Purpose: sets date format - -{ - if ( fmt >= Y_M_D && fmt <= M_D_Y) DateFormat = fmt; -} - -//============================================================================= - -DateTime datetime_addSeconds(DateTime date1, double seconds) - -// Input: date1 = an encoded date/time value -// seconds = number of seconds to add to date1 -// Output: returns updated value of date1 -// Purpose: adds a given number of seconds to a date/time. - -{ - double d = floor(date1); - int h, m, s; - datetime_decodeTime(date1, &h, &m, &s); - return d + (3600.0*h + 60.0*m + s + seconds)/SecsPerDay; -} - -//============================================================================= - -DateTime datetime_addDays(DateTime date1, DateTime date2) - -// Input: date1 = an encoded date/time value -// date2 = decimal days to be added to date1 -// Output: returns date1 + date2 -// Purpose: adds a given number of decimal days to a date/time. - -{ - double d1 = floor(date1); - double d2 = floor(date2); - int h1, m1, s1; - int h2, m2, s2; - datetime_decodeTime(date1, &h1, &m1, &s1); - datetime_decodeTime(date2, &h2, &m2, &s2); - return d1 + d2 + datetime_encodeTime(h1+h2, m1+m2, s1+s2); -} - -//============================================================================= - -long datetime_timeDiff(DateTime date1, DateTime date2) - -// Input: date1 = an encoded date/time value -// date2 = an encoded date/time value -// Output: returns date1 - date2 in seconds -// Purpose: finds number of seconds between two dates. - -{ - double d1 = floor(date1); - double d2 = floor(date2); - int h, m, s; - long s1, s2, secs; - datetime_decodeTime(date1, &h, &m, &s); - s1 = 3600*h + 60*m + s; - datetime_decodeTime(date2, &h, &m, &s); - s2 = 3600*h + 60*m + s; - secs = (int)(floor((d1 - d2)*SecsPerDay + 0.5)); - secs += (s1 - s2); - return secs; -} - -//============================================================================= - -int datetime_monthOfYear(DateTime date) - -// Input: date = an encoded date/time value -// Output: returns index of month of year (1..12) -// Purpose: finds month of year (Jan = 1 ...) for a given date. - -{ - int year, month, day; - datetime_decodeDate(date, &year, &month, &day); - return month; -} - -//============================================================================= - -int datetime_dayOfYear(DateTime date) - -// Input: date = an encoded date/time value -// Output: returns day of year (1..365) -// Purpose: finds day of year (Jan 1 = 1) for a given date. - -{ - int year, month, day; - DateTime startOfYear; - datetime_decodeDate(date, &year, &month, &day); - startOfYear = datetime_encodeDate(year, 1, 1); - return (int)(floor(date - startOfYear)) + 1; -} - -//============================================================================= - -int datetime_dayOfWeek(DateTime date) - -// Input: date = an encoded date/time value -// Output: returns index of day of week (1..7) -// Purpose: finds day of week (Sun = 1, ... Sat = 7) for a given date. - -{ - int t = (int)(floor(date)) + DateDelta; - return (t % 7) + 1; -} - -//============================================================================= - -int datetime_hourOfDay(DateTime date) - -// Input: date = an encoded date/time value -// Output: returns hour of day (0..23) -// Purpose: finds hour of day (0 = 12 AM, ..., 23 = 11 PM) for a given date. - -{ - int hour, min, sec; - datetime_decodeTime(date, &hour, &min, &sec); - return hour; -} - -//============================================================================= - -int datetime_daysPerMonth(int year, int month) - -// Input: year = year in which month falls -// month = month of year (1..12) -// Output: returns number of days in the month -// Purpose: finds number of days in a given month of a specified year. - -{ - if ( month < 1 || month > 12 ) return 0; - return DaysPerMonth[isLeapYear(year)][month-1]; -} - -//============================================================================= - -void datetime_getTimeStamp(int fmt, DateTime aDate, int stampSize, char* timeStamp) - -// Input: fmt = desired date format code -// aDate = a date/time value in decimal days -// stampSize = the number of bytes allocated for the time stamp -// Output: returns a time stamp string (e.g., Year-Month-Day Hr:Min:Sec) -// Purpose: Expresses a decimal day date by a time stamp. -{ - char dateStr[DATE_STR_SIZE]; - char timeStr[TIME_STR_SIZE]; - int oldDateFormat = DateFormat; - - if ( stampSize < TIME_STAMP_SIZE ) return; - datetime_setDateFormat(fmt); - datetime_dateToStr(aDate, dateStr); - DateFormat = oldDateFormat; - datetime_timeToStr(aDate, timeStr); - snprintf(timeStamp, stampSize, "%s %s", dateStr, timeStr); -} diff --git a/src/datetime.h b/src/datetime.h deleted file mode 100644 index 98b87b48e..000000000 --- a/src/datetime.h +++ /dev/null @@ -1,72 +0,0 @@ -//----------------------------------------------------------------------------- -// datetime.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// The DateTime type is used to store date and time values. It is -// equivalent to a double floating point type. -// -// The integral part of a DateTime value is the number of days that have -// passed since 12/31/1899. The fractional part of a DateTime value is the -// fraction of a 24 hour day that has elapsed. -// -// Update History -// ============== -// Build 5.1.011: -// - New getTimeStamp function added. -//----------------------------------------------------------------------------- - -#ifndef DATETIME_H -#define DATETIME_H - - -typedef double DateTime; - -#define Y_M_D 0 -#define M_D_Y 1 -#define D_M_Y 2 -#define NO_DATE -693594 // 1/1/0001 -#define DATE_STR_SIZE 12 -#define TIME_STR_SIZE 9 -#define TIME_STAMP_SIZE 21 - -// Functions for encoding a date or time value to a DateTime value -DateTime datetime_encodeDate(int year, int month, int day); -DateTime datetime_encodeTime(int hour, int minute, int second); - -// Functions for decoding a DateTime value to a date and time -void datetime_decodeDate(DateTime date, int* y, int* m, int* d); -void datetime_decodeTime(DateTime time, int* h, int* m, int* s); - -// Function for finding day of week for a date (1 = Sunday) -// month of year, days per month, and hour of day -int datetime_monthOfYear(DateTime date); -int datetime_dayOfYear(DateTime date); -int datetime_dayOfWeek(DateTime date); -int datetime_hourOfDay(DateTime date); -int datetime_daysPerMonth(int year, int month); - -// Functions for converting a DateTime value to a string -void datetime_dateToStr(DateTime date, char* s); -void datetime_timeToStr(DateTime time, char* s); -void datetime_getTimeStamp(int fmt, DateTime aDate, int stampSize, - char* timeStamp); - -// Functions for converting a string date or time to a DateTime value -int datetime_findMonth(char* s); -int datetime_strToDate(char* s, DateTime* d); -int datetime_strToTime(char* s, DateTime* t); - -// Function for setting date format -void datetime_setDateFormat(int fmt); - -// Functions for adding and subtracting dates -DateTime datetime_addSeconds(DateTime date1, double seconds); -DateTime datetime_addDays(DateTime date1, DateTime date2); -long datetime_timeDiff(DateTime date1, DateTime date2); - - -#endif //DATETIME_H diff --git a/src/dwflow.c b/src/dwflow.c deleted file mode 100644 index 14338b2de..000000000 --- a/src/dwflow.c +++ /dev/null @@ -1,683 +0,0 @@ -//----------------------------------------------------------------------------- -// dwflow.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// M. Tryby (EPA) -// R. Dickinson (CDM) -// -// Solves the momentum equation for flow in a conduit under dynamic wave -// flow routing. -// -// Update History -// ============== -// Build 5.1.008: -// - Bug in finding if conduit was upstrm/dnstrm full was fixed. -// Build 5.1.012: -// - Modified uniform loss rate term of conduit momentum equation. -// Build 5.1.013: -// - Preissmann slot surcharge option implemented. -// - Changed sign of uniform loss rate term (dq6) in flow updating equation. -// Build 5.1.014: -// - Conduit evap. and seepage loss initialized to 0 in dwflow_findConduitFlow. -// - Most current flow (qLast) used instead of previous time period flow -// (qOld) in call to link_getLossRate. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "headers.h" - -static const double MAXVELOCITY = 50.; // max. allowable velocity (ft/sec) - -static int getFlowClass(int link, double q, double h1, double h2, - double y1, double y2, double* criticalDepth, double* normalDepth, - double* fasnh); -static void findSurfArea(int link, double q, double length, double* h1, - double* h2, double* y1, double* y2); -static double findLocalLosses(int link, double a1, double a2, double aMid, - double q); - -static double getWidth(TXsect* xsect, double y); -static double getSlotWidth(TXsect* xsect, double y); -static double getArea(TXsect* xsect, double y, double wSlot); -static double getHydRad(TXsect* xsect, double y); - -static double checkNormalFlow(int j, double q, double y1, double y2, - double a1, double r1); - -//============================================================================= - -void dwflow_findConduitFlow(int j, int steps, double omega, double dt) -// -// Input: j = link index -// steps = number of iteration steps taken -// omega = under-relaxation parameter -// dt = time step (sec) -// Output: returns new flow value (cfs) -// Purpose: updates flow in conduit link by solving finite difference -// form of continuity and momentum equations. -// -{ - int k; // index of conduit - int n1, n2; // indexes of end nodes - double z1, z2; // upstream/downstream invert elev. (ft) - double h1, h2; // upstream/dounstream flow heads (ft) - double y1, y2; // upstream/downstream flow depths (ft) - double a1, a2; // upstream/downstream flow areas (ft2) - double r1; // upstream hyd. radius (ft) - double yMid, rMid, aMid; // mid-stream or avg. values of y, r, & a - double aWtd, rWtd; // upstream weighted area & hyd. radius - double qLast; // flow from previous iteration (cfs) - double qOld; // flow from previous time step (cfs) - double aOld; // area from previous time step (ft2) - double v; // velocity (ft/sec) - double rho; // upstream weighting factor - double sigma; // inertial damping factor - double length; // effective conduit length (ft) - double wSlot; // Preissmann slot width (ft) - double dq1, dq2, dq3, dq4, dq5, // terms in momentum eqn. - dq6; // term for evap and infil losses - double denom; // denominator of flow update formula - double q; // new flow value (cfs) - double barrels; // number of barrels in conduit - TXsect* xsect = &Link[j].xsect; // ptr. to conduit's cross section data - char isFull = FALSE; // TRUE if conduit flowing full - char isClosed = FALSE; // TRUE if conduit closed - - - - // --- adjust isClosed status by any control action - if ( Link[j].setting == 0 ) isClosed = TRUE; - - // --- get flow from last time step & previous iteration - k = Link[j].subIndex; - barrels = Conduit[k].barrels; - qOld = Link[j].oldFlow / barrels; - qLast = Conduit[k].q1; - Conduit[k].evapLossRate = 0.0; - Conduit[k].seepLossRate = 0.0; - - // --- get most current heads at upstream and downstream ends of conduit - n1 = Link[j].node1; - n2 = Link[j].node2; - z1 = Node[n1].invertElev + Link[j].offset1; - z2 = Node[n2].invertElev + Link[j].offset2; - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - h1 = MAX(h1, z1); - h2 = MAX(h2, z2); - - // --- get unadjusted upstream and downstream flow depths in conduit - // (flow depth = head in conduit - elev. of conduit invert) - y1 = h1 - z1; - y2 = h2 - z2; - y1 = MAX(y1, FUDGE); - y2 = MAX(y2, FUDGE); - - // --- flow depths can't exceed full depth of conduit if slot not used - if ( SurchargeMethod != SLOT ) - { - y1 = MIN(y1, xsect->yFull); - y2 = MIN(y2, xsect->yFull); - } - - // -- get area from solution at previous time step - aOld = Conduit[k].a2; - aOld = MAX(aOld, FUDGE); - - // --- use Courant-modified length instead of conduit's actual length - length = Conduit[k].modLength; - - // --- find surface area contributions to upstream and downstream nodes - // based on previous iteration's flow estimate - findSurfArea(j, qLast, length, &h1, &h2, &y1, &y2); - - // --- compute area at each end of conduit & hyd. radius at upstream end - wSlot = getSlotWidth(xsect, y1); - a1 = getArea(xsect, y1, wSlot); - r1 = getHydRad(xsect, y1); - wSlot = getSlotWidth(xsect, y2); - a2 = getArea(xsect, y2, wSlot); - - // --- compute area & hyd. radius at midpoint - yMid = 0.5 * (y1 + y2); - wSlot = getSlotWidth(xsect, yMid); - aMid = getArea(xsect, yMid, wSlot); - rMid = getHydRad(xsect, yMid); - - // --- alternate approach not currently used, but might produce better - // Bernoulli energy balance for steady flows - //aMid = (a1+a2)/2.0; - //rMid = (r1+getHydRad(xsect,y2))/2.0; - - // --- check if conduit is flowing full - if ( y1 >= xsect->yFull && - y2 >= xsect->yFull) isFull = TRUE; - - // --- set new flow to zero if conduit is dry or if flap gate is closed - if ( Link[j].flowClass == DRY || - Link[j].flowClass == UP_DRY || - Link[j].flowClass == DN_DRY || - isClosed || - aMid <= FUDGE ) - { - Conduit[k].a1 = 0.5 * (a1 + a2); - Conduit[k].q1 = 0.0;; - Conduit[k].q2 = 0.0; - Link[j].dqdh = GRAVITY * dt * aMid / length * barrels; - Link[j].froude = 0.0; - Link[j].newDepth = MIN(yMid, Link[j].xsect.yFull); - Link[j].newVolume = Conduit[k].a1 * link_getLength(j) * barrels; - Link[j].newFlow = 0.0; - return; - } - - // --- compute velocity from last flow estimate - v = qLast / aMid; - if ( fabs(v) > MAXVELOCITY ) v = MAXVELOCITY * SGN(qLast); - - // --- compute Froude No. - Link[j].froude = link_getFroude(j, v, yMid); - if ( Link[j].flowClass == SUBCRITICAL && - Link[j].froude > 1.0 ) Link[j].flowClass = SUPCRITICAL; - - // --- find inertial damping factor (sigma) - if ( Link[j].froude <= 0.5 ) sigma = 1.0; - else if ( Link[j].froude >= 1.0 ) sigma = 0.0; - else sigma = 2.0 * (1.0 - Link[j].froude); - - // --- get upstream-weighted area & hyd. radius based on damping factor - // (modified version of R. Dickinson's slope weighting) - rho = 1.0; - if ( !isFull && qLast > 0.0 && h1 >= h2 ) rho = sigma; - aWtd = a1 + (aMid - a1) * rho; - rWtd = r1 + (rMid - r1) * rho; - - // --- determine how much inertial damping to apply - if ( InertDamping == NO_DAMPING ) sigma = 1.0; - else if ( InertDamping == FULL_DAMPING ) sigma = 0.0; - - // --- use full inertial damping if closed conduit is surcharged - if ( isFull && !xsect_isOpen(xsect->type) ) sigma = 0.0; - - // --- compute terms of momentum eqn.: - // --- 1. friction slope term - if ( xsect->type == FORCE_MAIN && isFull ) - dq1 = dt * forcemain_getFricSlope(j, fabs(v), rMid); - else dq1 = dt * Conduit[k].roughFactor / pow(rWtd, 1.33333) * fabs(v); - - // --- 2. energy slope term - dq2 = dt * GRAVITY * aWtd * (h2 - h1) / length; - - // --- 3 & 4. inertial terms - dq3 = 0.0; - dq4 = 0.0; - if ( sigma > 0.0 ) - { - dq3 = 2.0 * v * (aMid - aOld) * sigma; - dq4 = dt * v * v * (a2 - a1) / length * sigma; - } - - // --- 5. local losses term - dq5 = 0.0; - if ( Conduit[k].hasLosses ) - { - dq5 = findLocalLosses(j, a1, a2, aMid, qLast) / 2.0 / length * dt; - } - - // --- 6. term for evap and seepage losses per unit length - dq6 = link_getLossRate(j, qLast) * 2.5 * dt * v / link_getLength(j); - - // --- combine terms to find new conduit flow - denom = 1.0 + dq1 + dq5; - q = (qOld - dq2 + dq3 + dq4 + dq6) / denom; - - // --- compute derivative of flow w.r.t. head - Link[j].dqdh = 1.0 / denom * GRAVITY * dt * aWtd / length * barrels; - - // --- check if any flow limitation applies - Link[j].inletControl = FALSE; - Link[j].normalFlow = FALSE; - if ( q > 0.0 ) - { - // --- check for inlet controlled culvert flow - if ( xsect->culvertCode > 0 && !isFull ) - q = culvert_getInflow(j, q, h1); - - // --- check for normal flow limitation based on surface slope & Fr - else - if ( y1 < Link[j].xsect.yFull && - ( Link[j].flowClass == SUBCRITICAL || - Link[j].flowClass == SUPCRITICAL ) - ) q = checkNormalFlow(j, q, y1, y2, a1, r1); - } - - // --- apply under-relaxation weighting between new & old flows; - // --- do not allow change in flow direction without first being zero - if ( steps > 0 ) - { - q = (1.0 - omega) * qLast + omega * q; - if ( q * qLast < 0.0 ) q = 0.001 * SGN(q); - } - - // --- check if user-supplied flow limit applies - if ( Link[j].qLimit > 0.0 ) - { - if ( fabs(q) > Link[j].qLimit ) q = SGN(q) * Link[j].qLimit; - } - - // --- check for reverse flow with closed flap gate - if ( link_setFlapGate(j, n1, n2, q) ) q = 0.0; - - // --- do not allow flow out of a dry node - // (as suggested by R. Dickinson) - if( q > FUDGE && Node[n1].newDepth <= FUDGE ) q = FUDGE; - if( q < -FUDGE && Node[n2].newDepth <= FUDGE ) q = -FUDGE; - - // --- save new values of area, flow, depth, & volume - Conduit[k].a1 = aMid; - Conduit[k].q1 = q; - Conduit[k].q2 = q; - Link[j].newDepth = MIN(yMid, xsect->yFull); - aMid = (a1 + a2) / 2.0; -// aMid = MIN(aMid, xsect->aFull); //Slot can have aMid > aFull - Conduit[k].fullState = link_getFullState(a1, a2, xsect->aFull); - Link[j].newVolume = aMid * link_getLength(j) * barrels; - Link[j].newFlow = q * barrels; -} - -//============================================================================= - -int getFlowClass(int j, double q, double h1, double h2, double y1, double y2, - double *yC, double *yN, double* fasnh) -// -// Input: j = conduit link index -// q = current conduit flow (cfs) -// h1 = head at upstream end of conduit (ft) -// h2 = head at downstream end of conduit (ft) -// y1 = upstream flow depth in conduit (ft) -// y2 = downstream flow depth in conduit (ft) -// yC = critical flow depth (ft) -// yN = normal flow depth (ft) -// fasnh = fraction between norm. & crit. depth -// Output: returns flow classification code -// Purpose: determines flow class for a conduit based on depths at each end. -// -{ - int n1, n2; // indexes of upstrm/downstrm nodes - int flowClass; // flow classification code - double ycMin, ycMax; // min/max critical depths (ft) - double z1, z2; // offsets of conduit inverts (ft) - - // --- get upstream & downstream node indexes - n1 = Link[j].node1; - n2 = Link[j].node2; - - // --- get upstream & downstream conduit invert offsets - z1 = Link[j].offset1; - z2 = Link[j].offset2; - - // --- base offset of an outfall conduit on outfall's depth - if ( Node[n1].type == OUTFALL ) z1 = MAX(0.0, (z1 - Node[n1].newDepth)); - if ( Node[n2].type == OUTFALL ) z2 = MAX(0.0, (z2 - Node[n2].newDepth)); - - // --- default class is SUBCRITICAL - flowClass = SUBCRITICAL; - *fasnh = 1.0; - - // --- case where both ends of conduit are wet - if ( y1 > FUDGE && y2 > FUDGE ) - { - if ( q < 0.0 ) - { - // --- upstream end at critical depth if flow depth is - // below conduit's critical depth and an upstream - // conduit offset exists - if ( z1 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - ycMin = MIN(*yN, *yC); - if ( y1 < ycMin ) flowClass = UP_CRITICAL; - } - } - - // --- case of normal direction flow - else - { - // --- downstream end at smaller of critical and normal depth - // if downstream flow depth below this and a downstream - // conduit offset exists - if ( z2 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - ycMin = MIN(*yN, *yC); - ycMax = MAX(*yN, *yC); - if ( y2 < ycMin ) flowClass = DN_CRITICAL; - else if ( y2 < ycMax ) - { - if ( ycMax - ycMin < FUDGE ) *fasnh = 0.0; - else *fasnh = (ycMax - y2) / (ycMax - ycMin); - } - } - } - } - - // --- case where no flow at either end of conduit - else if ( y1 <= FUDGE && y2 <= FUDGE ) flowClass = DRY; - - // --- case where downstream end of pipe is wet, upstream dry - else if ( y2 > FUDGE ) - { - // --- flow classification is UP_DRY if downstream head < - // invert of upstream end of conduit - if ( h2 < Node[n1].invertElev + Link[j].offset1 ) flowClass = UP_DRY; - - // --- otherwise, the downstream head will be >= upstream - // conduit invert creating a flow reversal and upstream end - // should be at critical depth, providing that an upstream - // offset exists (otherwise subcritical condition is maintained) - else if ( z1 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - flowClass = UP_CRITICAL; - } - } - - // --- case where upstream end of pipe is wet, downstream dry - else - { - // --- flow classification is DN_DRY if upstream head < - // invert of downstream end of conduit - if ( h1 < Node[n2].invertElev + Link[j].offset2 ) flowClass = DN_DRY; - - // --- otherwise flow at downstream end should be at critical depth - // providing that a downstream offset exists (otherwise - // subcritical condition is maintained) - else if ( z2 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - flowClass = DN_CRITICAL; - } - } - return flowClass; -} - -//============================================================================= - -void findSurfArea(int j, double q, double length, double* h1, double* h2, - double* y1, double* y2) -// -// Input: j = conduit link index -// q = current conduit flow (cfs) -// length = conduit length (ft) -// h1 = head at upstream end of conduit (ft) -// h2 = head at downstream end of conduit (ft) -// y1 = upstream flow depth (ft) -// y2 = downstream flow depth (ft) -// Output: updated values of h1, h2, y1, & y2; -// Purpose: assigns surface area of conduit to its up and downstream nodes. -// -{ - int n1, n2; // indexes of upstrm/downstrm nodes - double flowDepth1; // flow depth at upstrm end (ft) - double flowDepth2; // flow depth at downstrm end (ft) - double flowDepthMid; // flow depth at midpt. (ft) - double width1; // top width at upstrm end (ft) - double width2; // top width at downstrm end (ft) - double widthMid; // top width at midpt. (ft) - double surfArea1 = 0.0; // surface area at upstream node (ft2) - double surfArea2 = 0.0; // surface area st downstrm node (ft2) - double criticalDepth; // critical flow depth (ft) - double normalDepth; // normal flow depth (ft) - double fullDepth; // full depth (ft) //(5.1.013) - double fasnh = 1.0; // fraction between norm. & crit. depth //(5.1.013) - TXsect* xsect = &Link[j].xsect; // pointer to cross-section data - - // --- get node indexes & current flow depths - n1 = Link[j].node1; - n2 = Link[j].node2; - flowDepth1 = *y1; - flowDepth2 = *y2; - - normalDepth = (flowDepth1 + flowDepth2) / 2.0; - criticalDepth = normalDepth; - - // --- find conduit's flow classification - fullDepth = xsect->yFull; - if (flowDepth1 >= fullDepth && flowDepth2 >= fullDepth) - { - Link[j].flowClass = SUBCRITICAL; - } - else Link[j].flowClass = getFlowClass(j, q, *h1, *h2, *y1, *y2, - &criticalDepth, &normalDepth, &fasnh); - - // --- add conduit's surface area to its end nodes depending on flow class - switch ( Link[j].flowClass ) - { - case SUBCRITICAL: - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width1 = getWidth(xsect, flowDepth1); - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - surfArea1 = (width1 + widthMid) * length / 4.; - surfArea2 = (widthMid + width2) * length / 4. * fasnh; - break; - - case UP_CRITICAL: - flowDepth1 = criticalDepth; - if ( normalDepth < criticalDepth ) flowDepth1 = normalDepth; - flowDepth1 = MAX(flowDepth1, FUDGE); - *h1 = Node[n1].invertElev + Link[j].offset1 + flowDepth1; - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - surfArea2 = (widthMid + width2) * length * 0.5; - break; - - case DN_CRITICAL: - flowDepth2 = criticalDepth; - if ( normalDepth < criticalDepth ) flowDepth2 = normalDepth; - flowDepth2 = MAX(flowDepth2, FUDGE); - *h2 = Node[n2].invertElev + Link[j].offset2 + flowDepth2; - width1 = getWidth(xsect, flowDepth1); - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - widthMid = getWidth(xsect, flowDepthMid); - surfArea1 = (width1 + widthMid) * length * 0.5; - break; - - case UP_DRY: - flowDepth1 = FUDGE; - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width1 = getWidth(xsect, flowDepth1); - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - - // --- assign avg. surface area of downstream half of conduit - // to the downstream node - surfArea2 = (widthMid + width2) * length / 4.; - - // --- if there is no free-fall at upstream end, assign the - // upstream node the avg. surface area of the upstream half - if ( Link[j].offset1 <= 0.0 ) - { - surfArea1 = (width1 + widthMid) * length / 4.; - } - break; - - case DN_DRY: - flowDepth2 = FUDGE; - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width1 = getWidth(xsect, flowDepth1); - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - - // --- assign avg. surface area of upstream half of conduit - // to the upstream node - surfArea1 = (widthMid + width1) * length / 4.; - - // --- if there is no free-fall at downstream end, assign the - // downstream node the avg. surface area of the downstream half - if ( Link[j].offset2 <= 0.0 ) - { - surfArea2 = (width2 + widthMid) * length / 4.; - } - break; - - case DRY: - surfArea1 = FUDGE * length / 2.0; - surfArea2 = surfArea1; - break; - } - Link[j].surfArea1 = surfArea1; - Link[j].surfArea2 = surfArea2; - *y1 = flowDepth1; - *y2 = flowDepth2; -} - -//============================================================================= - -double findLocalLosses(int j, double a1, double a2, double aMid, double q) -// -// Input: j = link index -// a1 = upstream area (ft2) -// a2 = downstream area (ft2) -// aMid = midpoint area (ft2) -// q = flow rate (cfs) -// Output: returns local losses (ft/sec) -// Purpose: computes local losses term of momentum equation. -// -{ - double losses = 0.0; - q = fabs(q); - if ( a1 > FUDGE ) losses += Link[j].cLossInlet * (q/a1); - if ( a2 > FUDGE ) losses += Link[j].cLossOutlet * (q/a2); - if ( aMid > FUDGE ) losses += Link[j].cLossAvg * (q/aMid); - return losses; -} - -//============================================================================= - -double getSlotWidth(TXsect* xsect, double y) -{ - double yNorm = y / xsect->yFull; - - // --- return 0.0 if slot surcharge method not used - if (SurchargeMethod != SLOT || xsect_isOpen(xsect->type) || - yNorm < CrownCutoff) return 0.0; - - // --- for depth > 1.78 * pipe depth, slot width = 1% of max. width - if (yNorm > 1.78) return 0.01 * xsect->wMax; - - // --- otherwise use the Sjoberg formula - return xsect->wMax * 0.5423 * exp(-pow(yNorm, 2.4)); -} - -//============================================================================= - -double getWidth(TXsect* xsect, double y) -// -// Input: xsect = ptr. to conduit cross section -// y = flow depth (ft) -// Output: returns top width (ft) -// Purpose: computes top width of flow surface in conduit. -// -{ - double wSlot = getSlotWidth(xsect, y); - if (wSlot > 0.0) return wSlot; - if (y / xsect->yFull >= CrownCutoff && !xsect_isOpen(xsect->type)) - y = CrownCutoff * xsect->yFull; - return xsect_getWofY(xsect, y); -} - -//============================================================================= - -double getArea(TXsect* xsect, double y, double wSlot) -// -// Input: xsect = ptr. to conduit cross section -// y = flow depth (ft) -// Output: returns flow area (ft2) -// Purpose: computes area of flow cross-section in a conduit. -// -{ - if ( y >= xsect->yFull ) return xsect->aFull + (y - xsect->yFull) * wSlot; - return xsect_getAofY(xsect, y); -} - -//============================================================================= - -double getHydRad(TXsect* xsect, double y) -// -// Input: xsect = ptr. to conduit cross section -// y = flow depth (ft) -// Output: returns hydraulic radius (ft) -// Purpose: computes hydraulic radius of flow cross-section in a conduit. -// -{ - if (y >= xsect->yFull) return xsect->rFull; - return xsect_getRofY(xsect, y); -} - -//============================================================================= - -double checkNormalFlow(int j, double q, double y1, double y2, double a1, - double r1) -// -// Input: j = link index -// q = link flow found from dynamic wave equations (cfs) -// y1 = flow depth at upstream end (ft) -// y2 = flow depth at downstream end (ft) -// a1 = flow area at upstream end (ft2) -// r1 = hyd. radius at upstream end (ft) -// Output: returns modifed flow in link (cfs) -// Purpose: checks if flow in link should be replaced by normal flow. -// -{ - int check = FALSE; - int k = Link[j].subIndex; - int n1 = Link[j].node1; - int n2 = Link[j].node2; - int hasOutfall = (Node[n1].type == OUTFALL || Node[n2].type == OUTFALL); - double qNorm; - double f1; - - // --- check if water surface slope < conduit slope - if ( NormalFlowLtd == SLOPE || NormalFlowLtd == BOTH || hasOutfall ) - { - if ( y1 < y2 ) check = TRUE; - } - - // --- check if Fr >= 1.0 at upstream end of conduit - if ( !check && (NormalFlowLtd == FROUDE || NormalFlowLtd == BOTH) && - !hasOutfall ) - { - if ( y1 > FUDGE && y2 > FUDGE ) - { - f1 = link_getFroude(j, q/a1, y1); - if ( f1 >= 1.0 ) check = TRUE; - } - } - - // --- check if normal flow < dynamic flow - if ( check ) - { - qNorm = Conduit[k].beta * a1 * pow(r1, 2./3.); - if ( qNorm < q ) - { - Link[j].normalFlow = TRUE; - return qNorm; - } - } - return q; -} diff --git a/src/dynwave.c b/src/dynwave.c deleted file mode 100644 index ac302e849..000000000 --- a/src/dynwave.c +++ /dev/null @@ -1,908 +0,0 @@ -//----------------------------------------------------------------------------- -// dynwave.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// M. Tryby (EPA) -// R. Dickinson (CDM) -// -// Dynamic wave flow routing functions. -// -// This module solves the dynamic wave flow routing equations using -// Picard Iterations (i.e., a method of successive approximations) -// to solve the explicit form of the continuity and momentum equations -// for conduits. -// -// Update History -// ============== -// Build 5.1.002: -// - Only non-ponded nodal surface area is saved for use in -// surcharge algorithm. -// Build 5.1.007: -// - Node losses added to node outflow variable instead of treated -// as a separate item when computing change in node flow volume. -// Build 5.1.008: -// - Module-specific constants moved here from project.c. -// - Support added for user-specified minimum variable time step. -// - Node crown elevations found here instead of in flowrout.c module. -// - OpenMP use to parallelize findLinkFlows() & findNodeDepths(). -// - Bug in finding complete list of capacity limited links fixed. -// Build 5.1.011: -// - Added test for failed memory allocation. -// - Fixed illegal array index bug for Ideal Pumps. -// Build 5.1.013: -// - Include omp.h protected against lack of compiler support for OpenMP. -// - SurchargeMethod option used to decide how node surcharging is handled. -// - Storage nodes allowed to pressurize if their surcharge depth > 0. -// - Minimum flow needed to compute a Courant time step modified. -// Build 5.1.014: -// - updateNodeFlows() modified to subtract conduit evap. and seepage losses -// from downstream node inflow instead of upstream node outflow. -// Build 5.1.015: -// - Roll back the 5.1.014 change for conduit losses in updateNodeFlows(). -// Build 5.2.0: -// - Support added for reporting most frequent non-converging links. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double MINTIMESTEP = 0.001; // min. time step (sec) -static const double OMEGA = 0.5; // under-relaxation parameter -static const double DEFAULT_SURFAREA = 12.566; // Min. nodal surface area (~4 ft diam.) -static const double DEFAULT_HEADTOL = 0.005; // Default head tolerance (ft) -static const double EXTRAN_CROWN_CUTOFF = 0.96; // crown cutoff for EXTRAN -static const double SLOT_CROWN_CUTOFF = 0.985257; // crown cutoff for SLOT -static const int DEFAULT_MAXTRIALS = 8; // Max. trials per time step - - -//----------------------------------------------------------------------------- -// Data Structures -//----------------------------------------------------------------------------- -typedef struct -{ - char converged; // TRUE if iterations for a node done - double newSurfArea; // current surface area (ft2) - double oldSurfArea; // previous surface area (ft2) - double sumdqdh; // sum of dqdh from adjoining links - double dYdT; // change in depth w.r.t. time (ft/sec) -} TXnode; - -//----------------------------------------------------------------------------- -// Shared Variables -//----------------------------------------------------------------------------- -static double VariableStep; // size of variable time step (sec) -static TXnode* Xnode; // extended nodal information - -static double Omega; // actual under-relaxation parameter -static int Steps; // number of Picard iterations - -//----------------------------------------------------------------------------- -// Function declarations -//----------------------------------------------------------------------------- -static void initRoutingStep(void); -static void initNodeStates(void); -static void findBypassedLinks(); -static void findLimitedLinks(); - -static void findLinkFlows(double dt); -static int isTrueConduit(int link); -static void findNonConduitFlow(int link, double dt); -static void findNonConduitSurfArea(int link); -static double getModPumpFlow(int link, double q, double dt); -static void updateNodeFlows(int link); -static void updateConvergenceStats(); - -static int findNodeDepths(double dt); -static void setNodeDepth(int node, double dt); -static double getFloodedDepth(int node, int canPond, double dV, double yNew, - double yMax, double dt); - -static double getVariableStep(double maxStep); -static double getLinkStep(double tMin, int *minLink); -static double getNodeStep(double tMin, int *minNode); - -//============================================================================= - -void dynwave_init() -// -// Input: none -// Output: none -// Purpose: initializes dynamic wave routing method. -// -{ - int i, j; - double z; - - VariableStep = 0.0; - Xnode = (TXnode *) calloc(Nobjects[NODE], sizeof(TXnode)); - if ( Xnode == NULL ) - { - report_writeErrorMsg(ERR_MEMORY, - " Not enough memory for dynamic wave routing."); - return; - } - - // --- initialize node surface areas & crown elev. - for (i = 0; i < Nobjects[NODE]; i++ ) - { - Xnode[i].newSurfArea = 0.0; - Xnode[i].oldSurfArea = 0.0; - Node[i].crownElev = Node[i].invertElev; - } - - // --- initialize links & update node crown elevations - for (i = 0; i < Nobjects[LINK]; i++) - { - j = Link[i].node1; - z = Node[j].invertElev + Link[i].offset1 + Link[i].xsect.yFull; - Node[j].crownElev = MAX(Node[j].crownElev, z); - j = Link[i].node2; - z = Node[j].invertElev + Link[i].offset2 + Link[i].xsect.yFull; - Node[j].crownElev = MAX(Node[j].crownElev, z); - Link[i].flowClass = DRY; - Link[i].dqdh = 0.0; - } - - // --- set crown cutoff for finding top width of closed conduits - if ( SurchargeMethod == SLOT ) CrownCutoff = SLOT_CROWN_CUTOFF; - else CrownCutoff = EXTRAN_CROWN_CUTOFF; -} - -//============================================================================= - -void dynwave_close() -// -// Input: none -// Output: none -// Purpose: frees memory allocated for dynamic wave routing method. -// -{ - FREE(Xnode); -} - -//============================================================================= - -void dynwave_validate() -// -// Input: none -// Output: none -// Purpose: adjusts dynamic wave routing options. -// -{ - if ( MinRouteStep > RouteStep ) MinRouteStep = RouteStep; - if ( MinRouteStep < MINTIMESTEP ) MinRouteStep = MINTIMESTEP; - if ( MinSurfArea == 0.0 ) MinSurfArea = DEFAULT_SURFAREA; - else MinSurfArea /= UCF(LENGTH) * UCF(LENGTH); - if ( HeadTol == 0.0 ) HeadTol = DEFAULT_HEADTOL; - else HeadTol /= UCF(LENGTH); - if ( MaxTrials == 0 ) MaxTrials = DEFAULT_MAXTRIALS; -} - -//============================================================================= - -double dynwave_getRoutingStep(double fixedStep) -// -// Input: fixedStep = user-supplied fixed time step (sec) -// Output: returns routing time step (sec) -// Purpose: computes variable routing time step if applicable. -// -{ - // --- use user-supplied fixed step if variable step option turned off - // or if its smaller than the min. allowable variable time step - if ( CourantFactor == 0.0 ) return fixedStep; - if ( fixedStep < MINTIMESTEP ) return fixedStep; - - // --- at start of simulation (when current variable step is zero) - // use the minimum allowable time step - if ( VariableStep == 0.0 ) - { - VariableStep = MinRouteStep; - } - - // --- otherwise compute variable step based on current flow solution - else VariableStep = getVariableStep(fixedStep); - - // --- adjust step to be a multiple of a millisecond - VariableStep = floor(1000.0 * VariableStep) / 1000.0; - return VariableStep; -} - -//============================================================================= - -int dynwave_execute(double tStep) -// -// Input: links = array of topo sorted links indexes -// tStep = time step (sec) -// Output: returns number of iterations used -// Purpose: routes flows through drainage network over current time step. -// -{ - int converged; - - // --- initialize - if ( ErrorCode ) return 0; - Steps = 0; - converged = FALSE; - Omega = OMEGA; - initRoutingStep(); - - // --- keep iterating until convergence - while ( Steps < MaxTrials ) - { - // --- execute a routing step & check for nodal convergence - initNodeStates(); - findLinkFlows(tStep); - converged = findNodeDepths(tStep); - Steps++; - if ( Steps > 1 ) - { - if ( converged ) break; - - // --- check if link calculations can be skipped in next step - findBypassedLinks(); - } - } - if ( !converged ) updateConvergenceStats(); - - // --- identify any capacity-limited conduits - findLimitedLinks(); - return Steps; -} - -//============================================================================= - -void updateConvergenceStats() -{ - int i; - NonConvergeCount++; - for (i = 0; i < Nobjects[NODE]; i++) - stats_updateConvergenceStats(i, Xnode[i].converged); -} - -//============================================================================= - -void initRoutingStep() -{ - int i; - for (i = 0; i < Nobjects[NODE]; i++) - { - Xnode[i].converged = FALSE; - Xnode[i].dYdT = 0.0; - } - for (i = 0; i < Nobjects[LINK]; i++) - { - Link[i].bypassed = FALSE; - Link[i].surfArea1 = 0.0; - Link[i].surfArea2 = 0.0; - } - - // --- a2 preserves conduit area from solution at last time step - for ( i = 0; i < Nlinks[CONDUIT]; i++) Conduit[i].a2 = Conduit[i].a1; -} - -//============================================================================= - -void initNodeStates() -// -// Input: none -// Output: none -// Purpose: initializes node's surface area, inflow & outflow -// -{ - int i; - - for (i = 0; i < Nobjects[NODE]; i++) - { - // --- initialize nodal surface area - if ( AllowPonding ) - { - Xnode[i].newSurfArea = node_getPondedArea(i, Node[i].newDepth); - } - else - { - Xnode[i].newSurfArea = node_getSurfArea(i, Node[i].newDepth); - } - - // --- initialize nodal inflow & outflow - Node[i].inflow = 0.0; - Node[i].outflow = Node[i].losses; - if ( Node[i].newLatFlow >= 0.0 ) - { - Node[i].inflow += Node[i].newLatFlow; - } - else - { - Node[i].outflow -= Node[i].newLatFlow; - } - Xnode[i].sumdqdh = 0.0; - } -} - -//============================================================================= - -void findBypassedLinks() -{ - int i; - for (i = 0; i < Nobjects[LINK]; i++) - { - if ( Xnode[Link[i].node1].converged && - Xnode[Link[i].node2].converged ) - Link[i].bypassed = TRUE; - else Link[i].bypassed = FALSE; - } -} - -//============================================================================= - -void findLimitedLinks() -// -// Input: none -// Output: none -// Purpose: determines if a conduit link is capacity limited. -// -{ - int j, n1, n2, k; - double h1, h2; - - for (j = 0; j < Nobjects[LINK]; j++) - { - // ---- check only non-dummy conduit links - if ( !isTrueConduit(j) ) continue; - - // --- check that upstream end is full - k = Link[j].subIndex; - Conduit[k].capacityLimited = FALSE; - if ( Conduit[k].a1 >= Link[j].xsect.aFull ) - { - // --- check if HGL slope > conduit slope - n1 = Link[j].node1; - n2 = Link[j].node2; - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - if ( (h1 - h2) > fabs(Conduit[k].slope) * Conduit[k].length ) - Conduit[k].capacityLimited = TRUE; - } - } -} - -//============================================================================= - -void findLinkFlows(double dt) -{ - int i; - - // --- find new flow in each non-dummy conduit -#pragma omp parallel num_threads(NumThreads) -{ - #pragma omp for - for ( i = 0; i < Nobjects[LINK]; i++) - { - if ( isTrueConduit(i) && !Link[i].bypassed ) - dwflow_findConduitFlow(i, Steps, Omega, dt); - } -} - - // --- update inflow/outflows for nodes attached to non-dummy conduits - for ( i = 0; i < Nobjects[LINK]; i++) - { - if ( isTrueConduit(i) ) updateNodeFlows(i); - } - - // --- find new flows for all dummy conduits, pumps & regulators - for ( i = 0; i < Nobjects[LINK]; i++) - { - if ( !isTrueConduit(i) ) - { - if ( !Link[i].bypassed ) findNonConduitFlow(i, dt); - updateNodeFlows(i); - } - } -} - -//============================================================================= - -int isTrueConduit(int j) -{ - return ( Link[j].type == CONDUIT && Link[j].xsect.type != DUMMY ); -} - -//============================================================================= - -void findNonConduitFlow(int i, double dt) -// -// Input: i = link index -// dt = time step (sec) -// Output: none -// Purpose: finds new flow in a non-conduit-type link -// -{ - double qLast; // previous link flow (cfs) - double qNew; // new link flow (cfs) - - // --- get link flow from last iteration - qLast = Link[i].newFlow; - Link[i].dqdh = 0.0; - - // --- get new inflow to link from its upstream node - // (link_getInflow returns 0 if flap gate closed or pump is offline) - qNew = link_getInflow(i); - if ( Link[i].type == PUMP ) qNew = getModPumpFlow(i, qNew, dt); - - // --- find surface area at each end of link - findNonConduitSurfArea(i); - - // --- apply under-relaxation with flow from previous iteration; - // --- do not allow flow to change direction without first being 0 - if ( Steps > 0 && Link[i].type != PUMP ) - { - qNew = (1.0 - Omega) * qLast + Omega * qNew; - if ( qNew * qLast < 0.0 ) qNew = 0.001 * SGN(qNew); - } - Link[i].newFlow = qNew; -} - -//============================================================================= - -double getModPumpFlow(int i, double q, double dt) -// -// Input: i = link index -// q = pump flow from pump curve (cfs) -// dt = time step (sec) -// Output: returns modified pump flow rate (cfs) -// Purpose: modifies pump curve pumping rate depending on amount of water -// available at pump's inlet node. -// -{ - int j = Link[i].node1; // pump's inlet node index - int k = Link[i].subIndex; // pump's index - double newNetInflow; // inflow - outflow rate (cfs) - double netFlowVolume; // inflow - outflow volume (ft3) - double y; // node depth (ft) - - if ( q == 0.0 ) return q; - - // --- case where inlet node is a storage node: - // prevent node volume from going negative - if ( Node[j].type == STORAGE ) return node_getMaxOutflow(j, q, dt); - - // --- case where inlet is a non-storage node - switch ( Pump[k].type ) - { - // --- for Type1 pump, a volume is computed for inlet node, - // so make sure it doesn't go negative - case TYPE1_PUMP: - return node_getMaxOutflow(j, q, dt); - - // --- for other types of pumps, if pumping rate would make depth - // at upstream node negative, then set pumping rate = inflow - case TYPE2_PUMP: - case TYPE4_PUMP: - case TYPE3_PUMP: - newNetInflow = Node[j].inflow - Node[j].outflow - q; - netFlowVolume = 0.5 * (Node[j].oldNetInflow + newNetInflow ) * dt; - y = Node[j].oldDepth + netFlowVolume / Xnode[j].newSurfArea; - if ( y <= 0.0 ) return Node[j].inflow; - } - return q; -} - -//============================================================================= - -void findNonConduitSurfArea(int i) -// -// Input: i = link index -// Output: none -// Purpose: finds the surface area contributed by a non-conduit -// link to its upstream and downstream nodes. -// -{ - if ( Link[i].type == ORIFICE ) - { - Link[i].surfArea1 = Orifice[Link[i].subIndex].surfArea / 2.; - } - - // --- no surface area for weirs to maintain SWMM 4 compatibility - else Link[i].surfArea1 = 0.0; - - Link[i].surfArea2 = Link[i].surfArea1; - if ( Link[i].flowClass == UP_CRITICAL || - Node[Link[i].node1].type == STORAGE ) Link[i].surfArea1 = 0.0; - if ( Link[i].flowClass == DN_CRITICAL || - Node[Link[i].node2].type == STORAGE ) Link[i].surfArea2 = 0.0; -} - -//============================================================================= - -void updateNodeFlows(int i) -// -// Input: i = link index -// q = link flow rate (cfs) -// Output: none -// Purpose: updates cumulative inflow & outflow at link's end nodes. -// -{ - int k; - int barrels = 1; - int n1 = Link[i].node1; - int n2 = Link[i].node2; - double q = Link[i].newFlow; - double uniformLossRate = 0.0; - - // --- compute any uniform seepage loss from a conduit - if ( Link[i].type == CONDUIT ) - { - k = Link[i].subIndex; - uniformLossRate = Conduit[k].evapLossRate + Conduit[k].seepLossRate; - barrels = Conduit[k].barrels; - uniformLossRate *= barrels; - } - - // --- update total inflow & outflow at upstream/downstream nodes - if ( q >= 0.0 ) - { - Node[n1].outflow += q + uniformLossRate; - Node[n2].inflow += q; - } - else - { - Node[n1].inflow -= q; - Node[n2].outflow -= q - uniformLossRate; - } - - // --- add surf. area contributions to upstream/downstream nodes - Xnode[Link[i].node1].newSurfArea += Link[i].surfArea1 * barrels; - Xnode[Link[i].node2].newSurfArea += Link[i].surfArea2 * barrels; - - // --- update summed value of dqdh at each end node - Xnode[Link[i].node1].sumdqdh += Link[i].dqdh; - if ( Link[i].type == PUMP ) - { - k = Link[i].subIndex; - if ( Pump[k].type != TYPE4_PUMP ) - { - Xnode[n2].sumdqdh += Link[i].dqdh; - } - } - else Xnode[n2].sumdqdh += Link[i].dqdh; -} - -//============================================================================= - -int findNodeDepths(double dt) -// -// Input: dt = time step (sec) -// Output: returns TRUE if depth change at all non-Outfall nodes is -// within the convergence tolerance and FALSE otherwise -// Purpose: finds new depth at all nodes and checks if convergence achieved. -// -{ - int i; - double yOld; // previous node depth (ft) - - // --- compute outfall depths based on flow in connecting link - for ( i = 0; i < Nobjects[LINK]; i++ ) link_setOutfallDepth(i); - - // --- compute new depth for all non-outfall nodes and determine if - // depth change from previous iteration is below tolerance -#pragma omp parallel num_threads(NumThreads) -{ - #pragma omp for private(yOld) - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - if ( Node[i].type == OUTFALL ) continue; - yOld = Node[i].newDepth; - setNodeDepth(i, dt); - Xnode[i].converged = TRUE; - if ( fabs(yOld - Node[i].newDepth) > HeadTol ) - { - Xnode[i].converged = FALSE; - } - } -} - - // --- return FALSE if any non-Outfall node failed to converge - for (i = 0; i < Nobjects[NODE]; i++) - { - if ( Node[i].type == OUTFALL ) continue; - if (Xnode[i].converged == FALSE) return FALSE; - } - return TRUE; -} - -//============================================================================= - -void setNodeDepth(int i, double dt) -// -// Input: i = node index -// dt = time step (sec) -// Output: none -// Purpose: sets depth at non-outfall node after current time step. -// -{ - int canPond; // TRUE if node can pond overflows - int isPonded; // TRUE if node is currently ponded - int isSurcharged = FALSE; // TRUE if node is surcharged - double dQ; // inflow minus outflow at node (cfs) - double dV; // change in node volume (ft3) - double dy; // change in node depth (ft) - double yMax; // max. depth at node (ft) - double yOld; // node depth at previous time step (ft) - double yLast; // previous node depth (ft) - double yNew; // new node depth (ft) - double yCrown; // depth to node crown (ft) - double surfArea; // node surface area (ft2) - double denom; // denominator term - double corr; // correction factor - double f; // relative surcharge depth - - // --- see if node can pond water above it - canPond = (AllowPonding && Node[i].pondedArea > 0.0); - isPonded = (canPond && Node[i].newDepth > Node[i].fullDepth); - - // --- initialize values - yCrown = Node[i].crownElev - Node[i].invertElev; - yOld = Node[i].oldDepth; - yLast = Node[i].newDepth; - Node[i].overflow = 0.0; - surfArea = Xnode[i].newSurfArea; - surfArea = MAX(surfArea, MinSurfArea); - - // --- determine average net flow volume into node over the time step - dQ = Node[i].inflow - Node[i].outflow; - dV = 0.5 * (Node[i].oldNetInflow + dQ) * dt; - - - // --- determine if node is EXTRAN surcharged - if (SurchargeMethod == EXTRAN) - { - // --- ponded nodes don't surcharge - if (isPonded) isSurcharged = FALSE; - - // --- closed storage units that are full are in surcharge - else if (Node[i].type == STORAGE) - { - isSurcharged = (Node[i].surDepth > 0.0 && - yLast > Node[i].fullDepth); - } - - // --- surcharge occurs when node depth exceeds top of its highest link - else isSurcharged = (yCrown > 0.0 && yLast > yCrown); - } - - // --- if node not surcharged, base depth change on surface area - if (!isSurcharged) - { - dy = dV / surfArea; - yNew = yOld + dy; - - // --- save non-ponded surface area for use in surcharge algorithm - if ( !isPonded ) Xnode[i].oldSurfArea = surfArea; - - // --- apply under-relaxation to new depth estimate - if ( Steps > 0 ) - { - yNew = (1.0 - Omega) * yLast + Omega * yNew; - } - - // --- don't allow a ponded node to drop much below full depth - if ( isPonded && yNew < Node[i].fullDepth ) - yNew = Node[i].fullDepth - FUDGE; - } - - // --- if node surcharged, base depth change on dqdh - // NOTE: depth change is w.r.t depth from previous - // iteration; also, do not apply under-relaxation. - else - { - // --- apply correction factor for upstream terminal nodes - corr = 1.0; - if ( Node[i].degree < 0 ) corr = 0.6; - - // --- allow surface area from last non-surcharged condition - // to influence dqdh if depth close to crown depth - denom = Xnode[i].sumdqdh; - if ( yLast < 1.25 * yCrown ) - { - f = (yLast - yCrown) / yCrown; - denom += (Xnode[i].oldSurfArea/dt - - Xnode[i].sumdqdh) * exp(-15.0 * f); - } - - // --- compute new estimate of node depth - if ( denom == 0.0 ) dy = 0.0; - else dy = corr * dQ / denom; - yNew = yLast + dy; - if ( yNew < yCrown ) yNew = yCrown - FUDGE; - - // --- don't allow a newly ponded node to rise much above full depth - if ( canPond && yNew > Node[i].fullDepth ) - yNew = Node[i].fullDepth + FUDGE; - } - - // --- depth cannot be negative - if ( yNew < 0 ) yNew = 0.0; - - // --- determine max. non-flooded depth - yMax = Node[i].fullDepth; - if ( canPond == FALSE ) yMax += Node[i].surDepth; - - // --- find flooded depth & volume - if ( yNew > yMax ) - { - yNew = getFloodedDepth(i, canPond, dV, yNew, yMax, dt); - } - else Node[i].newVolume = node_getVolume(i, yNew); - - // --- compute change in depth w.r.t. time - Xnode[i].dYdT = fabs(yNew - yOld) / dt; - - // --- save new depth for node - Node[i].newDepth = yNew; -} - -//============================================================================= - -double getFloodedDepth(int i, int canPond, double dV, double yNew, - double yMax, double dt) -// -// Input: i = node index -// canPond = TRUE if water can pond over node -// isPonded = TRUE if water is currently ponded -// dV = change in volume over time step (ft3) -// yNew = current depth at node (ft) -// yMax = max. depth at node before ponding (ft) -// dt = time step (sec) -// Output: returns depth at node when flooded (ft) -// Purpose: computes depth, volume and overflow for a flooded node. -// -{ - if ( canPond == FALSE ) - { - Node[i].overflow = dV / dt; - Node[i].newVolume = Node[i].fullVolume; - yNew = yMax; - } - else - { - Node[i].newVolume = MAX((Node[i].oldVolume+dV), Node[i].fullVolume); - Node[i].overflow = (Node[i].newVolume - - MAX(Node[i].oldVolume, Node[i].fullVolume)) / dt; - } - if ( Node[i].overflow < FUDGE ) Node[i].overflow = 0.0; - return yNew; - -} - -//============================================================================= - -double getVariableStep(double maxStep) -// -// Input: maxStep = user-supplied max. time step (sec) -// Output: returns time step (sec) -// Purpose: finds time step that satisfies stability criterion but -// is no greater than the user-supplied max. time step. -// -{ - int minLink = -1; // index of link w/ min. time step - int minNode = -1; // index of node w/ min. time step - double tMin; // allowable time step (sec) - double tMinLink; // allowable time step for links (sec) - double tMinNode; // allowable time step for nodes (sec) - - // --- find stable time step for links & then nodes - tMin = maxStep; - tMinLink = getLinkStep(tMin, &minLink); - tMinNode = getNodeStep(tMinLink, &minNode); - - // --- use smaller of the link and node time step - tMin = tMinLink; - if ( tMinNode < tMin ) - { - tMin = tMinNode ; - minLink = -1; - } - - // --- update count of times the minimum node or link was critical - stats_updateCriticalTimeCount(minNode, minLink); - - // --- don't let time step go below an absolute minimum - if ( tMin < MinRouteStep ) tMin = MinRouteStep; - return tMin; -} - -//============================================================================= - -double getLinkStep(double tMin, int *minLink) -// -// Input: tMin = critical time step found so far (sec) -// Output: minLink = index of link with critical time step; -// returns critical time step (sec) -// Purpose: finds critical time step for conduits based on Courant criterion. -// -{ - int i; // link index - int k; // conduit index - double q; // conduit flow (cfs) - double t; // time step (sec) - double tLink = tMin; // critical link time step (sec) - - // --- examine each conduit link - for ( i = 0; i < Nobjects[LINK]; i++ ) - { - if ( Link[i].type == CONDUIT ) - { - // --- skip conduits with negligible flow, area or Fr - k = Link[i].subIndex; - q = fabs(Link[i].newFlow) / Conduit[k].barrels; - if ( q <= FUDGE - || Conduit[k].a1 <= FUDGE - || Link[i].froude <= 0.01 - ) continue; - - // --- compute time step to satisfy Courant condition - t = Link[i].newVolume / Conduit[k].barrels / q; - t = t * Conduit[k].modLength / link_getLength(i); - t = t * Link[i].froude / (1.0 + Link[i].froude) * CourantFactor; - - // --- update critical link time step - if ( t < tLink ) - { - tLink = t; - *minLink = i; - } - } - } - return tLink; -} - -//============================================================================= - -double getNodeStep(double tMin, int *minNode) -// -// Input: tMin = critical time step found so far (sec) -// Output: minNode = index of node with critical time step; -// returns critical time step (sec) -// Purpose: finds critical time step for nodes based on max. allowable -// projected change in depth. -// -{ - int i; // node index - double maxDepth; // max. depth allowed at node (ft) - double dYdT; // change in depth per unit time (ft/sec) - double t1; // time needed to reach depth limit (sec) - double tNode = tMin; // critical node time step (sec) - - // --- find smallest time so that estimated change in nodal depth - // does not exceed safety factor * maxdepth - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - // --- see if node can be skipped - if ( Node[i].type == OUTFALL ) continue; - if ( Node[i].newDepth <= FUDGE) continue; - if ( Node[i].newDepth + FUDGE >= - Node[i].crownElev - Node[i].invertElev ) continue; - - // --- define max. allowable depth change using crown elevation - maxDepth = (Node[i].crownElev - Node[i].invertElev) * 0.25; - if ( maxDepth < FUDGE ) continue; - dYdT = Xnode[i].dYdT; - if (dYdT < FUDGE ) continue; - - // --- compute time to reach max. depth & compare with critical time - t1 = maxDepth / dYdT; - if ( t1 < tNode ) - { - tNode = t1; - *minNode = i; - } - } - return tNode; -} diff --git a/src/enums.h b/src/enums.h deleted file mode 100644 index fc3cc2b4b..000000000 --- a/src/enums.h +++ /dev/null @@ -1,497 +0,0 @@ -//----------------------------------------------------------------------------- -// enums.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Enumerated constants -// -// Update History -// ============== -// Build 5.1.004: -// - IGNORE_RDII for the ignore RDII option added. -// Build 5.1.007: -// - s_GWF for [GWF] input file section added. -// - s_ADJUST for [ADJUSTMENTS] input file section added. -// Build 5.1.008: -// - Enumerations for fullness state of a conduit added. -// - NUM_THREADS added for number of parallel threads option. -// - Runoff flow categories added to represent mass balance components. -// Build 5.1.010: -// - New ROADWAY_WEIR type of weir added. -// - Potential evapotranspiration (PET) added as a system output variable. -// Build 5.1.011: -// - s_EVENT added to InputSectionType enumeration. -// Build 5.1.013: -// - SURCHARGE_METHOD and RULE_STEP options added. -// - WEIR_CURVE added as a curve type. -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for variable speed pumps. -// - Support added for analytical storage shapes. -//----------------------------------------------------------------------------- - -#ifndef ENUMS_H -#define ENUMS_H - - -//------------------------------------- -// Names of major object types -//------------------------------------- - enum ObjectType { - GAGE, // rain gage - SUBCATCH, // subcatchment - NODE, // conveyance system node - LINK, // conveyance system link - POLLUT, // pollutant - LANDUSE, // land use category - TIMEPATTERN, // dry weather flow time pattern - CURVE, // generic table of values - TSERIES, // generic time series of values - CONTROL, // conveyance system control rules - TRANSECT, // irregular channel cross-section - AQUIFER, // groundwater aquifer - UNITHYD, // RDII unit hydrograph - SNOWMELT, // snowmelt parameter set - SHAPE, // custom conduit shape - LID, // LID treatment units - STREET, // street cross section - INLET, // street inlet design - MAX_OBJ_TYPES}; - -//------------------------------------- -// Names of Node sub-types -//------------------------------------- - #define MAX_NODE_TYPES 5 - enum NodeType { - JUNCTION, - OUTFALL, - STORAGE, - DIVIDER}; - -//------------------------------------- -// Names of Link sub-types -//------------------------------------- - #define MAX_LINK_TYPES 5 - enum LinkType { - CONDUIT, - PUMP, - ORIFICE, - WEIR, - OUTLET}; - -//------------------------------------- -// File types -//------------------------------------- - enum FileType { - RAINFALL_FILE, // rainfall file - RUNOFF_FILE, // runoff file - HOTSTART_FILE, // hotstart file - RDII_FILE, // RDII file - INFLOWS_FILE, // inflows interface file - OUTFLOWS_FILE}; // outflows interface file - -//------------------------------------- -// File usage types -//------------------------------------- - enum FileUsageType { - NO_FILE, // no file usage - SCRATCH_FILE, // use temporary scratch file - USE_FILE, // use previously saved file - SAVE_FILE}; // save file currently in use - -//------------------------------------- -// Rain gage data types -//------------------------------------- - enum GageDataType { - RAIN_TSERIES, // rainfall from user-supplied time series - RAIN_FILE}; // rainfall from external file - -//------------------------------------- -// Cross section shape types -//------------------------------------- - enum XsectType { - DUMMY, // 0 - CIRCULAR, // 1 closed - FILLED_CIRCULAR, // 2 closed - RECT_CLOSED, // 3 closed - RECT_OPEN, // 4 - TRAPEZOIDAL, // 5 - TRIANGULAR, // 6 - PARABOLIC, // 7 - POWERFUNC, // 8 - RECT_TRIANG, // 9 - RECT_ROUND, // 10 - MOD_BASKET, // 11 - HORIZ_ELLIPSE, // 12 closed - VERT_ELLIPSE, // 13 closed - ARCH, // 14 closed - EGGSHAPED, // 15 closed - HORSESHOE, // 16 closed - GOTHIC, // 17 closed - CATENARY, // 18 closed - SEMIELLIPTICAL, // 19 closed - BASKETHANDLE, // 20 closed - SEMICIRCULAR, // 21 closed - IRREGULAR, // 22 - CUSTOM, // 23 closed - FORCE_MAIN, // 24 closed - STREET_XSECT}; // 25 - -//------------------------------------- -// Measurement units types -//------------------------------------- - enum UnitsType { - US, // US units - SI}; // SI (metric) units - - enum FlowUnitsType { - CFS, // cubic feet per second - GPM, // gallons per minute - MGD, // million gallons per day - CMS, // cubic meters per second - LPS, // liters per second - MLD}; // million liters per day - - enum ConcUnitsType { - MG, // Milligrams / L - UG, // Micrograms / L - COUNT}; // Counts / L - -//-------------------------------------- -// Quantities requiring unit conversions -//-------------------------------------- - enum ConversionType { - RAINFALL, - RAINDEPTH, - EVAPRATE, - LENGTH, - LANDAREA, - VOLUME, - WINDSPEED, - TEMPERATURE, - MASS, - GWFLOW, - FLOW}; // Flow must always be listed last - -//------------------------------------- -// Computed subcatchment quantities -//------------------------------------- - #define MAX_SUBCATCH_RESULTS 9 - enum SubcatchResultType { - SUBCATCH_RAINFALL, // rainfall intensity - SUBCATCH_SNOWDEPTH, // snow depth - SUBCATCH_EVAP, // evap loss - SUBCATCH_INFIL, // infil loss - SUBCATCH_RUNOFF, // runoff flow rate - SUBCATCH_GW_FLOW, // groundwater flow rate to node - SUBCATCH_GW_ELEV, // elevation of saturated gw table - SUBCATCH_SOIL_MOIST, // soil moisture - SUBCATCH_WASHOFF}; // pollutant washoff concentration - -//------------------------------------- -// Computed node quantities -//------------------------------------- - #define MAX_NODE_RESULTS 7 - enum NodeResultType { - NODE_DEPTH, // water depth above invert - NODE_HEAD, // hydraulic head - NODE_VOLUME, // volume stored & ponded - NODE_LATFLOW, // lateral inflow rate - NODE_INFLOW, // total inflow rate - NODE_OVERFLOW, // overflow rate - NODE_QUAL}; // concentration of each pollutant - -//------------------------------------- -// Computed link quantities -//------------------------------------- - #define MAX_LINK_RESULTS 6 - enum LinkResultType { - LINK_FLOW, // flow rate - LINK_DEPTH, // flow depth - LINK_VELOCITY, // flow velocity - LINK_VOLUME, // link volume - LINK_CAPACITY, // ratio of area to full area - LINK_QUAL}; // concentration of each pollutant - -//------------------------------------- -// System-wide quantities -//------------------------------------- -#define MAX_SYS_RESULTS 15 -enum SysFlowType { - SYS_TEMPERATURE, // air temperature - SYS_RAINFALL, // rainfall intensity - SYS_SNOWDEPTH, // snow depth - SYS_INFIL, // infil - SYS_RUNOFF, // runoff flow - SYS_DWFLOW, // dry weather inflow - SYS_GWFLOW, // ground water inflow - SYS_IIFLOW, // RDII inflow - SYS_EXFLOW, // external inflow - SYS_INFLOW, // total lateral inflow - SYS_FLOODING, // flooding outflow - SYS_OUTFLOW, // outfall outflow - SYS_STORAGE, // storage volume - SYS_EVAP, // evaporation - SYS_PET}; // potential ET - -//------------------------------------- -// Conduit flow classifications -//------------------------------------- - enum FlowClassType { - DRY, // dry conduit - UP_DRY, // upstream end is dry - DN_DRY, // downstream end is dry - SUBCRITICAL, // sub-critical flow - SUPCRITICAL, // super-critical flow - UP_CRITICAL, // free-fall at upstream end - DN_CRITICAL, // free-fall at downstream end - MAX_FLOW_CLASSES, // number of distinct flow classes - UP_FULL, // upstream end is full - DN_FULL, // downstream end is full - ALL_FULL}; // completely full - -//------------------------ -// Runoff flow categories -//------------------------ -enum RunoffFlowType { - RUNOFF_RAINFALL, // rainfall - RUNOFF_EVAP, // evaporation - RUNOFF_INFIL, // infiltration - RUNOFF_RUNOFF, // runoff - RUNOFF_DRAINS, // LID drain flow - RUNOFF_RUNON}; // runon from outfalls - -//------------------------------------- -// Surface pollutant loading categories -//------------------------------------- - enum LoadingType { - BUILDUP_LOAD, // pollutant buildup load - DEPOSITION_LOAD, // rainfall deposition load - SWEEPING_LOAD, // load removed by sweeping - BMP_REMOVAL_LOAD, // load removed by BMPs - INFIL_LOAD, // runon load removed by infiltration - RUNOFF_LOAD, // load removed by runoff - FINAL_LOAD}; // load remaining on surface - -//------------------------------------- -// Input data options -//------------------------------------- - enum RainfallType { - RAINFALL_INTENSITY, // rainfall expressed as intensity - RAINFALL_VOLUME, // rainfall expressed as volume - CUMULATIVE_RAINFALL}; // rainfall expressed as cumulative volume - - enum TempType { - NO_TEMP, // no temperature data supplied - TSERIES_TEMP, // temperatures come from time series - FILE_TEMP}; // temperatures come from file - -enum WindType { - MONTHLY_WIND, // wind speed varies by month - FILE_WIND}; // wind speed comes from file - - enum EvapType { - CONSTANT_EVAP, // constant evaporation rate - MONTHLY_EVAP, // evaporation rate varies by month - TIMESERIES_EVAP, // evaporation supplied by time series - TEMPERATURE_EVAP, // evaporation from daily temperature - FILE_EVAP, // evaporation comes from file - RECOVERY, // soil recovery pattern - DRYONLY}; // evap. allowed only in dry periods - - enum NormalizerType { - PER_AREA, // buildup is per unit of area - PER_CURB}; // buildup is per unit of curb length - - enum BuildupType { - NO_BUILDUP, // no buildup - POWER_BUILDUP, // power function buildup equation - EXPON_BUILDUP, // exponential function buildup equation - SATUR_BUILDUP, // saturation function buildup equation - EXTERNAL_BUILDUP}; // external time series buildup - - enum WashoffType { - NO_WASHOFF, // no washoff - EXPON_WASHOFF, // exponential washoff equation - RATING_WASHOFF, // rating curve washoff equation - EMC_WASHOFF}; // event mean concentration washoff - -enum SubAreaType { - IMPERV0, // impervious w/o depression storage - IMPERV1, // impervious w/ depression storage - PERV}; // pervious - - enum RunoffRoutingType { - TO_OUTLET, // perv & imperv runoff goes to outlet - TO_IMPERV, // perv runoff goes to imperv area - TO_PERV}; // imperv runoff goes to perv subarea - - enum RouteModelType { - NO_ROUTING, // no routing - SF, // steady flow model - KW, // kinematic wave model - EKW, // extended kin. wave model - DW}; // dynamic wave model - - enum ForceMainType { - H_W, // Hazen-Williams eqn. - D_W}; // Darcy-Weisbach eqn. - - enum OffsetType { - DEPTH_OFFSET, // offset measured as depth - ELEV_OFFSET}; // offset measured as elevation - - enum KinWaveMethodType { - NORMAL, // normal method - MODIFIED}; // modified method - -enum CompatibilityType { - SWMM5, // SWMM 5 weighting for area & hyd. radius - SWMM3, // SWMM 3 weighting - SWMM4}; // SWMM 4 weighting - - enum NormalFlowType { - SLOPE, // based on slope only - FROUDE, // based on Fr only - BOTH}; // based on slope & Fr - - enum InertialDampingType { - NO_DAMPING, // no inertial damping - PARTIAL_DAMPING, // partial damping - FULL_DAMPING}; // full damping - - enum SurchargeMethodType { - EXTRAN, // original EXTRAN method - SLOT}; // Preissmann slot method - - enum InflowType { - EXTERNAL_INFLOW, // user-supplied external inflow - DRY_WEATHER_INFLOW, // user-supplied dry weather inflow - WET_WEATHER_INFLOW, // computed runoff inflow - GROUNDWATER_INFLOW, // computed groundwater inflow - RDII_INFLOW, // computed I&I inflow - FLOW_INFLOW, // inflow parameter is flow - CONCEN_INFLOW, // inflow parameter is pollutant concen. - MASS_INFLOW}; // inflow parameter is pollutant mass - - enum PatternType { - MONTHLY_PATTERN, // DWF multipliers for each month - DAILY_PATTERN, // DWF multipliers for each day of week - HOURLY_PATTERN, // DWF multipliers for each hour of day - WEEKEND_PATTERN}; // hourly multipliers for week end days - - enum OutfallType { - FREE_OUTFALL, // critical depth outfall condition - NORMAL_OUTFALL, // normal flow depth outfall condition - FIXED_OUTFALL, // fixed depth outfall condition - TIDAL_OUTFALL, // variable tidal stage outfall condition - TIMESERIES_OUTFALL}; // variable time series outfall depth - - enum StorageType { - TABULAR, // area v. depth from table - FUNCTIONAL, // area v. depth from power function - CYLINDRICAL, // area v. depth from elliptical cylinder - CONICAL, // area v. depth from elliptical cone - PARABOLOID, // area v. depth from elliptical paraboloid - PYRAMIDAL}; // area v. depth from rectangular pyramid - - enum ReactorType { - CSTR, // completely mixed reactor - PLUG}; // plug flow reactor - - enum TreatmentType { - REMOVAL, // treatment stated as a removal - CONCEN}; // treatment stated as effluent concen. - - enum DividerType { - CUTOFF_DIVIDER, // diverted flow is excess of cutoff flow - TABULAR_DIVIDER, // table of diverted flow v. inflow - WEIR_DIVIDER, // diverted flow proportional to excess flow - OVERFLOW_DIVIDER}; // diverted flow is flow > full conduit flow - - enum PumpCurveType { - TYPE1_PUMP, // flow varies stepwise with wet well volume - TYPE2_PUMP, // flow varies stepwise with inlet depth - TYPE3_PUMP, // flow varies with head delivered - TYPE4_PUMP, // flow varies with inlet depth - TYPE5_PUMP, // variable speed version of TYPE3 pump - IDEAL_PUMP}; // outflow equals inflow - - enum OrificeType { - SIDE_ORIFICE, // side orifice - BOTTOM_ORIFICE}; // bottom orifice - - enum WeirType { - TRANSVERSE_WEIR, // transverse weir - SIDEFLOW_WEIR, // side flow weir - VNOTCH_WEIR, // V-notch (triangular) weir - TRAPEZOIDAL_WEIR, // trapezoidal weir - ROADWAY_WEIR}; // FHWA HDS-5 roadway weir - - enum CurveType { - STORAGE_CURVE, // surf. area v. depth for storage node - DIVERSION_CURVE, // diverted flow v. inflow for divider node - TIDAL_CURVE, // water elev. v. hour of day for outfall - RATING_CURVE, // flow rate v. head for outlet link - CONTROL_CURVE, // control setting v. controller variable - SHAPE_CURVE, // width v. depth for custom x-section - WEIR_CURVE, // discharge coeff. v. head for weir - PUMP1_CURVE, // flow v. wet well volume for pump - PUMP2_CURVE, // flow v. depth for pump (discrete) - PUMP3_CURVE, // flow v. head for pump (continuous) - PUMP4_CURVE, // flow v. depth for pump (continuous) - PUMP5_CURVE}; // variable speed version of TYPE3 pump - - enum NodeInletType { - NO_INLET, - BYPASS, - CAPTURE - }; - - enum InputSectionType { - s_TITLE, s_OPTION, s_FILE, s_RAINGAGE, - s_TEMP, s_EVAP, s_SUBCATCH, s_SUBAREA, - s_INFIL, s_AQUIFER, s_GROUNDWATER, s_SNOWMELT, - s_JUNCTION, s_OUTFALL, s_STORAGE, s_DIVIDER, - s_CONDUIT, s_PUMP, s_ORIFICE, s_WEIR, - s_OUTLET, s_XSECTION, s_TRANSECT, s_LOSSES, - s_CONTROL, s_POLLUTANT, s_LANDUSE, s_BUILDUP, - s_WASHOFF, s_COVERAGE, s_INFLOW, s_DWF, - s_PATTERN, s_RDII, s_UNITHYD, s_LOADING, - s_TREATMENT, s_CURVE, s_TIMESERIES, s_REPORT, - s_COORDINATE, s_VERTICES, s_POLYGON, s_LABEL, - s_SYMBOL, s_BACKDROP, s_TAG, s_PROFILE, - s_MAP, s_LID_CONTROL, s_LID_USAGE, s_GWF, - s_ADJUST, s_EVENT, s_STREET, s_INLET_USAGE, - s_INLET}; - - enum InputOptionType { - FLOW_UNITS, INFIL_MODEL, ROUTE_MODEL, - START_DATE, START_TIME, END_DATE, - END_TIME, REPORT_START_DATE, REPORT_START_TIME, - SWEEP_START, SWEEP_END, START_DRY_DAYS, - WET_STEP, DRY_STEP, ROUTE_STEP, RULE_STEP, - REPORT_STEP, ALLOW_PONDING, INERT_DAMPING, - SLOPE_WEIGHTING, VARIABLE_STEP, NORMAL_FLOW_LTD, - LENGTHENING_STEP, MIN_SURFAREA, COMPATIBILITY, - SKIP_STEADY_STATE, TEMPDIR, IGNORE_RAINFALL, - FORCE_MAIN_EQN, LINK_OFFSETS, MIN_SLOPE, - IGNORE_SNOWMELT, IGNORE_GWATER, IGNORE_ROUTING, - IGNORE_QUALITY, MAX_TRIALS, HEAD_TOL, - SYS_FLOW_TOL, LAT_FLOW_TOL, IGNORE_RDII, - MIN_ROUTE_STEP, NUM_THREADS, SURCHARGE_METHOD}; - -enum NoYesType { - NO, - YES}; - -enum NoneAllType { - NONE, - ALL, - SOME}; - - -#endif //ENUMS_H diff --git a/src/error.c b/src/error.c deleted file mode 100644 index 626950b6f..000000000 --- a/src/error.c +++ /dev/null @@ -1,49 +0,0 @@ -//----------------------------------------------------------------------------- -// error.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Error messages -// -// Update History -// ============== -// Build 5.1.008: -// - Text of Error 217 for control rules modified. -// Build 5.1.010: -// - Text of Error 318 for rainfall data files modified. -// Build 5.1.015: -// - Added new Error 140 for storage nodes. -// Build 5.2.0: -// - Re-designed error message system. -// - Added new Error 235 for invalid infiltration parameters. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "error.h" - -char ErrString[256]; - -char* error_getMsg(int errCode, char* msg) -{ - switch (errCode) - { - -#define ERR(code,string) case code: strcpy(msg, string); break; -#include "error.txt" -#undef ERR - - default: - strcpy(msg, ""); - } - return (msg); -}; - -int error_setInpError(int errcode, char* s) -{ - strcpy(ErrString, s); - return errcode; -} diff --git a/src/error.h b/src/error.h deleted file mode 100644 index fedf0847e..000000000 --- a/src/error.h +++ /dev/null @@ -1,182 +0,0 @@ -//----------------------------------------------------------------------------- -// error.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Error codes -// -//----------------------------------------------------------------------------- - -#ifndef ERROR_H -#define ERROR_H - -enum ErrorType { - -// ... Runtime Errors - ERR_NONE = 0, - ERR_MEMORY = 101, - ERR_KINWAVE = 103, - ERR_ODE_SOLVER = 105, - ERR_TIMESTEP = 107, - -// ... Subcatchment/Aquifer Errors - ERR_SUBCATCH_OUTLET = 108, - ERR_AQUIFER_PARAMS = 109, - ERR_GROUND_ELEV = 110, - -// ... Conduit/Pump Errors - ERR_LENGTH = 111, - ERR_ELEV_DROP = 112, - ERR_ROUGHNESS = 113, - ERR_BARRELS = 114, - ERR_SLOPE = 115, - ERR_NO_XSECT = 117, - ERR_XSECT = 119, - ERR_NO_CURVE = 121, - ERR_PUMP_LIMITS = 122, - -// ... Topology Errors - ERR_LOOP = 131, - ERR_MULTI_OUTLET = 133, - ERR_DUMMY_LINK = 134, - -// ... Node Errors - ERR_DIVIDER = 135, - ERR_DIVIDER_LINK = 136, - ERR_WEIR_DIVIDER = 137, - ERR_NODE_DEPTH = 138, - ERR_REGULATOR = 139, - ERR_STORAGE_VOLUME = 140, - ERR_OUTFALL = 141, - ERR_REGULATOR_SHAPE = 143, - ERR_NO_OUTLETS = 145, - -// ... RDII Errors - ERR_UNITHYD_TIMES = 151, - ERR_UNITHYD_RATIOS = 153, - ERR_RDII_AREA = 155, - -// ... Rain Gage Errors - ERR_RAIN_FILE_CONFLICT = 156, - ERR_RAIN_GAGE_FORMAT = 157, - ERR_RAIN_GAGE_TSERIES = 158, - ERR_RAIN_GAGE_INTERVAL = 159, - -// ... Treatment Function Error - ERR_CYCLIC_TREATMENT = 161, - -// ... Curve/Time Series Errors - ERR_CURVE_SEQUENCE = 171, - ERR_TIMESERIES_SEQUENCE = 173, - -// ... Snowmelt Errors - ERR_SNOWMELT_PARAMS = 181, - ERR_SNOWPACK_PARAMS = 182, - -// ... LID Errors - ERR_LID_TYPE = 183, - ERR_LID_LAYER = 184, - ERR_LID_PARAMS = 185, - ERR_LID_AREAS = 187, - ERR_LID_CAPTURE_AREA = 188, - -// ... Simulation Date/Time Errors - ERR_START_DATE = 191, - ERR_REPORT_DATE = 193, - ERR_REPORT_STEP = 195, - -// ... Input Parser Errors - ERR_INPUT = 200, - ERR_LINE_LENGTH = 201, - ERR_ITEMS = 203, - ERR_KEYWORD = 205, - ERR_DUP_NAME = 207, - ERR_NAME = 209, - ERR_NUMBER = 211, - ERR_DATETIME = 213, - ERR_RULE = 217, - ERR_TRANSECT_UNKNOWN = 219, - ERR_TRANSECT_SEQUENCE = 221, - ERR_TRANSECT_TOO_FEW = 223, - ERR_TRANSECT_TOO_MANY = 225, - ERR_TRANSECT_MANNING = 227, - ERR_TRANSECT_OVERBANK = 229, - ERR_TRANSECT_NO_DEPTH = 231, - ERR_MATH_EXPR = 233, - ERR_INFIL_PARAMS = 235, - -// ... File Name/Opening Errors - ERR_FILE_NAME = 301, - ERR_INP_FILE = 303, - ERR_RPT_FILE = 305, - ERR_OUT_FILE = 307, - ERR_OUT_SIZE = 308, - ERR_OUT_WRITE = 309, - ERR_OUT_READ = 311, - -// ... Rain File Errors - ERR_RAIN_FILE_SCRATCH = 313, - ERR_RAIN_FILE_OPEN = 315, - ERR_RAIN_FILE_DATA = 317, - ERR_RAIN_FILE_SEQUENCE = 318, - ERR_RAIN_FILE_FORMAT = 319, - ERR_RAIN_IFACE_FORMAT = 320, - ERR_RAIN_FILE_GAGE = 321, - -// ... Runoff File Errors - ERR_RUNOFF_FILE_OPEN = 323, - ERR_RUNOFF_FILE_FORMAT = 325, - ERR_RUNOFF_FILE_END = 327, - ERR_RUNOFF_FILE_READ = 329, - -// ... Hotstart File Errors - ERR_HOTSTART_FILE_OPEN = 331, - ERR_HOTSTART_FILE_FORMAT = 333, - ERR_HOTSTART_FILE_READ = 335, - -// ... Climate File Errors - ERR_NO_CLIMATE_FILE = 336, - ERR_CLIMATE_FILE_OPEN = 337, - ERR_CLIMATE_FILE_READ = 338, - ERR_CLIMATE_END_OF_FILE = 339, - -// ... RDII File Errors - ERR_RDII_FILE_SCRATCH = 341, - ERR_RDII_FILE_OPEN = 343, - ERR_RDII_FILE_FORMAT = 345, - -// ... Routing File Errors - ERR_ROUTING_FILE_OPEN = 351, - ERR_ROUTING_FILE_FORMAT = 353, - ERR_ROUTING_FILE_NOMATCH = 355, - ERR_ROUTING_FILE_NAMES = 357, - -// ... Time Series File Errors - ERR_TABLE_FILE_OPEN = 361, - ERR_TABLE_FILE_READ = 363, - -// ... Runtime Errors - ERR_SYSTEM = 500, - -// ... API Errors - ERR_API_NOT_OPEN = 501, - ERR_API_NOT_STARTED = 502, - ERR_API_NOT_ENDED = 503, - ERR_API_OBJECT_TYPE = 504, - ERR_API_OBJECT_INDEX = 505, - ERR_API_OBJECT_NAME = 506, - ERR_API_PROPERTY_TYPE = 507, - ERR_API_PROPERTY_VALUE = 508, - ERR_API_TIME_PERIOD = 509, - -// ... Additional Errors - MAXERRMSG = 1000 -}; - -char* error_getMsg(int i, char* msg); -int error_setInpError(int errcode, char* s); - -#endif //ERROR_H diff --git a/src/error.txt b/src/error.txt deleted file mode 100644 index 801d96b6a..000000000 --- a/src/error.txt +++ /dev/null @@ -1,135 +0,0 @@ -// SWMM 5.2 Error Messages - -ERR(101,"\n ERROR 101: memory allocation error.") -ERR(103,"\n ERROR 103: cannot solve KW equations for Link %s.") -ERR(105,"\n ERROR 105: cannot open ODE solver.") -ERR(107,"\n ERROR 107: cannot compute a valid time step.") - -ERR(108,"\n ERROR 108: ambiguous outlet ID name for Subcatchment %s.") -ERR(109,"\n ERROR 109: invalid parameter values for Aquifer %s.") -ERR(110,"\n ERROR 110: ground elevation is below water table for Subcatchment %s.") - -ERR(111,"\n ERROR 111: invalid length for Conduit %s.") -ERR(112,"\n ERROR 112: elevation drop exceeds length for Conduit %s.") -ERR(113,"\n ERROR 113: invalid roughness for Conduit %s.") -ERR(114,"\n ERROR 114: invalid number of barrels for Conduit %s.") -ERR(115,"\n ERROR 115: adverse slope for Conduit %s.") -ERR(117,"\n ERROR 117: no cross section defined for Link %s.") -ERR(119,"\n ERROR 119: invalid cross section for Link %s.") -ERR(121,"\n ERROR 121: missing or invalid pump curve assigned to Pump %s.") -ERR(122,"\n ERROR 122: startup depth not higher than shutoff depth for Pump %s.") - -ERR(131,"\n ERROR 131: the following links form cyclic loops in the drainage system:") -ERR(133,"\n ERROR 133: Node %s has more than one outlet link.") -ERR(134,"\n ERROR 134: Node %s has illegal DUMMY link connections.") - -ERR(135,"\n ERROR 135: Divider %s does not have two outlet links.") -ERR(136,"\n ERROR 136: Divider %s has invalid diversion link.") -ERR(137,"\n ERROR 137: Weir Divider %s has invalid parameters.") -ERR(138,"\n ERROR 138: Node %s has initial depth greater than maximum depth.") -ERR(139,"\n ERROR 139: Regulator %s is the outlet of a non-storage node.") -ERR(140,"\n ERROR 140: Storage node %s has negative volume at full depth.") -ERR(141,"\n ERROR 141: Outfall %s has more than 1 inlet link or an outlet link.") -ERR(143,"\n ERROR 143: Regulator %s has invalid cross-section shape.") -ERR(145,"\n ERROR 145: Drainage system has no acceptable outlet nodes.") - -ERR(151,"\n ERROR 151: a Unit Hydrograph in set %s has invalid time base.") -ERR(153,"\n ERROR 153: a Unit Hydrograph in set %s has invalid response ratios.") -ERR(155,"\n ERROR 155: invalid sewer area for RDII at node %s.") - -ERR(156,"\n ERROR 156: ambiguous station ID for Rain Gage %s.") -ERR(157,"\n ERROR 157: inconsistent rainfall format for Rain Gage %s.") -ERR(158,"\n ERROR 158: time series for Rain Gage %s is also used by another object.") -ERR(159,"\n ERROR 159: recording interval greater than time series interval for Rain Gage %s.") - -ERR(161,"\n ERROR 161: cyclic dependency in treatment functions at node %s.") - -ERR(171,"\n ERROR 171: Curve %s has invalid or out of sequence data.") -ERR(173,"\n ERROR 173: Time Series %s has its data out of sequence.") - -ERR(181,"\n ERROR 181: invalid Snow Melt Climatology parameters.") -ERR(182,"\n ERROR 182: invalid parameters for Snow Pack %s.") - -ERR(183,"\n ERROR 183: no type specified for LID %s.") -ERR(184,"\n ERROR 184: missing layer for LID %s.") -ERR(185,"\n ERROR 185: invalid parameter value for LID %s.") -ERR(187,"\n ERROR 187: LID area exceeds total area for Subcatchment %s.") -ERR(188,"\n ERROR 188: LID capture area exceeds total impervious area for Subcatchment %s.") - -ERR(191,"\n ERROR 191: simulation start date comes after ending date.") -ERR(193,"\n ERROR 193: report start date comes after ending date.") -ERR(195,"\n ERROR 195: reporting time step or duration is less than routing time step.") - -ERR(200,"\n ERROR 200: one or more errors in input file.") -ERR(201,"\n ERROR 201: too many characters in input line ") -ERR(203,"\n ERROR 203: too few items ") -ERR(205,"\n ERROR 205: invalid keyword %s ") -ERR(207,"\n ERROR 207: duplicate ID name %s ") -ERR(209,"\n ERROR 209: undefined object %s ") -ERR(211,"\n ERROR 211: invalid number %s ") -ERR(213,"\n ERROR 213: invalid date/time %s ") -ERR(217,"\n ERROR 217: control rule clause invalid or out of sequence ") -ERR(219,"\n ERROR 219: data provided for unidentified transect ") -ERR(221,"\n ERROR 221: transect station out of sequence ") -ERR(223,"\n ERROR 223: Transect %s has too few stations.") -ERR(225,"\n ERROR 225: Transect %s has too many stations.") -ERR(227,"\n ERROR 227: Transect %s has no Manning's N.") -ERR(229,"\n ERROR 229: Transect %s has invalid overbank locations.") -ERR(231,"\n ERROR 231: Transect %s has no depth.") -ERR(233,"\n ERROR 233: invalid math expression ") -ERR(235,"\n ERROR 235: invalid infiltration parameters.") - -ERR(301,"\n ERROR 301: files share same names.") -ERR(303,"\n ERROR 303: cannot open input file.") -ERR(305,"\n ERROR 305: cannot open report file.") -ERR(307,"\n ERROR 307: cannot open binary results file.") -ERR(308,"\n ERROR 308: amount of output produced will exceed maximum file size.") - -ERR(309,"\n ERROR 309: error writing to binary results file.") -ERR(311,"\n ERROR 311: error reading from binary results file.") - -ERR(313,"\n ERROR 313: cannot open scratch rainfall interface file.") -ERR(315,"\n ERROR 315: cannot open rainfall interface file %s.") -ERR(317,"\n ERROR 317: cannot open rainfall data file %s.") -ERR(318,"\n ERROR 318: the following line is out of sequence in rainfall data file %s.") -ERR(319,"\n ERROR 319: unknown format for rainfall data file %s.") -ERR(320,"\n ERROR 320: invalid format for rainfall interface file.") -ERR(321,"\n ERROR 321: no data in rainfall interface file for gage %s.") - -ERR(323,"\n ERROR 323: cannot open runoff interface file %s.") -ERR(325,"\n ERROR 325: incompatible data found in runoff interface file.") -ERR(327,"\n ERROR 327: attempting to read beyond end of runoff interface file.") -ERR(329,"\n ERROR 329: error in reading from runoff interface file.") - -ERR(331,"\n ERROR 331: cannot open hot start interface file %s.") -ERR(333,"\n ERROR 333: incompatible data found in hot start interface file.") -ERR(335,"\n ERROR 335: error in reading from hot start interface file.") - -ERR(336,"\n ERROR 336: no climate file specified for evaporation and/or wind speed.") -ERR(337,"\n ERROR 337: cannot open climate file %s.") -ERR(338,"\n ERROR 338: error in reading from climate file %s.") -ERR(339,"\n ERROR 339: attempt to read beyond end of climate file %s.") - -ERR(341,"\n ERROR 341: cannot open scratch RDII interface file.") -ERR(343,"\n ERROR 343: cannot open RDII interface file %s.") -ERR(345,"\n ERROR 345: invalid format for RDII interface file.") - -ERR(351,"\n ERROR 351: cannot open routing interface file %s.") -ERR(353,"\n ERROR 353: invalid format for routing interface file %s.") -ERR(355,"\n ERROR 355: mis-matched names in routing interface file %s.") -ERR(357,"\n ERROR 357: inflows and outflows interface files have same name.") - -ERR(361,"\n ERROR 361: could not open external file used for Time Series %s.") -ERR(363,"\n ERROR 363: invalid data in external file used for Time Series %s.") - -// API Error Keys -ERR(500,"\n ERROR 500: System exception thrown.") -ERR(501,"\n API Error 501: project not opened.") -ERR(502,"\n API Error 502: simulation not started.") -ERR(503,"\n API Error 503: simulation not ended.") -ERR(504,"\n API Error 504: invalid object type.") -ERR(505,"\n API Error 505: invalid object index.") -ERR(506,"\n API Error 506: invalid object name.") -ERR(507,"\n API Error 507: invalid property type.") -ERR(508,"\n API Error 508: invalid property value.") -ERR(509,"\n API Error 509: invalid time period.") diff --git a/src/exfil.c b/src/exfil.c deleted file mode 100644 index f1cb2adf6..000000000 --- a/src/exfil.c +++ /dev/null @@ -1,252 +0,0 @@ -//----------------------------------------------------------------------------- -// exfil.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Storage unit exfiltration functions. -// -// Update History -// ============== -// Build 5.1.008: -// - Monthly conductivity adjustment applied to exfiltration rate. -// Build 5.1.010: -// - New modified Green-Ampt infiltration option used. -// Build 5.1.011: -// - Fixed units conversion error for storage units with surface area curves. -// Build 5.2.0: -// - Support added for analytical storage shapes. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" -#include "infil.h" -#include "exfil.h" - -static int createStorageExfil(int k, double x[]); - -//============================================================================= - -int exfil_readStorageParams(int k, char* tok[], int ntoks, int n) -// -// Input: k = storage unit index -// tok[] = array of string tokens -// ntoks = number of tokens -// n = last token processed -// Output: returns an error code -// Purpose: reads a storage unit's exfiltration parameters from a -// tokenized line of input. -// -{ - int i; - double x[3]; //suction head, Ksat, IMDmax - - // --- read Ksat if it's the only remaining token - if ( ntoks == n+1 ) - { - if ( ! getDouble(tok[n], &x[1]) ) - return error_setInpError(ERR_NUMBER, tok[n]); - x[0] = 0.0; - x[2] = 0.0; - } - - // --- otherwise read Green-Ampt infiltration parameters from input tokens - else if ( ntoks < n + 3 ) return error_setInpError(ERR_ITEMS, ""); - else for (i = 0; i < 3; i++) - { - if ( ! getDouble(tok[n+i], &x[i]) ) - return error_setInpError(ERR_NUMBER, tok[n+i]); - } - - // --- no exfiltration if Ksat is 0 - if ( x[1] == 0.0 ) return 0; - - // --- create an exfiltration object - return createStorageExfil(k, x); -} - -//============================================================================= - -void exfil_initState(int k) -// -// Input: k = storage unit index -// Output: none -// Purpose: initializes the state of a storage unit's exfiltration object. -// -{ - int i; - double a, alast, d; - TTable* aCurve; - TExfil* exfil = Storage[k].exfil; - - // --- initialize exfiltration object - if ( exfil != NULL ) - { - // --- initialize the Green-Ampt infil. parameters - grnampt_initState(exfil->btmExfil); - grnampt_initState(exfil->bankExfil); - - switch (Storage[k].shape) - { - // --- shape given by a Storage Curve - case TABULAR: - i = Storage[k].aCurve; - exfil->btmArea = 0.0; - exfil->bankMinDepth = 0.0; - exfil->bankMaxDepth = 0.0; - exfil->bankMaxArea = 0.0; - if ( i >= 0 ) - { - // --- get bottom area - aCurve = &Curve[i]; - Storage[k].exfil->btmArea = table_lookupEx(aCurve, 0.0); - - // --- find min/max bank depths and max. bank area - table_getFirstEntry(aCurve, &d, &a); - alast = a; - while ( table_getNextEntry(aCurve, &d, &a) ) - { - if ( a < alast ) break; - else if ( a > alast ) - { - exfil->bankMaxArea = a; - exfil->bankMaxDepth = d; - } - else if ( exfil->bankMaxArea == 0.0 ) - exfil->bankMinDepth = d; - else break; - alast = a; - } - - // --- convert from user units to internal units - exfil->btmArea /= UCF(LENGTH) * UCF(LENGTH); - exfil->bankMaxArea /= UCF(LENGTH) * UCF(LENGTH); - exfil->bankMinDepth /= UCF(LENGTH); - exfil->bankMaxDepth /= UCF(LENGTH); - } - break; - - // --- functional storage shape curve - case FUNCTIONAL: - exfil->btmArea = Storage[k].a0; - if ( Storage[k].a2 == 0.0 ) - exfil->btmArea +=Storage[k].a1; - exfil->bankMinDepth = 0.0; - exfil->bankMaxDepth = BIG; - exfil->bankMaxArea = BIG; - break; - - // --- cylindrical, conical & prismatic shapes - case CYLINDRICAL: - case CONICAL: - case PYRAMIDAL: - exfil->btmArea = Storage[k].a0; - exfil->bankMinDepth = 0.0; - exfil->bankMaxDepth = BIG; - exfil->bankMaxArea = BIG; - break; - } - } -} - -//============================================================================= - -double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area) -// -// Input: exfil = ptr. to a storage exfiltration object -// tStep = time step (sec) -// depth = water depth (ft) -// area = surface area (ft2) -// Output: returns exfiltration rate out of storage unit (cfs) -// Purpose: computes rate of water exfiltrated from a storage node into -// the soil beneath it. -// -{ - double exfilRate = 0.0; - - // --- find infiltration through bottom of unit - if ( exfil->btmExfil->IMDmax == 0.0 ) - { - exfilRate = exfil->btmExfil->Ks * Adjust.hydconFactor; - } - else exfilRate = grnampt_getInfil(exfil->btmExfil, tStep, 0.0, depth, - MOD_GREEN_AMPT); - exfilRate *= exfil->btmArea; - - // --- find infiltration through sloped banks - if ( depth > exfil->bankMinDepth ) - { - // --- get area of banks - area = MIN(area, exfil->bankMaxArea) - exfil->btmArea; - if ( area > 0.0 ) - { - // --- if infil. rate not a function of depth - if ( exfil->btmExfil->IMDmax == 0.0 ) - { - exfilRate += area * exfil->btmExfil->Ks * Adjust.hydconFactor; - } - - // --- infil. rate depends on depth above bank - else - { - // --- case where water depth is above the point where the - // storage curve no longer has increasing area with depth - if ( depth > exfil->bankMaxDepth ) - { - depth = depth - exfil->bankMaxDepth + - (exfil->bankMaxDepth - exfil->bankMinDepth) / 2.0; - } - - // --- case where water depth is below top of bank - else depth = (depth - exfil->bankMinDepth) / 2.0; - - // --- use Green-Ampt function for bank infiltration - exfilRate += area * grnampt_getInfil(exfil->bankExfil, - tStep, 0.0, depth, MOD_GREEN_AMPT); - } - } - } - return exfilRate; -} - -//============================================================================= - -int createStorageExfil(int k, double x[]) -// -// Input: k = index of storage unit node -// x = array of Green-Ampt infiltration parameters -// Output: returns an error code. -// Purpose: creates an exfiltration object for a storage node. -// -// Note: the exfiltration object is freed in project.c. -// -{ - TExfil* exfil; - - // --- create an exfiltration object for the storage node - exfil = Storage[k].exfil; - if ( exfil == NULL ) - { - exfil = (TExfil *) malloc(sizeof(TExfil)); - if ( exfil == NULL ) return error_setInpError(ERR_MEMORY, ""); - Storage[k].exfil = exfil; - - // --- create Green-Ampt infiltration objects for the bottom & banks - exfil->btmExfil = NULL; - exfil->bankExfil = NULL; - exfil->btmExfil = (TGrnAmpt *) malloc(sizeof(TGrnAmpt)); - if ( exfil->btmExfil == NULL ) return error_setInpError(ERR_MEMORY, ""); - exfil->bankExfil = (TGrnAmpt *) malloc(sizeof(TGrnAmpt)); - if ( exfil->bankExfil == NULL ) return error_setInpError(ERR_MEMORY, ""); - } - - // --- initialize the Green-Ampt parameters - if ( !grnampt_setParams(exfil->btmExfil, x) ) - return error_setInpError(ERR_NUMBER, ""); - grnampt_setParams(exfil->bankExfil, x); - return 0; -} diff --git a/src/exfil.h b/src/exfil.h deleted file mode 100644 index 8da4da302..000000000 --- a/src/exfil.h +++ /dev/null @@ -1,35 +0,0 @@ -//----------------------------------------------------------------------------- -// exfil.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Public interface for exfiltration functions. -//----------------------------------------------------------------------------- - -#ifndef EXFIL_H -#define EXFIL_H - -//---------------------------- -// EXFILTRATION OBJECT -//---------------------------- -typedef struct -{ - TGrnAmpt* btmExfil; - TGrnAmpt* bankExfil; - double btmArea; - double bankMinDepth; - double bankMaxDepth; - double bankMaxArea; -} TExfil; - -//----------------------------------------------------------------------------- -// Exfiltration Methods -//----------------------------------------------------------------------------- -int exfil_readStorageParams(int k, char* tok[], int ntoks, int n); -void exfil_initState(int k); -double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area); - -#endif diff --git a/src/findroot.c b/src/findroot.c deleted file mode 100644 index d6056f658..000000000 --- a/src/findroot.c +++ /dev/null @@ -1,138 +0,0 @@ -//----------------------------------------------------------------------------- -// findroot.c -// -// Finds solution of func(x) = 0 using either the Newton-Raphson -// method or Ridder's Method. -// Based on code from Numerical Recipes in C (Cambridge University -// Press, 1992). -// -// Date: 11/19/13 -// Author: L. Rossman -//----------------------------------------------------------------------------- - -#include -#include "findroot.h" - -#define SIGN(a,b) ((b) >= 0.0 ? fabs(a) : -fabs(a)) -#define MAXIT 60 - - -int findroot_Newton(double x1, double x2, double* rts, double xacc, - void (*func) (double x, double* f, double* df, void* p), - void* p) -// -// Using a combination of Newton-Raphson and bisection, find the root of a -// function func bracketed between x1 and x2. The root, returned in rts, -// will be refined until its accuracy is known within +/-xacc. func is a -// user-supplied routine, that returns both the function value and the first -// derivative of the function. p is a pointer to any auxilary data structure -// that func may require. It can be NULL if not needed. The function returns -// the number of function evaluations used or 0 if the maximum allowed -// iterations were exceeded. -// -// NOTES: -// 1. The calling program must insure that the signs of func(x1) and func(x2) -// are not the same, otherwise x1 and x2 do not bracket the root. -// 2. If func(x1) > func(x2) then the order of x1 and x2 should be -// switched in the call to Newton. -// -{ - int j, n = 0; - double df, dx, dxold, f, x; - double temp, xhi, xlo; - - // Initialize the "stepsize before last" and the last step. - x = *rts; - xlo = x1; - xhi = x2; - dxold = fabs(x2-x1); - dx = dxold; - func(x, &f, &df, p); - n++; - - // Loop over allowed iterations. - for (j=1; j<=MAXIT; j++) - { - // Bisect if Newton out of range or not decreasing fast enough. - if ( ( ( (x-xhi)*df-f)*((x-xlo)*df-f) >= 0.0 - || (fabs(2.0*f) > fabs(dxold*df) ) ) ) - { - dxold = dx; - dx = 0.5*(xhi-xlo); - x = xlo + dx; - if ( xlo == x ) break; - } - - // Newton step acceptable. Take it. - else - { - dxold = dx; - dx = f/df; - temp = x; - x -= dx; - if ( temp == x ) break; - } - - // Convergence criterion. - if ( fabs(dx) < xacc ) break; - - // Evaluate function. Maintain bracket on the root. - func(x, &f, &df, p); - n++; - if ( f < 0.0 ) xlo = x; - else xhi = x; - } - *rts = x; - if ( n <= MAXIT) return n; - else return 0; -}; - - -double findroot_Ridder(double x1, double x2, double xacc, - double (*func)(double, void* p), void* p) -{ - int j; - double ans, fhi, flo, fm, fnew, s, xhi, xlo, xm, xnew; - - flo = func(x1, p); - fhi = func(x2, p); - if ( flo == 0.0 ) return x1; - if ( fhi == 0.0 ) return x2; - ans = 0.5*(x1+x2); - if ( (flo > 0.0 && fhi < 0.0) || (flo < 0.0 && fhi > 0.0) ) - { - xlo = x1; - xhi = x2; - for (j=1; j<=MAXIT; j++) { - xm = 0.5*(xlo + xhi); - fm = func(xm, p); - s = sqrt( fm*fm - flo*fhi ); - if (s == 0.0) return ans; - xnew = xm + (xm-xlo)*( (flo >= fhi ? 1.0 : -1.0)*fm/s ); - if ( fabs(xnew - ans) <= xacc ) break; - ans = xnew; - fnew = func(ans, p); - if ( SIGN(fm, fnew) != fm) - { - xlo = xm; - flo = fm; - xhi = ans; - fhi = fnew; - } - else if ( SIGN(flo, fnew) != flo ) - { - xhi = ans; - fhi = fnew; - } - else if ( SIGN(fhi, fnew) != fhi) - { - xlo = ans; - flo = fnew; - } - else return ans; - if ( fabs(xhi - xlo) <= xacc ) return ans; - } - return ans; - } - return -1.e20; -} diff --git a/src/findroot.h b/src/findroot.h deleted file mode 100644 index 3b54c6456..000000000 --- a/src/findroot.h +++ /dev/null @@ -1,18 +0,0 @@ -//----------------------------------------------------------------------------- -// findroot.h -// -// Header file for root finding method contained in findroot.c -// -// Last modified on 11/19/13. -//----------------------------------------------------------------------------- - -#ifndef FINDROOT_H -#define FINDROOT_H - -int findroot_Newton(double x1, double x2, double* rts, double xacc, - void (*func) (double x, double* f, double* df, void* p), - void* p); -double findroot_Ridder(double x1, double x2, double xacc, - double (*func)(double, void* p), void* p); - -#endif //FINDROOT_H diff --git a/src/flowrout.c b/src/flowrout.c deleted file mode 100644 index 9544dd459..000000000 --- a/src/flowrout.c +++ /dev/null @@ -1,797 +0,0 @@ -//----------------------------------------------------------------------------- -// flowrout.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// M. Tryby (EPA) -// -// Flow routing functions. -// -// Update History -// ============== -// Build 5.1.007: -// - updateStorageState() modified in response to node outflow being -// initialized with current evap & seepage losses in routing_execute(). -// Build 5.1.008: -// - Determination of node crown elevations moved to dynwave.c. -// - Support added for new way of recording conduit's fullness state. -// Build 5.1.012: -// - Overflow computed in updateStorageState() must be non-negative. -// - Terminal storage nodes now updated corectly. -// Build 5.1.014: -// - Arguments to function link_getLossRate changed. -// Build 5.2.0: -// - Correction made to updating state of terminal storage nodes. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double OMEGA = 0.55; // under-relaxation parameter -static const int MAXITER = 10; // max. iterations for storage updating -static const double STOPTOL = 0.005; // storage updating stopping tolerance - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// flowrout_init (called by routing_open) -// flowrout_close (called by routing_close) -// flowrout_getRoutingStep (called routing_getRoutingStep) -// flowrout_execute (called routing_execute) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static void initLinkDepths(void); -static void initNodeDepths(void); -static void initNodes(void); -static void initLinks(int routingModel); -static void validateTreeLayout(void); -static void validateGeneralLayout(void); -static void updateStorageState(int i, int j, int links[], double dt); -static double getStorageOutflow(int node, int j, int links[], double dt); -static double getLinkInflow(int link, double dt); -static void setNewNodeState(int node, double dt); -static void setNewLinkState(int link); -static void updateNodeDepth(int node, double y); -static int steadyflow_execute(int link, double* qin, double* qout, - double tStep); - - -//============================================================================= - -void flowrout_init(int routingModel) -// -// Input: routingModel = routing model code -// Output: none -// Purpose: initializes flow routing system. -// -{ - // --- initialize for dynamic wave routing - if ( routingModel == DW ) - { - // --- check for valid conveyance network layout - validateGeneralLayout(); - dynwave_init(); - - // --- initialize node & link depths if not using a hotstart file - if ( Fhotstart1.mode == NO_FILE ) - { - initNodeDepths(); - initLinkDepths(); - } - } - - // --- validate network layout for kinematic wave routing - else validateTreeLayout(); - - // --- initialize node & link volumes - initNodes(); - initLinks(routingModel); -} - -//============================================================================= - -void flowrout_close(int routingModel) -// -// Input: routingModel = routing method code -// Output: none -// Purpose: closes down routing method used. -// -{ - if ( routingModel == DW ) dynwave_close(); -} - -//============================================================================= - -double flowrout_getRoutingStep(int routingModel, double fixedStep) -// -// Input: routingModel = type of routing method used -// fixedStep = user-assigned max. routing step (sec) -// Output: returns adjusted value of routing time step (sec) -// Purpose: finds variable time step for dynamic wave routing. -// -{ - if ( routingModel == DW ) - { - return dynwave_getRoutingStep(fixedStep); - } - return fixedStep; -} - -//============================================================================= - -int flowrout_execute(int links[], int routingModel, double tStep) -// -// Input: links = array of link indexes in topo-sorted order (per routing model) -// routingModel = type of routing method used -// tStep = routing time step (sec) -// Output: returns number of computational steps taken -// Purpose: routes flow through conveyance network over current time step. -// -{ - int i, j; - int n1; // upstream node of link - double qin; // link inflow (cfs) - double qout; // link outflow (cfs) - double steps; // computational step count - - // --- set overflows to drain any ponded water - if ( ErrorCode ) return 0; - for (j = 0; j < Nobjects[NODE]; j++) - { - Node[j].updated = FALSE; - Node[j].overflow = 0.0; - if ( Node[j].type != STORAGE - && Node[j].newVolume > Node[j].fullVolume ) - { - Node[j].overflow = (Node[j].newVolume - Node[j].fullVolume)/tStep; - } - } - - // --- execute dynamic wave routing if called for - if ( routingModel == DW ) - { - return dynwave_execute(tStep); - } - - // --- otherwise examine each link, moving from upstream to downstream - steps = 0.0; - for (i = 0; i < Nobjects[LINK]; i++) - { - // --- see if upstream node is a storage unit whose state needs updating - j = links[i]; - n1 = Link[j].node1; - if ( Node[n1].type == STORAGE ) updateStorageState(n1, i, links, tStep); - - // --- retrieve inflow at upstream end of link - qin = getLinkInflow(j, tStep); - - // --- route flow through link - if ( routingModel == SF ) - steps += steadyflow_execute(j, &qin, &qout, tStep); - else - steps += kinwave_execute(j, &qin, &qout, tStep); - Link[j].newFlow = qout; - - // adjust outflow at upstream node and inflow at downstream node - Node[ Link[j].node1 ].outflow += qin; - Node[ Link[j].node2 ].inflow += qout; - } - if ( Nobjects[LINK] > 0 ) steps /= Nobjects[LINK]; - - // --- update state of each non-updated node and link - for ( j=0; j 2 ) - { - report_writeErrorMsg(ERR_DIVIDER, Node[j].ID); - } - break; - - // --- outfalls cannot have any outlet links - case OUTFALL: - if ( Node[j].degree > 0 ) - { - report_writeErrorMsg(ERR_OUTFALL, Node[j].ID); - } - break; - - // --- storage nodes can have multiple outlets - case STORAGE: break; - - // --- all other nodes allowed only one outlet link - default: - if ( Node[j].degree > 1 ) - { - report_writeErrorMsg(ERR_MULTI_OUTLET, Node[j].ID); - } - } - } - - // --- check links - for (j=0; j 1 ) - { - report_writeErrorMsg(ERR_DUMMY_LINK, Node[i].ID); - } - } - } - - // --- check each node to see if it qualifies as an outlet node - // (meaning that degree = 0) - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - // --- if node is of type Outfall, check that it has only 1 - // connecting link (which can either be an outflow or inflow link) - if ( Node[i].type == OUTFALL ) - { - if ( Node[i].degree + (int)Node[i].inflow > 1 ) - { - report_writeErrorMsg(ERR_OUTFALL, Node[i].ID); - } - else outletCount++; - } - } - if ( outletCount == 0 ) report_writeErrorMsg(ERR_NO_OUTLETS, ""); - - // --- reset node inflows back to zero - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - if ( Node[i].inflow == 0.0 ) Node[i].degree = -Node[i].degree; - Node[i].inflow = 0.0; - } -} - -//============================================================================= - -void initNodeDepths(void) -// -// Input: none -// Output: none -// Purpose: sets initial depth at nodes for Dynamic Wave flow routing. -// -{ - int i; // link or node index - int n; // node index - double y; // node water depth (ft) - - // --- use Node[].inflow as a temporary accumulator for depth in - // connecting links and Node[].outflow as a temporary counter - // for the number of connecting links - for (i = 0; i < Nobjects[NODE]; i++) - { - Node[i].inflow = 0.0; - Node[i].outflow = 0.0; - } - - // --- total up flow depths in all connecting links into nodes - for (i = 0; i < Nobjects[LINK]; i++) - { - if ( Link[i].newDepth > FUDGE ) y = Link[i].newDepth + Link[i].offset1; - else y = 0.0; - n = Link[i].node1; - Node[n].inflow += y; - Node[n].outflow += 1.0; - n = Link[i].node2; - Node[n].inflow += y; - Node[n].outflow += 1.0; - } - - // --- if no user-supplied depth then set initial depth at non-storage/ - // non-outfall nodes to average of depths in connecting links - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - if ( Node[i].type == OUTFALL ) continue; - if ( Node[i].type == STORAGE ) continue; - if ( Node[i].initDepth > 0.0 ) continue; - if ( Node[i].outflow > 0.0 ) - { - Node[i].newDepth = Node[i].inflow / Node[i].outflow; - } - } - - // --- compute initial depths at all outfall nodes - for ( i = 0; i < Nobjects[LINK]; i++ ) link_setOutfallDepth(i); -} - -//============================================================================= - -void initLinkDepths() -// -// Input: none -// Output: none -// Purpose: sets initial flow depths in conduits under Dyn. Wave routing. -// -{ - int i; // link index - double y, y1, y2; // depths (ft) - - // --- examine each link - for (i = 0; i < Nobjects[LINK]; i++) - { - // --- examine each conduit - if ( Link[i].type == CONDUIT ) - { - // --- skip conduits with user-assigned initial flows - // (their depths have already been set to normal depth) - if ( Link[i].q0 != 0.0 ) continue; - - // --- set depth to average of depths at end nodes - y1 = Node[Link[i].node1].newDepth - Link[i].offset1; - y1 = MAX(y1, 0.0); - y1 = MIN(y1, Link[i].xsect.yFull); - y2 = Node[Link[i].node2].newDepth - Link[i].offset2; - y2 = MAX(y2, 0.0); - y2 = MIN(y2, Link[i].xsect.yFull); - y = 0.5 * (y1 + y2); - y = MAX(y, FUDGE); - Link[i].newDepth = y; - } - } -} - -//============================================================================= - -void initNodes() -// -// Input: none -// Output: none -// Purpose: sets initial inflow/outflow and volume for each node -// -{ - int i; - - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - // --- initialize node inflow and outflow - Node[i].inflow = Node[i].newLatFlow; - Node[i].outflow = 0.0; - - // --- initialize node volume - Node[i].newVolume = 0.0; - if ( AllowPonding && - Node[i].pondedArea > 0.0 && - Node[i].newDepth > Node[i].fullDepth ) - { - Node[i].newVolume = Node[i].fullVolume + - (Node[i].newDepth - Node[i].fullDepth) * - Node[i].pondedArea; - } - else Node[i].newVolume = node_getVolume(i, Node[i].newDepth); - } - - // --- update nodal inflow/outflow at ends of each link - // (needed for Steady Flow & Kin. Wave routing) - for ( i = 0; i < Nobjects[LINK]; i++ ) - { - if ( Link[i].newFlow >= 0.0 ) - { - Node[Link[i].node1].outflow += Link[i].newFlow; - Node[Link[i].node2].inflow += Link[i].newFlow; - } - else - { - Node[Link[i].node1].inflow -= Link[i].newFlow; - Node[Link[i].node2].outflow -= Link[i].newFlow; - } - } -} - -//============================================================================= - -void initLinks(int routingModel) -// -// Input: none -// Output: none -// Purpose: sets initial upstream/downstream conditions in links. -// -{ - int i; // link index - int k; // conduit or pump index - - // --- examine each link - for ( i = 0; i < Nobjects[LINK]; i++ ) - { - if ( routingModel == SF) Link[i].newFlow = 0.0; - - // --- otherwise if link is a conduit - else if ( Link[i].type == CONDUIT ) - { - // --- assign initial flow to both ends of conduit - k = Link[i].subIndex; - Conduit[k].q1 = Link[i].newFlow / Conduit[k].barrels; - Conduit[k].q2 = Conduit[k].q1; - - // --- find areas based on initial flow depth - Conduit[k].a1 = xsect_getAofY(&Link[i].xsect, Link[i].newDepth); - Conduit[k].a2 = Conduit[k].a1; - - // --- compute initial volume from area - { - Link[i].newVolume = Conduit[k].a1 * link_getLength(i) * - Conduit[k].barrels; - } - Link[i].oldVolume = Link[i].newVolume; - } - } -} - -//============================================================================= - -double getLinkInflow(int j, double dt) -// -// Input: j = link index -// dt = routing time step (sec) -// Output: returns link inflow (cfs) -// Purpose: finds flow into upstream end of link at current time step under -// Steady or Kin. Wave routing. -// -{ - int n1 = Link[j].node1; - double q; - if ( Link[j].type == CONDUIT || - Link[j].type == PUMP || - Node[n1].type == STORAGE ) q = link_getInflow(j); - else q = 0.0; - return node_getMaxOutflow(n1, q, dt); -} - -//============================================================================= - -void updateStorageState(int i, int j, int links[], double dt) -// -// Input: i = index of storage node -// j = current position in links array -// links = array of topo-sorted link indexes -// dt = routing time step (sec) -// Output: none -// Purpose: updates depth and volume of a storage node using successive -// approximation with under-relaxation for Steady or Kin. Wave -// routing. -// -{ - int iter; // iteration counter - int stopped; // TRUE when iterations stop - double vFixed; // fixed terms of flow balance eqn. - double v2; // new volume estimate (ft3) - double d1; // initial value of storage depth (ft) - double d2; // updated value of storage depth (ft) - - // --- see if storage node needs updating - if ( Node[i].type != STORAGE ) return; - if ( Node[i].updated ) return; - - // --- compute terms of flow balance eqn. - // v2 = v1 + (inflow - outflow)*dt - // that do not depend on storage depth at end of time step - vFixed = Node[i].oldVolume + - 0.5 * (Node[i].oldNetInflow + Node[i].inflow - - Node[i].outflow) * dt; - d1 = Node[i].newDepth; - - // --- iterate finding outflow (which depends on depth) and subsequent - // new volume and depth until negligible depth change occurs - iter = 1; - stopped = FALSE; - while ( iter < MAXITER && !stopped ) - { - // --- find new volume from flow balance eqn. - v2 = vFixed - 0.5 * getStorageOutflow(i, j, links, dt) * dt; - - // --- limit volume to full volume if no ponding - // and compute overflow rate - v2 = MAX(0.0, v2); - Node[i].overflow = 0.0; - if ( v2 > Node[i].fullVolume ) - { - Node[i].overflow = (v2 - MAX(Node[i].oldVolume, - Node[i].fullVolume)) / dt; - if ( Node[i].overflow < FUDGE ) Node[i].overflow = 0.0; - if ( !AllowPonding || Node[i].pondedArea == 0.0 ) - v2 = Node[i].fullVolume; - } - - // --- update node's volume & depth - Node[i].newVolume = v2; - d2 = node_getDepth(i, v2); - Node[i].newDepth = d2; - - // --- use under-relaxation to estimate new depth value - // and stop if close enough to previous value - d2 = (1.0 - OMEGA)*d1 + OMEGA*d2; - if ( fabs(d2 - d1) <= STOPTOL ) stopped = TRUE; - - // --- update old depth with new value and continue to iterate - Node[i].newDepth = d2; - d1 = d2; - iter++; - } - - // --- mark node as being updated - Node[i].updated = TRUE; -} - -//============================================================================= - -double getStorageOutflow(int i, int j, int links[], double dt) -// -// Input: i = index of storage node -// j = current position in links array -// links = array of topo-sorted link indexes -// dt = routing time step (sec) -// Output: returns total outflow from storage node (cfs) -// Purpose: computes total flow released from a storage node. -// -{ - int k, m; - double outflow = 0.0; - - for (k = j; k < Nobjects[LINK]; k++) - { - m = links[k]; - if ( Link[m].node1 != i ) break; - outflow += getLinkInflow(m, dt); - } - return outflow; -} - -//============================================================================= - -void setNewNodeState(int j, double dt) -// -// Input: j = node index -// dt = time step (sec) -// Output: none -// Purpose: updates state of node after current time step -// for Steady Flow or Kinematic Wave flow routing. -// -{ - int canPond; // TRUE if ponding can occur at node - double newNetInflow; // inflow - outflow at node (cfs) - - // --- update terminal storage nodes - if ( Node[j].type == STORAGE ) - { - if ( Node[j].updated == FALSE ) - updateStorageState(j, Nobjects[LINK], NULL, dt); - } - - // --- update stored volume - newNetInflow = Node[j].inflow - Node[j].outflow - Node[j].losses; - Node[j].newVolume = Node[j].oldVolume + newNetInflow * dt; - if ( Node[j].newVolume < FUDGE ) Node[j].newVolume = 0.0; - - // --- determine any overflow lost from system - Node[j].overflow = 0.0; - canPond = (AllowPonding && Node[j].pondedArea > 0.0); - if ( Node[j].newVolume > Node[j].fullVolume ) - { - Node[j].overflow = (Node[j].newVolume - MAX(Node[j].oldVolume, - Node[j].fullVolume)) / dt; - if ( Node[j].overflow < FUDGE ) Node[j].overflow = 0.0; - if ( !canPond ) Node[j].newVolume = Node[j].fullVolume; - } - - // --- compute a depth from volume - // (depths at upstream nodes are subsequently adjusted in - // setNewLinkState to reflect depths in connected conduit) - Node[j].newDepth = node_getDepth(j, Node[j].newVolume); -} - -//============================================================================= - -void setNewLinkState(int j) -// -// Input: j = link index -// Output: none -// Purpose: updates state of link after current time step under -// Steady Flow or Kinematic Wave flow routing -// -{ - int k; - double a, y1, y2; - - Link[j].newDepth = 0.0; - Link[j].newVolume = 0.0; - - if ( Link[j].type == CONDUIT ) - { - // --- find avg. depth from entry/exit conditions - k = Link[j].subIndex; - a = 0.5 * (Conduit[k].a1 + Conduit[k].a2); - Link[j].newVolume = a * link_getLength(j) * Conduit[k].barrels; - y1 = xsect_getYofA(&Link[j].xsect, Conduit[k].a1); - y2 = xsect_getYofA(&Link[j].xsect, Conduit[k].a2); - Link[j].newDepth = 0.5 * (y1 + y2); - - // --- update depths at end nodes - updateNodeDepth(Link[j].node1, y1 + Link[j].offset1); - updateNodeDepth(Link[j].node2, y2 + Link[j].offset2); - - // --- check if capacity limited - if ( Conduit[k].a1 >= Link[j].xsect.aFull ) - { - Conduit[k].capacityLimited = TRUE; - Conduit[k].fullState = ALL_FULL; - } - else - { - Conduit[k].capacityLimited = FALSE; - Conduit[k].fullState = 0; - } - } -} - -//============================================================================= - -void updateNodeDepth(int i, double y) -// -// Input: i = node index -// y = flow depth (ft) -// Output: none -// Purpose: updates water depth at a node with a possibly higher value. -// -{ - // --- storage nodes were updated elsewhere - if ( Node[i].type == STORAGE ) return; - - // --- if non-outfall node is flooded, then use full depth - if ( Node[i].type != OUTFALL && Node[i].degree > 0 && - Node[i].overflow > 0.0 ) y = Node[i].fullDepth; - - // --- if current new depth below y - if ( Node[i].newDepth < y ) - { - // --- update new depth - Node[i].newDepth = y; - - // --- depth cannot exceed full depth (if value exists) - if ( Node[i].fullDepth > 0.0 && y > Node[i].fullDepth ) - { - Node[i].newDepth = Node[i].fullDepth; - } - } -} - -//============================================================================= - -int steadyflow_execute(int j, double* qin, double* qout, double tStep) -// -// Input: j = link index -// qin = inflow to link (cfs) -// tStep = time step (sec) -// Output: qin = adjusted inflow to link (limited by flow capacity) (cfs) -// qout = link's outflow (cfs) -// returns 1 if successful -// Purpose: performs steady flow routing through a single link. -// -{ - int k; - double s; - double q; - - // --- use Manning eqn. to compute flow area for conduits - if ( Link[j].type == CONDUIT ) - { - k = Link[j].subIndex; - q = (*qin) / Conduit[k].barrels; - if ( Link[j].xsect.type == DUMMY ) Conduit[k].a1 = 0.0; - else - { - // --- adjust flow for evap and infil losses - q -= link_getLossRate(j, q); - - // --- flow can't exceed full flow - if ( q > Link[j].qFull ) - { - q = Link[j].qFull; - Conduit[k].a1 = Link[j].xsect.aFull; - (*qin) = q * Conduit[k].barrels; - } - - // --- infer flow area from flow rate - else - { - s = q / Conduit[k].beta; - Conduit[k].a1 = xsect_getAofS(&Link[j].xsect, s); - } - } - Conduit[k].a2 = Conduit[k].a1; - - Conduit[k].q1Old = Conduit[k].q1; - Conduit[k].q2Old = Conduit[k].q2; - - Conduit[k].q1 = q; - Conduit[k].q2 = q; - (*qout) = q * Conduit[k].barrels; - } - else (*qout) = (*qin); - return 1; -} - -//============================================================================= diff --git a/src/forcmain.c b/src/forcmain.c deleted file mode 100644 index a6314bbce..000000000 --- a/src/forcmain.c +++ /dev/null @@ -1,157 +0,0 @@ -//----------------------------------------------------------------------------- -// forcemain.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Special Non-Manning Force Main functions -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double VISCOS = 1.1E-5; // Kinematic viscosity of water - // @ 20 deg C (sq ft/sec) - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// forcemain_getEquivN -// forcemain_getRoughFactor -// forcemain_getFricSlope - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static double forcemain_getFricFactor(double e, double hrad, double re); -static double forcemain_getReynolds(double v, double hrad); - -//============================================================================= - -double forcemain_getEquivN(int j, int k) -// -// Input: j = link index -// k = conduit index -// Output: returns an equivalent Manning's n for a force main -// Purpose: computes a Mannng's n that results in the same normal flow -// value for a force main flowing full under fully turbulent -// conditions using either the Hazen-Williams or Dary-Weisbach -// flow equations. -// -{ - TXsect xsect = Link[j].xsect; - double f; - double d = xsect.yFull; - switch ( ForceMainEqn ) - { - case H_W: - return 1.067 / xsect.rBot * pow(d/Conduit[k].slope, 0.04); - case D_W: - f = forcemain_getFricFactor(xsect.rBot, d/4.0, 1.0e12); - return sqrt(f/185.0) * pow(d, (1./6.)); - } - return Conduit[k].roughness; -} - -//============================================================================= - -double forcemain_getRoughFactor(int j, double lengthFactor) -// -// Input: j = link index -// lengthFactor = factor by which a pipe will be artifically lengthened -// Output: returns a roughness adjustment factor for a force main -// Purpose: computes an adjustment factor for a force main that compensates for -// any artificial lengthening the pipe may have received. -// -{ - TXsect xsect = Link[j].xsect; - double r; - switch ( ForceMainEqn ) - { - case H_W: - r = 1.318*xsect.rBot*pow(lengthFactor, 0.54); - return GRAVITY / pow(r, 1.852); - case D_W: - return 1.0/8.0/lengthFactor; - } - return 0.0; -} - -//============================================================================= - -double forcemain_getFricSlope(int j, double v, double hrad) -// -// Input: j = link index -// v = flow velocity (ft/sec) -// hrad = hydraulic radius (ft) -// Output: returns a force main pipe's friction slope -// Purpose: computes the headloss per unit length used in dynamic wave -// flow routing for a pressurized force main using either the -// Hazen-Williams or Darcy-Weisbach flow equations. -// Note: the pipe's roughness factor was saved in xsect.sBot in -// conduit_validate() in LINK.C. -// -{ - double re, f; - TXsect xsect = Link[j].xsect; - switch ( ForceMainEqn ) - { - case H_W: - return xsect.sBot * pow(v, 0.852) / pow(hrad, 1.1667); - case D_W: - re = forcemain_getReynolds(v, hrad); - f = forcemain_getFricFactor(xsect.rBot, hrad, re); - return f * xsect.sBot * v / hrad; - } - return 0.0; -} - -//============================================================================= - -double forcemain_getReynolds(double v, double hrad) -// -// Input: v = flow velocity (ft/sec) -// hrad = hydraulic radius (ft) -// Output: returns a flow's Reynolds Number -// Purpose: computes a flow's Reynolds Number -// -{ - return 4.0 * hrad * v / VISCOS; -} - -//============================================================================= - -double forcemain_getFricFactor(double e, double hrad, double re) -// -// Input: e = roughness height (ft) -// hrad = hydraulic radius (ft) -// re = Reynolds number -// Output: returns a Darcy-Weisbach friction factor -// Purpose: computes the Darcy-Weisbach friction factor for a force main -// using the Swamee and Jain approximation to the Colebrook-White -// equation. -// -{ - double f; - if ( re < 10.0 ) re = 10.0; - if ( re <= 2000.0 ) f = 64.0 / re; - else if ( re < 4000.0 ) - { - f = forcemain_getFricFactor(e, hrad, 4000.0); - f = 0.032 + (f - 0.032) * ( re - 2000.0) / 2000.0; - } - else - { - f = e/3.7/(4.0*hrad); - if ( re < 1.0e10 ) f += 5.74/pow(re, 0.9); - f = log10(f); - f = 0.25 / f / f; - } - return f; -} diff --git a/src/funcs.h b/src/funcs.h deleted file mode 100644 index 6f9782723..000000000 --- a/src/funcs.h +++ /dev/null @@ -1,547 +0,0 @@ -//----------------------------------------------------------------------------- -// funcs.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// M. Tryby (EPA) -// -// Global interfacing functions. -// -// Update History -// ============== -// Build 5.1.007: -// - climate_readAdjustments() added. -// Build 5.1.008: -// - Function list was re-ordered and blank lines added for readability. -// - Pollutant buildup/washoff functions for the new surfqual.c module added. -// - Several other functions added, re-named or have modified arguments. -// Build 5.1.010: -// - New roadway_getInflow() function added. -// Build 5.1.013: -// - Additional arguments added to function stats_updateSubcatchStats. -// Build 5.1.014: -// - Arguments to link_getLossRate function changed. -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for reporting most frequent non-converging links. -// - Support added for named variables & math expressions in control rules. -// - Support added for tracking a gage's prior n-hour rainfall total. -// - Refactored external inflow code. -//----------------------------------------------------------------------------- - -#ifndef FUNCS_H -#define FUNCS_H - -//----------------------------------------------------------------------------- -// Project Methods -//----------------------------------------------------------------------------- -void project_open(const char *f1, const char *f2, const char *f3); -void project_close(void); - -void project_readInput(void); -int project_readOption(char* s1, char* s2); -void project_validate(void); -int project_init(void); - -int project_addObject(int type, char* id, int n); -int project_findObject(int type, const char* id); -char* project_findID(int type, char* id); - -double** project_createMatrix(int nrows, int ncols); -void project_freeMatrix(double** m); - -//----------------------------------------------------------------------------- -// Input Reader Methods -//----------------------------------------------------------------------------- -int input_countObjects(void); -int input_readData(void); - -//----------------------------------------------------------------------------- -// Report Writer Methods -//----------------------------------------------------------------------------- -int report_readOptions(char* tok[], int ntoks); - -void report_writeLine(const char* line); -void report_writeSysTime(void); -void report_writeLogo(void); -void report_writeTitle(void); -void report_writeOptions(void); -void report_writeReport(void); - -void report_writeRainStats(int gage, TRainStats* rainStats); -void report_writeRdiiStats(double totalRain, double totalRdii); - -void report_writeControlActionsHeading(void); -void report_writeControlAction(DateTime aDate, char* linkID, double value, - char* ruleID); - -void report_writeRunoffError(TRunoffTotals* totals, double area); -void report_writeLoadingError(TLoadingTotals* totals); -void report_writeGwaterError(TGwaterTotals* totals, double area); -void report_writeFlowError(TRoutingTotals* totals); -void report_writeQualError(TRoutingTotals* totals); - -void report_writeMaxStats(TMaxStats massBalErrs[], TMaxStats CourantCrit[], - int nMaxStats); -void report_writeMaxFlowTurns(TMaxStats flowTurns[], int nMaxStats); -void report_writeNonconvergedStats(TMaxStats maxNonconverged[], - int nMaxStats); -void report_writeTimeStepStats(TTimeStepStats* timeStepStats); - -void report_writeErrorMsg(int code, char* msg); -void report_writeErrorCode(void); -void report_writeInputErrorMsg(int k, int sect, char* line, long lineCount); -void report_writeWarningMsg(char* msg, char* id); -void report_writeTseriesErrorMsg(int code, TTable *tseries); - -void inputrpt_writeInput(void); -void statsrpt_writeReport(void); - -//----------------------------------------------------------------------------- -// Temperature/Evaporation Methods -//----------------------------------------------------------------------------- -int climate_readParams(char* tok[], int ntoks); -int climate_readEvapParams(char* tok[], int ntoks); -int climate_readAdjustments(char* tok[], int ntoks); -void climate_validate(void); -void climate_openFile(void); -void climate_initState(void); -void climate_setState(DateTime aDate); -DateTime climate_getNextEvapDate(void); - -//----------------------------------------------------------------------------- -// Rainfall Processing Methods -//----------------------------------------------------------------------------- -void rain_open(void); -void rain_close(void); - -//----------------------------------------------------------------------------- -// Snowmelt Processing Methods -//----------------------------------------------------------------------------- -int snow_readMeltParams(char* tok[], int ntoks); -int snow_createSnowpack(int subcacth, int snowIndex); - -void snow_validateSnowmelt(int snowIndex); -void snow_initSnowpack(int subcatch); -void snow_initSnowmelt(int snowIndex); - -void snow_getState(int subcatch, int subArea, double x[]); -void snow_setState(int subcatch, int subArea, double x[]); - -void snow_setMeltCoeffs(int snowIndex, double season); -void snow_plowSnow(int subcatch, double tStep); -double snow_getSnowMelt(int subcatch, double rainfall, double snowfall, - double tStep, double netPrecip[]); -double snow_getSnowCover(int subcatch); - -//----------------------------------------------------------------------------- -// Runoff Analyzer Methods -//----------------------------------------------------------------------------- -int runoff_open(void); -void runoff_execute(void); -void runoff_close(void); - -//----------------------------------------------------------------------------- -// Conveyance System Routing Methods -//----------------------------------------------------------------------------- -int routing_open(void); -double routing_getRoutingStep(int routingModel, double fixedStep); -void routing_execute(int routingModel, double routingStep); -void routing_close(int routingModel); - -//----------------------------------------------------------------------------- -// Output Filer Methods -//----------------------------------------------------------------------------- -int output_open(void); -void output_end(void); -void output_close(void); -void output_saveResults(double reportTime); -void output_updateAvgResults(void); -void output_readDateTime(long period, DateTime *aDate); -void output_readSubcatchResults(long period, int index); -void output_readNodeResults(int long, int index); -void output_readLinkResults(int long, int index); - -//----------------------------------------------------------------------------- -// Groundwater Methods -//----------------------------------------------------------------------------- -int gwater_readAquiferParams(int aquifer, char* tok[], int ntoks); -int gwater_readGroundwaterParams(char* tok[], int ntoks); -int gwater_readFlowExpression(char* tok[], int ntoks); -void gwater_deleteFlowExpression(int subcatch); - -void gwater_validateAquifer(int aquifer); -void gwater_validate(int subcatch); - -void gwater_initState(int subcatch); -void gwater_getState(int subcatch, double x[]); -void gwater_setState(int subcatch, double x[]); - -void gwater_getGroundwater(int subcatch, double evap, double infil, - double tStep); -double gwater_getVolume(int subcatch); - -//----------------------------------------------------------------------------- -// RDII Methods -//----------------------------------------------------------------------------- -int rdii_readRdiiInflow(char* tok[], int ntoks); -void rdii_deleteRdiiInflow(int node); -void rdii_initUnitHyd(int unitHyd); -int rdii_readUnitHydParams(char* tok[], int ntoks); -void rdii_openRdii(void); -void rdii_closeRdii(void); -int rdii_getNumRdiiFlows(DateTime aDate); -void rdii_getRdiiFlow(int index, int* node, double* q); - -//----------------------------------------------------------------------------- -// Landuse Methods -//----------------------------------------------------------------------------- -int landuse_readParams(int landuse, char* tok[], int ntoks); -int landuse_readPollutParams(int pollut, char* tok[], int ntoks); -int landuse_readBuildupParams(char* tok[], int ntoks); -int landuse_readWashoffParams(char* tok[], int ntoks); - -void landuse_getInitBuildup(TLandFactor* landFactor, double* initBuildup, - double area, double curb); -double landuse_getBuildup(int landuse, int pollut, double area, double curb, - double buildup, double tStep); - -double landuse_getWashoffLoad(int landuse, int p, double area, - TLandFactor landFactor[], double runoff, double vOutflow); -double landuse_getAvgBmpEffic(int j, int p); -double landuse_getCoPollutLoad(int p, double washoff[]); - -//----------------------------------------------------------------------------- -// Flow/Quality Routing Methods -//----------------------------------------------------------------------------- -void flowrout_init(int routingModel); -void flowrout_close(int routingModel); -double flowrout_getRoutingStep(int routingModel, double fixedStep); -int flowrout_execute(int links[], int routingModel, double tStep); - -void toposort_sortLinks(int links[]); -int kinwave_execute(int link, double* qin, double* qout, double tStep); - -void dynwave_validate(void); -void dynwave_init(void); -void dynwave_close(void); -double dynwave_getRoutingStep(double fixedStep); -int dynwave_execute(double tStep); -void dwflow_findConduitFlow(int j, int steps, double omega, double dt); - -void qualrout_init(void); -void qualrout_execute(double tStep); - -//----------------------------------------------------------------------------- -// Treatment Methods -//----------------------------------------------------------------------------- -int treatmnt_open(void); -void treatmnt_close(void); -int treatmnt_readExpression(char* tok[], int ntoks); -void treatmnt_delete(int node); -void treatmnt_treat(int node, double q, double v, double tStep); -void treatmnt_setInflow(double qIn, double wIn[]); - -//----------------------------------------------------------------------------- -// Mass Balance Methods -//----------------------------------------------------------------------------- -int massbal_open(void); -void massbal_close(void); -void massbal_report(void); - -void massbal_updateRunoffTotals(int type, double v); -void massbal_updateLoadingTotals(int type, int pollut, double w); -void massbal_updateGwaterTotals(double vInfil, double vUpperEvap, - double vLowerEvap, double vLowerPerc, double vGwater); -void massbal_updateRoutingTotals(double tStep); - - -void massbal_initTimeStepTotals(void); -void massbal_addInflowFlow(int type, double q); -void massbal_addInflowQual(int type, int pollut, double w); -void massbal_addOutflowFlow(double q, int isFlooded); -void massbal_addOutflowQual(int pollut, double mass, int isFlooded); -void massbal_addNodeLosses(double evapLoss, double infilLoss); -void massbal_addLinkLosses(double evapLoss, double infilLoss); -void massbal_addReactedMass(int pollut, double mass); -void massbal_addSeepageLoss(int pollut, double seepLoss); -void massbal_addToFinalStorage(int pollut, double mass); -double massbal_getStepFlowError(void); -double massbal_getRunoffError(void); -double massbal_getFlowError(void); - -//----------------------------------------------------------------------------- -// Simulation Statistics Methods -//----------------------------------------------------------------------------- -int stats_open(void); -void stats_close(void); -void stats_report(void); - -void stats_updateCriticalTimeCount(int node, int link); -void stats_updateFlowStats(double tStep, DateTime aDate); -void stats_updateTimeStepStats(double tStep, int trialsCount, int steadyState); - -void stats_updateSubcatchStats(int subcatch, double rainVol, - double runonVol, double evapVol, double infilVol, - double impervVol, double pervVol, double runoffVol, double runoff); -void stats_updateGwaterStats(int j, double infil, double evap, - double latFlow, double deepFlow, double theta, double waterTable, - double tStep); -void stats_updateMaxRunoff(void); -void stats_updateMaxNodeDepth(int node, double depth); -void stats_updateConvergenceStats(int node, int converged); - - -//----------------------------------------------------------------------------- -// Raingage Methods -//----------------------------------------------------------------------------- -int gage_readParams(int gage, char* tok[], int ntoks); -void gage_validate(int gage); -void gage_initState(int gage); -void gage_setState(int gage, DateTime aDate); -double gage_getPrecip(int gage, double *rainfall, double *snowfall); -void gage_setReportRainfall(int gage, DateTime aDate); -DateTime gage_getNextRainDate(int gage, DateTime aDate); -void gage_updatePastRain(int j, int tStep); -double gage_getPastRain(int gage, int hrs); - -//----------------------------------------------------------------------------- -// Subcatchment Methods -//----------------------------------------------------------------------------- -int subcatch_readParams(int subcatch, char* tok[], int ntoks); -int subcatch_readSubareaParams(char* tok[], int ntoks); -int subcatch_readLanduseParams(char* tok[], int ntoks); -int subcatch_readInitBuildup(char* tok[], int ntoks); - -void subcatch_validate(int subcatch); -void subcatch_initState(int subcatch); -void subcatch_setOldState(int subcatch); - -double subcatch_getFracPerv(int subcatch); -double subcatch_getStorage(int subcatch); -double subcatch_getDepth(int subcatch); - -void subcatch_getRunon(int subcatch); -void subcatch_addRunonFlow(int subcatch, double flow); -double subcatch_getRunoff(int subcatch, double tStep); - -double subcatch_getWtdOutflow(int subcatch, double wt); -void subcatch_getResults(int subcatch, double wt, float x[]); - -//----------------------------------------------------------------------------- -// Surface Pollutant Buildup/Washoff Methods -//----------------------------------------------------------------------------- -void surfqual_initState(int subcatch); -void surfqual_getWashoff(int subcatch, double runoff, double tStep); -void surfqual_getBuildup(int subcatch, double tStep); -void surfqual_sweepBuildup(int subcatch, DateTime aDate); -double surfqual_getWtdWashoff(int subcatch, int pollut, double wt); - -//----------------------------------------------------------------------------- -// Conveyance System Node Methods -//----------------------------------------------------------------------------- -int node_readParams(int node, int type, int subIndex, char* tok[], int ntoks); -void node_validate(int node); - -void node_initState(int node); -void node_initFlows(int node, double tStep); -void node_setOldHydState(int node); -void node_setOldQualState(int node); -void node_setOutletDepth(int node, double yNorm, double yCrit, double z); - -double node_getSurfArea(int node, double depth); -double node_getDepth(int node, double volume); -double node_getVolume(int node, double depth); -double node_getPondedArea(int node, double depth); - -double node_getOutflow(int node, int link); -double node_getLosses(int node, double tStep); -double node_getMaxOutflow(int node, double q, double tStep); -double node_getSystemOutflow(int node, int *isFlooded); -void node_getResults(int node, double wt, float x[]); - -//----------------------------------------------------------------------------- -// Conveyance System Inflow Methods -//----------------------------------------------------------------------------- -int inflow_readExtInflow(char* tok[], int ntoks); -int inflow_readDwfInflow(char* tok[], int ntoks); -int inflow_readDwfPattern(char* tok[], int ntoks); -int inflow_setExtInflow(int j, int param, int type, int tSeries, - int basePat, double cf, double baseline, double sf); - -void inflow_initDwfInflow(TDwfInflow* inflow); -void inflow_initDwfPattern(int pattern); - -double inflow_getExtInflow(TExtInflow* inflow, DateTime aDate); -double inflow_getDwfInflow(TDwfInflow* inflow, int m, int d, int h); - -void inflow_deleteExtInflows(int node); -void inflow_deleteDwfInflows(int node); - -//----------------------------------------------------------------------------- -// Routing Interface File Methods -//----------------------------------------------------------------------------- -int iface_readFileParams(char* tok[], int ntoks); -void iface_openRoutingFiles(void); -void iface_closeRoutingFiles(void); -int iface_getNumIfaceNodes(DateTime aDate); -int iface_getIfaceNode(int index); -double iface_getIfaceFlow(int index); -double iface_getIfaceQual(int index, int pollut); -void iface_saveOutletResults(DateTime reportDate, FILE* file); - -//----------------------------------------------------------------------------- -// Hot Start File Methods -//----------------------------------------------------------------------------- -int hotstart_open(void); -void hotstart_close(void); - -//----------------------------------------------------------------------------- -// Conveyance System Link Methods -//----------------------------------------------------------------------------- -int link_readParams(int link, int type, int subIndex, char* tok[], int ntoks); -int link_readXsectParams(char* tok[], int ntoks); -int link_readLossParams(char* tok[], int ntoks); - -void link_validate(int link); -void link_initState(int link); -void link_setOldHydState(int link); -void link_setOldQualState(int link); - -void link_setTargetSetting(int j); -void link_setSetting(int j, double tstep); -int link_setFlapGate(int link, int n1, int n2, double q); - -double link_getInflow(int link); -void link_setOutfallDepth(int link); -double link_getLength(int link); -double link_getYcrit(int link, double q); -double link_getYnorm(int link, double q); -double link_getVelocity(int link, double q, double y); -double link_getFroude(int link, double v, double y); -double link_getPower(int link); -double link_getLossRate(int link, double q); -char link_getFullState(double a1, double a2, double aFull); - -void link_getResults(int link, double wt, float x[]); - -//----------------------------------------------------------------------------- -// Link Cross-Section Methods -//----------------------------------------------------------------------------- -int xsect_isOpen(int type); -int xsect_setParams(TXsect *xsect, int type, double p[], double ucf); -void xsect_setIrregXsectParams(TXsect *xsect); -void xsect_setCustomXsectParams(TXsect *xsect); -void xsect_setStreetXsectParams(TXsect *xsect); -double xsect_getAmax(TXsect* xsect); - -double xsect_getSofA(TXsect* xsect, double area); -double xsect_getYofA(TXsect* xsect, double area); -double xsect_getRofA(TXsect* xsect, double area); -double xsect_getAofS(TXsect* xsect, double sFactor); -double xsect_getdSdA(TXsect* xsect, double area); -double xsect_getAofY(TXsect* xsect, double y); -double xsect_getRofY(TXsect* xsect, double y); -double xsect_getWofY(TXsect* xsect, double y); -double xsect_getYcrit(TXsect* xsect, double q); - -//----------------------------------------------------------------------------- -// Culvert/Roadway Methods -//----------------------------------------------------------------------------- -double culvert_getInflow(int link, double q, double h); -double roadway_getInflow(int link, double dir, double hcrest, double h1, - double h2); - -//----------------------------------------------------------------------------- -// Force Main Methods -//----------------------------------------------------------------------------- -double forcemain_getEquivN(int j, int k); -double forcemain_getRoughFactor(int j, double lengthFactor); -double forcemain_getFricSlope(int j, double v, double hrad); - -//----------------------------------------------------------------------------- -// Cross-Section Transect Methods -//----------------------------------------------------------------------------- -int transect_create(int n); -void transect_delete(void); -int transect_readParams(int* count, char* tok[], int ntoks); -void transect_validate(int j); -void transect_createStreetTransect(TStreet* street); - -//----------------------------------------------------------------------------- -// Street Cross-Section Methods -//----------------------------------------------------------------------------- -int street_create(int nStreets); -void street_delete(); -int street_readParams(char* tok[], int ntoks); -double street_getExtentFilled(int link); - -//----------------------------------------------------------------------------- -// Custom Shape Cross-Section Methods -//----------------------------------------------------------------------------- -int shape_validate(TShape *shape, TTable *curve); - -//----------------------------------------------------------------------------- -// Control Rule Methods -//----------------------------------------------------------------------------- -int controls_create(int n); -void controls_delete(void); -void controls_init(void); -void controls_addToCount(char* s); -int controls_addVariable(char* tok[], int ntoks); -int controls_addExpression(char* tok[], int ntoks); -int controls_addRuleClause(int rule, int keyword, char* Tok[], int nTokens); -int controls_evaluate(DateTime currentTime, DateTime elapsedTime, - double tStep); - -//----------------------------------------------------------------------------- -// Table & Time Series Methods -//----------------------------------------------------------------------------- -int table_readCurve(char* tok[], int ntoks); -int table_readTimeseries(char* tok[], int ntoks); - -int table_addEntry(TTable* table, double x, double y); -int table_getFirstEntry(TTable* table, double* x, double* y); -int table_getNextEntry(TTable* table, double* x, double* y); -void table_deleteEntries(TTable* table); - -void table_init(TTable* table); -int table_validate(TTable* table); - -double table_lookup(TTable* table, double x); -double table_lookupEx(TTable* table, double x); -double table_intervalLookup(TTable* table, double x); -double table_inverseLookup(TTable* table, double y); - -double table_getSlope(TTable *table, double x); -double table_getMaxY(TTable *table, double x); -double table_getStorageVolume(TTable* table, double x); -double table_getStorageDepth(TTable* table, double v); - -void table_tseriesInit(TTable *table); -double table_tseriesLookup(TTable* table, double t, char extend); - -//----------------------------------------------------------------------------- -// Utility Methods -//----------------------------------------------------------------------------- -double UCF(int quantity); // units conversion factor -int getInt(char *s, int *y); // get integer from string -int getFloat(char *s, float *y); // get float from string -int getDouble(char *s, double *y); // get double from string -char* getTempFileName(char *s); // get temporary file name -int findmatch(char *s, char *keyword[]); // search for matching keyword -int match(char *str, char *substr); // true if substr matches part of str -int strcomp(const char *s1, const char *s2); // case insensitive string compare -size_t sstrncpy(char *dest, const char *src, - size_t n); // safe string copy -size_t sstrcat(char* dest, const char* src, - size_t destsize); // safe string concatenation -void writecon(const char *s); // writes string to console -DateTime getDateTime(double elapsedMsec); // convert elapsed time to date -void getElapsedTime(DateTime aDate, // convert elapsed date - int* days, int* hrs, int* mins); -char* addAbsolutePath(char *fname); // add full path to a file name - -#endif //FUNCS_H diff --git a/src/gage.c b/src/gage.c deleted file mode 100644 index 031cc017c..000000000 --- a/src/gage.c +++ /dev/null @@ -1,705 +0,0 @@ -//----------------------------------------------------------------------------- -// gage.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Rain gage functions. -// -// Update History -// ============== -// Build 5.1.007: -// - Support for monthly rainfall adjustments added. -// Build 5.1.013: -// - Validation no longer performed on unused gages. -// Build 5.2.0: -// - Support added for tracking a gage's prior n-hour rainfall total. -// - Support added for relative file names. -// - Support added for setting rainfall through API call. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -const double OneSecond = 1.1574074e-5; - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// gage_readParams (called by input_readLine) -// gage_validate (called by project_validate) -// gage_initState (called by project_init) -// gage_setState (called by runoff_execute & getRainfall in rdii.c) -// gage_getPrecip (called by subcatch_getRunoff) -// gage_getNextRainDate (called by runoff_getTimeStep) -// gage_updatePastRain (called by runoff_execute) -// gage_getPastRain (called by getRainValue in controls.c) -// gage_setReportRainfall (called by output_saveSubcatchResults) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int readGageSeriesFormat(char* tok[], int ntoks, double x[]); -static int readGageFileFormat(char* tok[], int ntoks, double x[]); -static int getFirstRainfall(int gage); -static int getNextRainfall(int gage); -static double convertRainfall(int gage, double rain); -static void initPastRain(int gage); - -//============================================================================= - -int gage_readParams(int j, char* tok[], int ntoks) -// -// Input: j = rain gage index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads rain gage parameters from a line of input data -// -// Data formats are: -// Name RainType RecdFreq SCF TIMESERIES SeriesName -// Name RainType RecdFreq SCF FILE FileName Station Units StartDate -// -{ - int k, err; - char *id; - char fname[MAXFNAME+1]; - char staID[MAXMSG+1]; - double x[7]; - - // --- check that gage exists - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(GAGE, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - - // --- assign default parameter values - x[0] = -1.0; // No time series index - x[1] = 1.0; // Rain type is volume - x[2] = 3600.0; // Recording freq. is 3600 sec - x[3] = 1.0; // Snow catch deficiency factor - x[4] = NO_DATE; // Default is no start/end date - x[5] = NO_DATE; - x[6] = 0.0; // US units - fname[0] = '\0'; - staID[0] = '\0'; - - if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); - k = findmatch(tok[4], GageDataWords); - if ( k == RAIN_TSERIES ) - { - err = readGageSeriesFormat(tok, ntoks, x); - } - else if ( k == RAIN_FILE ) - { - if ( ntoks < 8 ) return error_setInpError(ERR_ITEMS, ""); - sstrncpy(fname, tok[5], MAXFNAME); - sstrncpy(staID, tok[6], MAXMSG); - err = readGageFileFormat(tok, ntoks, x); - } - else return error_setInpError(ERR_KEYWORD, tok[4]); - - // --- save parameters to rain gage object - if ( err > 0 ) return err; - Gage[j].ID = id; - Gage[j].tSeries = (int)x[0]; - Gage[j].rainType = (int)x[1]; - Gage[j].rainInterval = (int)x[2]; - Gage[j].snowFactor = x[3]; - Gage[j].rainUnits = (int)x[6]; - if ( Gage[j].tSeries >= 0 ) Gage[j].dataSource = RAIN_TSERIES; - else Gage[j].dataSource = RAIN_FILE; - if ( Gage[j].dataSource == RAIN_FILE ) - { - sstrncpy(Gage[j].fname, addAbsolutePath(fname), MAXFNAME); - sstrncpy(Gage[j].staID, staID, MAXMSG); - Gage[j].startFileDate = x[4]; - Gage[j].endFileDate = x[5]; - } - Gage[j].unitsFactor = 1.0; - Gage[j].coGage = -1; - Gage[j].isUsed = FALSE; - return 0; -} - -//============================================================================= - -int readGageSeriesFormat(char* tok[], int ntoks, double x[]) -{ - int m, ts; - DateTime aTime; - - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - - // --- determine type of rain data - m = findmatch(tok[1], RainTypeWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - x[1] = (double)m; - - // --- get data time interval & convert to seconds - if ( getDouble(tok[2], &x[2]) ) x[2] = floor(x[2]*3600 + 0.5); - else if ( datetime_strToTime(tok[2], &aTime) ) - { - x[2] = floor(aTime*SECperDAY + 0.5); - } - else return error_setInpError(ERR_DATETIME, tok[2]); - if ( x[2] <= 0.0 ) return error_setInpError(ERR_DATETIME, tok[2]); - - // --- get snow catch deficiency factor - if ( !getDouble(tok[3], &x[3]) ) - return error_setInpError(ERR_DATETIME, tok[3]);; - - // --- get time series index - ts = project_findObject(TSERIES, tok[5]); - if ( ts < 0 ) return error_setInpError(ERR_NAME, tok[5]); - x[0] = (double)ts; - sstrncpy(tok[2], "", 0); - return 0; -} - -//============================================================================= - -int readGageFileFormat(char* tok[], int ntoks, double x[]) -{ - int m, u; - DateTime aDate; - DateTime aTime; - - // --- determine type of rain data - m = findmatch(tok[1], RainTypeWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - x[1] = (double)m; - - // --- get data time interval & convert to seconds - if ( getDouble(tok[2], &x[2]) ) x[2] *= 3600; - else if ( datetime_strToTime(tok[2], &aTime) ) - { - x[2] = floor(aTime*SECperDAY + 0.5); - } - else return error_setInpError(ERR_DATETIME, tok[2]); - if ( x[2] <= 0.0 ) return error_setInpError(ERR_DATETIME, tok[2]); - - // --- get snow catch deficiency factor - if ( !getDouble(tok[3], &x[3]) ) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- get rain depth units - u = findmatch(tok[7], RainUnitsWords); - if ( u < 0 ) return error_setInpError(ERR_KEYWORD, tok[7]); - x[6] = (double)u; - - // --- get start date (if present) - if ( ntoks > 8 && *tok[8] != '*') - { - if ( !datetime_strToDate(tok[8], &aDate) ) - return error_setInpError(ERR_DATETIME, tok[8]); - x[4] = (float) aDate; - } - return 0; -} - -//============================================================================= - -void gage_validate(int j) -// -// Input: j = rain gage index -// Output: none -// Purpose: checks for valid rain gage parameters -// -// NOTE: assumes that any time series used by a rain gage has been -// previously validated. -// -{ - int i, k; - int gageInterval; - - // --- for gage with time series data: - if ( Gage[j].dataSource == RAIN_TSERIES ) - { - // --- no validation for an unused gage - if ( !Gage[j].isUsed ) return; - - // --- see if gage uses same time series as another gage - k = Gage[j].tSeries; - for (i=0; i= 0 ) - { - report_writeErrorMsg(ERR_RAIN_GAGE_TSERIES, Gage[j].ID); - } - gageInterval = (int)(floor(Tseries[k].dxMin*SECperDAY + 0.5)); - if ( gageInterval > 0 && Gage[j].rainInterval > gageInterval ) - { - report_writeErrorMsg(ERR_RAIN_GAGE_INTERVAL, Gage[j].ID); - } - if ( Gage[j].rainInterval < gageInterval ) - { - report_writeWarningMsg(WARN09, Gage[j].ID); - } - if ( Gage[j].rainInterval < WetStep ) - { - report_writeWarningMsg(WARN01, Gage[j].ID); - WetStep = Gage[j].rainInterval; - } - } -} - -//============================================================================= - -void gage_initState(int j) -// -// Input: j = rain gage index -// Output: none -// Purpose: initializes state of rain gage. -// -{ - // --- initialize actual and reported rainfall - Gage[j].rainfall = 0.0; - Gage[j].apiRainfall = MISSING; - Gage[j].reportRainfall = 0.0; - if ( IgnoreRainfall ) return; - - // --- for gage with file data: - if ( Gage[j].dataSource == RAIN_FILE ) - { - // --- set current file position to start of period of record - Gage[j].currentFilePos = Gage[j].startFilePos; - - // --- assign units conversion factor - // (rain depths on interface file are in inches) - if ( UnitSystem == SI ) Gage[j].unitsFactor = MMperINCH; - } - - // --- get first & next rainfall values - if ( getFirstRainfall(j) ) - { - // --- find date at end of starting rain interval - Gage[j].endDate = datetime_addSeconds( - Gage[j].startDate, Gage[j].rainInterval); - - // --- if rainfall record begins after start of simulation, - if ( Gage[j].startDate > StartDateTime ) - { - // --- make next rainfall date the start of the rain record - Gage[j].nextDate = Gage[j].startDate; - Gage[j].nextRainfall = Gage[j].rainfall; - - // --- make start of current rain interval the simulation start - Gage[j].startDate = StartDateTime; - Gage[j].endDate = Gage[j].nextDate; - Gage[j].rainfall = 0.0; - } - - // --- otherwise find next recorded rainfall - else if ( !getNextRainfall(j) ) Gage[j].nextDate = NO_DATE; - } - else Gage[j].startDate = NO_DATE; - initPastRain(j); -} - -//============================================================================= - -void gage_setState(int j, DateTime t) -// -// Input: j = rain gage index -// t = a calendar date/time -// Output: none -// Purpose: updates state of rain gage for specified date. -// -{ - // --- return if gage not used by any subcatchment - if ( Gage[j].isUsed == FALSE ) return; - - // --- set rainfall to zero if disabled - if ( IgnoreRainfall ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- use rainfall from co-gage (gage with lower index that uses - // same rainfall time series or file) if it exists - if ( Gage[j].coGage >= 0) - { - Gage[j].rainfall = Gage[Gage[j].coGage].rainfall; - return; - } - - // --- use rainfall supplied by API function call - // (where constant ZERO (1.e-10) is used for 0 rainfall) - if (Gage[j].apiRainfall != MISSING) - { - Gage[j].rainfall = Gage[j].apiRainfall; - return; - } - - // --- otherwise march through rainfall record until date t is bracketed - t += OneSecond; - for (;;) - { - // --- no rainfall if no interval start date - if ( Gage[j].startDate == NO_DATE ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- no rainfall if time is before interval start date - if ( t < Gage[j].startDate ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- use current rainfall if time is before interval end date - if ( t < Gage[j].endDate ) - { - return; - } - - // --- no rainfall if t >= interval end date & no next interval exists - if ( Gage[j].nextDate == NO_DATE ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- no rainfall if t > interval end date & < next interval date - if ( t < Gage[j].nextDate ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- otherwise update next rainfall interval date - Gage[j].startDate = Gage[j].nextDate; - Gage[j].endDate = datetime_addSeconds(Gage[j].startDate, - Gage[j].rainInterval); - Gage[j].rainfall = Gage[j].nextRainfall; - if ( !getNextRainfall(j) ) Gage[j].nextDate = NO_DATE; - } -} - -//============================================================================= - -void initPastRain(int j) -{ - // --- initialize past hourly rain accumulation - int i; - for (i = 0; i <= MAXPASTRAIN; i++) - Gage[j].pastRain[i] = 0.0; - Gage[j].pastInterval = 0; -} - -//============================================================================= - -void gage_updatePastRain(int j, int tStep) -// -// Input: j = rain gage index -// tStep = current runoff time step (sec) -// Output: none -// Purpose: updates past MAXPASTRAIN hourly rain totals. -// -// Note: pastRain[0] is past rain volume prior to 1 hour, -// pastRain[n] is past rain volume after n hours, -// pastInterval is time since last hour was reached. -{ - int i, t; - double r; - - // --- current rainfall intensity (in/sec or mm/sec) - r = Gage[j].rainfall / 3600.; - - // --- process each hourly interval of current time step - while (tStep > 0) - { - // --- time for most recent rainfall interval to reach 1 hr - t = 3600 - Gage[j].pastInterval; - - // --- remaining time step is greater than this time - if (tStep > t) - { - // --- add current rain to most recent interval - Gage[j].pastRain[0] += t * r; - - // --- shift all prior hourly rain amounts by 1 hour - for (i = MAXPASTRAIN; i > 0; i-- ) - Gage[j].pastRain[i] = Gage[j].pastRain[i-1]; - - // --- begin a new most recent interval - Gage[j].pastInterval = 0; - Gage[j].pastRain[0] = 0.0; - tStep -= t; - } - // --- time to reach 1 hr in most recent interval is greater - // than remaining time step so update most recent interval - else - { - Gage[j].pastRain[0] += tStep * r; - Gage[j].pastInterval += tStep; - tStep = 0; - } - } -} - -//============================================================================= - -double gage_getPastRain(int j, int n) -// -// Input: j = rain gage index -// n = number of hours prior to current date -// Output: cumulative rain volume (inches or mm) in last n hours -// Purpose: retrieves rainfall total over some previous number of hours. -// -{ - int i; - double result = 0.0; - if (n < 1 || n > MAXPASTRAIN) return 0.0; - for (i = 1; i <= n; i++) - result += Gage[j].pastRain[i]; - return result; -} - -//============================================================================= - -DateTime gage_getNextRainDate(int j, DateTime aDate) -// -// Input: j = rain gage index -// aDate = calendar date/time -// Output: next date with rainfall occurring -// Purpose: finds the next date from specified date when rainfall occurs. -// -{ - if ( Gage[j].isUsed == FALSE ) return aDate; - aDate += OneSecond; - if ( aDate < Gage[j].startDate ) return Gage[j].startDate; - if ( aDate < Gage[j].endDate ) return Gage[j].endDate; - return Gage[j].nextDate; -} - -//============================================================================= - -double gage_getPrecip(int j, double *rainfall, double *snowfall) -// -// Input: j = rain gage index -// Output: rainfall = rainfall rate (ft/sec) -// snowfall = snow fall rate (ft/sec) -// returns total precipitation (ft/sec) -// Purpose: determines whether gage's recorded rainfall is rain or snow. -// -{ - *rainfall = 0.0; - *snowfall = 0.0; - if ( !IgnoreSnowmelt && Temp.ta <= Snow.snotmp ) - { - *snowfall = Gage[j].rainfall * Gage[j].snowFactor / UCF(RAINFALL); - } - else *rainfall = Gage[j].rainfall / UCF(RAINFALL); - return (*rainfall) + (*snowfall); -} - -//============================================================================= - -void gage_setReportRainfall(int j, DateTime reportDate) -// -// Input: j = rain gage index -// reportDate = date/time value of current reporting time -// Output: none -// Purpose: sets the rainfall value reported at the current reporting time. -// -{ - double result; - - // --- use value from co-gage if it exists - if ( Gage[j].coGage >= 0) - { - Gage[j].reportRainfall = Gage[Gage[j].coGage].reportRainfall; - return; - } - - // --- rainfall set by API call - if (Gage[j].apiRainfall != MISSING) - { - Gage[j].reportRainfall = Gage[j].apiRainfall; - return; - } - - // --- otherwise increase reporting time by 1 second to avoid - // roundoff problems - reportDate += OneSecond; - - // --- use current rainfall if report date/time is before end - // of current rain interval - if ( reportDate < Gage[j].endDate ) result = Gage[j].rainfall; - - // --- use 0.0 if report date/time is before start of next rain interval - else if ( reportDate < Gage[j].nextDate ) result = 0.0; - - // --- otherwise report date/time falls right on end of current rain - // interval and start of next interval so use next interval's rainfall - else result = Gage[j].nextRainfall; - Gage[j].reportRainfall = result; -} - -//============================================================================= - -int getFirstRainfall(int j) -// -// Input: j = rain gage index -// Output: returns TRUE if successful -// Purpose: positions rainfall record to date with first rainfall. -// -{ - int k; // time series index - float vFirst; // first rain volume (ft or m) - double rFirst; // first rain intensity (in/hr or mm/hr) - - // --- assign default values to date & rainfall - Gage[j].startDate = NO_DATE; - Gage[j].rainfall = 0.0; - - // --- initialize internal cumulative rainfall value - Gage[j].rainAccum = 0; - - // --- use rain interface file if applicable - if ( Gage[j].dataSource == RAIN_FILE ) - { - if ( Frain.file && Gage[j].endFilePos > Gage[j].startFilePos ) - { - // --- retrieve 1st date & rainfall volume from file - fseek(Frain.file, Gage[j].startFilePos, SEEK_SET); - fread(&Gage[j].startDate, sizeof(DateTime), 1, Frain.file); - fread(&vFirst, sizeof(float), 1, Frain.file); - Gage[j].currentFilePos = ftell(Frain.file); - - // --- convert rainfall to intensity - Gage[j].rainfall = convertRainfall(j, (double)vFirst); - return 1; - } - return 0; - } - - // --- otherwise access user-supplied rainfall time series - else - { - k = Gage[j].tSeries; - if ( k >= 0 ) - { - // --- retrieve first rainfall value from time series - if ( table_getFirstEntry(&Tseries[k], &Gage[j].startDate, - &rFirst) ) - { - // --- convert rainfall to intensity - Gage[j].rainfall = convertRainfall(j, rFirst); - return 1; - } - } - return 0; - } -} - -//============================================================================= - -int getNextRainfall(int j) -// -// Input: j = rain gage index -// Output: returns 1 if successful; 0 if not -// Purpose: positions rainfall record to date with next non-zero rainfall -// while updating the gage's next rain intensity value. -// -// Note: zero rainfall values explicitly entered into a rain file or -// time series are skipped over so that a proper accounting of -// wet and dry periods can be maintained. -// -{ - int k; // time series index - float vNext; // next rain volume (ft or m) - double rNext; // next rain intensity (in/hr or mm/hr) - - Gage[j].nextRainfall = 0.0; - do - { - if ( Gage[j].dataSource == RAIN_FILE ) - { - if ( Frain.file && Gage[j].currentFilePos < Gage[j].endFilePos ) - { - fseek(Frain.file, Gage[j].currentFilePos, SEEK_SET); - fread(&Gage[j].nextDate, sizeof(DateTime), 1, Frain.file); - fread(&vNext, sizeof(float), 1, Frain.file); - Gage[j].currentFilePos = ftell(Frain.file); - rNext = convertRainfall(j, (double)vNext); - } - else return 0; - } - - else - { - k = Gage[j].tSeries; - if ( k >= 0 ) - { - if ( !table_getNextEntry(&Tseries[k], - &Gage[j].nextDate, &rNext) ) return 0; - rNext = convertRainfall(j, rNext); - } - else return 0; - } - } while (rNext == 0.0); - Gage[j].nextRainfall = rNext; - return 1; -} - -//============================================================================= - -double convertRainfall(int j, double r) -// -// Input: j = rain gage index -// r = rainfall value (user units) -// Output: returns rainfall intensity (user units) -// Purpose: converts rainfall value to an intensity (depth per hour). -// -{ - double r1; - switch ( Gage[j].rainType ) - { - case RAINFALL_INTENSITY: - r1 = r; - break; - - case RAINFALL_VOLUME: - r1 = r / Gage[j].rainInterval * 3600.0; - break; - - case CUMULATIVE_RAINFALL: - if ( r < Gage[j].rainAccum ) - r1 = r / Gage[j].rainInterval * 3600.0; - else r1 = (r - Gage[j].rainAccum) / Gage[j].rainInterval * 3600.0; - Gage[j].rainAccum = r; - break; - - default: r1 = r; - } - return r1 * Gage[j].unitsFactor * Adjust.rainFactor; -} - -//============================================================================= diff --git a/src/globals.h b/src/globals.h deleted file mode 100644 index 70f2431aa..000000000 --- a/src/globals.h +++ /dev/null @@ -1,172 +0,0 @@ -//----------------------------------------------------------------------------- -// globals.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Global Variables -// -// Update History -// ============== -// Build 5.1.004: -// - Ignore RDII option added. -// Build 5.1.007: -// - Monthly climate variable adjustments added. -// Build 5.1.008: -// - Number of parallel threads for dynamic wave routing added. -// - Minimum dynamic wave routing variable time step added. -// Build 5.1.011: -// - Changed WarningCode to Warnings (# warnings issued) -// - Added error message text as a variable. -// - Added elapsed simulation time (in decimal days) variable. -// - Added variables associated with detailed routing events. -// Build 5.1.012: -// - InSteadyState variable made local to routing_execute in routing.c. -// Build 5.1.013: -// - CrownCutoff and RuleStep added as analysis option variables. -// Build 5.1.015: -// - Fixes bug in summary statistics when Report Start date > Start Date. -// Build 5.2.0: -// - Support for relative file names added. -//----------------------------------------------------------------------------- - -#ifndef GLOBALS_H -#define GLOBALS_H - - -EXTERN TFile - Finp, // Input file - Fout, // Output file - Frpt, // Report file - Fclimate, // Climate file - Frain, // Rainfall file - Frunoff, // Runoff file - Frdii, // RDII inflow file - Fhotstart1, // Hot start input file - Fhotstart2, // Hot start output file - Finflows, // Inflows routing file - Foutflows; // Outflows routing file - -EXTERN long - Nperiods, // Number of reporting periods - TotalStepCount, // Total routing steps used - ReportStepCount, // Reporting routing steps used - NonConvergeCount; // Number of non-converging steps - -EXTERN char - Msg[MAXMSG+1], // Text of output message - ErrorMsg[MAXMSG+1], // Text of error message - Title[MAXTITLE][MAXMSG+1],// Project title - TempDir[MAXFNAME+1], // Temporary file directory - InpDir[MAXFNAME+1]; // Input file directory - -EXTERN TRptFlags - RptFlags; // Reporting options - -EXTERN int - Nobjects[MAX_OBJ_TYPES], // Number of each object type - Nnodes[MAX_NODE_TYPES], // Number of each node sub-type - Nlinks[MAX_LINK_TYPES], // Number of each link sub-type - UnitSystem, // Unit system - FlowUnits, // Flow units - InfilModel, // Infiltration method - RouteModel, // Flow routing method - ForceMainEqn, // Flow equation for force mains - LinkOffsets, // Link offset convention - SurchargeMethod, // EXTRAN or SLOT method - AllowPonding, // Allow water to pond at nodes - InertDamping, // Degree of inertial damping - NormalFlowLtd, // Normal flow limited - SlopeWeighting, // Use slope weighting - Compatibility, // SWMM 5/3/4 compatibility - SkipSteadyState, // Skip over steady state periods - IgnoreRainfall, // Ignore rainfall/runoff - IgnoreRDII, // Ignore RDII - IgnoreSnowmelt, // Ignore snowmelt - IgnoreGwater, // Ignore groundwater - IgnoreRouting, // Ignore flow routing - IgnoreQuality, // Ignore water quality - ErrorCode, // Error code number - Warnings, // Number of warning messages - WetStep, // Runoff wet time step (sec) - DryStep, // Runoff dry time step (sec) - ReportStep, // Reporting time step (sec) - RuleStep, // Rule evaluation time step (sec) - SweepStart, // Day of year when sweeping starts - SweepEnd, // Day of year when sweeping ends - MaxTrials, // Max. trials for DW routing - NumThreads, // Number of parallel threads used - NumEvents; // Number of detailed events - -EXTERN double - RouteStep, // Routing time step (sec) - MinRouteStep, // Minimum variable time step (sec) - LengtheningStep, // Time step for lengthening (sec) - StartDryDays, // Antecedent dry days - CourantFactor, // Courant time step factor - MinSurfArea, // Minimum nodal surface area - MinSlope, // Minimum conduit slope - RunoffError, // Runoff continuity error - GwaterError, // Groundwater continuity error - FlowError, // Flow routing error - QualError, // Quality routing error - HeadTol, // DW routing head tolerance (ft) - SysFlowTol, // Tolerance for steady system flow - LatFlowTol, // Tolerance for steady nodal inflow - CrownCutoff; // Fractional pipe crown cutoff - -EXTERN DateTime - StartDate, // Starting date - StartTime, // Starting time - StartDateTime, // Starting Date+Time - EndDate, // Ending date - EndTime, // Ending time - EndDateTime, // Ending Date+Time - ReportStartDate, // Report start date - ReportStartTime, // Report start time - ReportStart; // Report start Date+Time - -EXTERN double - ReportTime, // Current reporting time (msec) - OldRunoffTime, // Previous runoff time (msec) - NewRunoffTime, // Current runoff time (msec) - OldRoutingTime, // Previous routing time (msec) - NewRoutingTime, // Current routing time (msec) - TotalDuration, // Simulation duration (msec) - ElapsedTime; // Current elapsed time (days) - -EXTERN TTemp Temp; // Temperature data -EXTERN TEvap Evap; // Evaporation data -EXTERN TWind Wind; // Wind speed data -EXTERN TSnow Snow; // Snow melt data -EXTERN TAdjust Adjust; // Climate adjustments - -EXTERN TSnowmelt* Snowmelt; // Array of snow melt objects -EXTERN TGage* Gage; // Array of rain gages -EXTERN TSubcatch* Subcatch; // Array of subcatchments -EXTERN TAquifer* Aquifer; // Array of groundwater aquifers -EXTERN TUnitHyd* UnitHyd; // Array of unit hydrographs -EXTERN TNode* Node; // Array of nodes -EXTERN TOutfall* Outfall; // Array of outfall nodes -EXTERN TDivider* Divider; // Array of divider nodes -EXTERN TStorage* Storage; // Array of storage nodes -EXTERN TLink* Link; // Array of links -EXTERN TConduit* Conduit; // Array of conduit links -EXTERN TPump* Pump; // Array of pump links -EXTERN TOrifice* Orifice; // Array of orifice links -EXTERN TWeir* Weir; // Array of weir links -EXTERN TOutlet* Outlet; // Array of outlet device links -EXTERN TPollut* Pollut; // Array of pollutants -EXTERN TLanduse* Landuse; // Array of landuses -EXTERN TPattern* Pattern; // Array of time patterns -EXTERN TTable* Curve; // Array of curve tables -EXTERN TTable* Tseries; // Array of time series tables -EXTERN TTransect* Transect; // Array of transect data -EXTERN TStreet* Street; // Array of defined Street cross-sections -EXTERN TShape* Shape; // Array of custom conduit shapes -EXTERN TEvent* Event; // Array of routing events - - -#endif //GLOBALS_H diff --git a/src/gwater.c b/src/gwater.c deleted file mode 100644 index 48db9de17..000000000 --- a/src/gwater.c +++ /dev/null @@ -1,872 +0,0 @@ -//----------------------------------------------------------------------------- -// gwater.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Groundwater functions. -// -// Update History -// ============== -// Build 5.1.007: -// - User-supplied function for deep GW seepage flow added. -// - New variable names for use in user-supplied GW flow equations added. -// Build 5.1.008: -// - More variable names for user-supplied GW flow equations added. -// - Subcatchment area made into a shared variable. -// - Evaporation loss initialized to 0. -// - Support for collecting GW statistics added. -// Build 5.1.010: -// - Unsaturated hydraulic conductivity added to GW flow equation variables. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" -#include "odesolve.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double GWTOL = 0.0001; // ODE solver tolerance -static const double XTOL = 0.001; // tolerance for moisture & depth - -enum GWstates {THETA, // moisture content of upper GW zone - LOWERDEPTH}; // depth of lower saturated GW zone - -enum GWvariables { - gwvHGW, // water table height (ft) - gwvHSW, // surface water height (ft) - gwvHCB, // channel bottom height (ft) - gwvHGS, // ground surface height (ft) - gwvKS, // sat. hyd. condutivity (ft/s) - gwvK, // unsat. hyd. conductivity (ft/s) - gwvTHETA, // upper zone moisture content - gwvPHI, // soil porosity - gwvFI, // surface infiltration (ft/s) - gwvFU, // uper zone percolation rate (ft/s) - gwvA, // subcatchment area (ft2) - gwvMAX}; - -// Names of GW variables that can be used in GW outflow expression -static char* GWVarWords[] = {"HGW", "HSW", "HCB", "HGS", "KS", "K", - "THETA", "PHI", "FI", "FU", "A", NULL}; - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -// NOTE: all flux rates are in ft/sec, all depths are in ft. -static double Area; // subcatchment area (ft2) -static double Infil; // infiltration rate from surface -static double MaxEvap; // max. evaporation rate -static double AvailEvap; // available evaporation rate -static double UpperEvap; // evaporation rate from upper GW zone -static double LowerEvap; // evaporation rate from lower GW zone -static double UpperPerc; // percolation rate from upper to lower zone -static double LowerLoss; // loss rate from lower GW zone -static double GWFlow; // flow rate from lower zone to conveyance node -static double MaxUpperPerc; // upper limit on UpperPerc -static double MaxGWFlowPos; // upper limit on GWFlow when its positve -static double MaxGWFlowNeg; // upper limit on GWFlow when its negative -static double FracPerv; // fraction of surface that is pervious -static double TotalDepth; // total depth of GW aquifer -static double Theta; // moisture content of upper zone -static double HydCon; // unsaturated hydraulic conductivity (ft/s) -static double Hgw; // ht. of saturated zone -static double Hstar; // ht. from aquifer bottom to node invert -static double Hsw; // ht. from aquifer bottom to water surface -static double Tstep; // current time step (sec) -static TAquifer A; // aquifer being analyzed -static TGroundwater* GW; // groundwater object being analyzed -static MathExpr* LatFlowExpr; // user-supplied lateral GW flow expression -static MathExpr* DeepFlowExpr; // user-supplied deep GW flow expression - -//----------------------------------------------------------------------------- -// External Functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// gwater_readAquiferParams (called by input_readLine) -// gwater_readGroundwaterParams (called by input_readLine) -// gwater_readFlowExpression (called by input_readLine) -// gwater_deleteFlowExpression (called by deleteObjects in project.c) -// gwater_validateAquifer (called by swmm_open) -// gwater_validate (called by subcatch_validate) -// gwater_initState (called by subcatch_initState) -// gwater_getVolume (called by massbal_open & massbal_getGwaterError) -// gwater_getGroundwater (called by getSubareaRunoff in subcatch.c) -// gwater_getState (called by saveRunoff in hotstart.c) -// gwater_setState (called by readRunoff in hotstart.c) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static void getDxDt(double t, double* x, double* dxdt); -static void getFluxes(double upperVolume, double lowerDepth); -static void getEvapRates(double theta, double upperDepth); -static double getUpperPerc(double theta, double upperDepth); -static double getGWFlow(double lowerDepth); -static void updateMassBal(double area, double tStep); - -// Used to process custom GW outflow equations -static int getVariableIndex(char* s); -static double getVariableValue(int varIndex); - -//============================================================================= - -int gwater_readAquiferParams(int j, char* tok[], int ntoks) -// -// Input: j = aquifer index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns error message -// Purpose: reads aquifer parameter values from line of input data -// -// Data line contains following parameters: -// ID, porosity, wiltingPoint, fieldCapacity, conductivity, -// conductSlope, tensionSlope, upperEvapFraction, lowerEvapDepth, -// gwRecession, bottomElev, waterTableElev, upperMoisture -// (evapPattern) -// -{ - int i, p; - double x[12]; - char *id; - - // --- check that aquifer exists - if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(AQUIFER, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - - // --- read remaining tokens as numbers - for (i = 0; i < 11; i++) x[i] = 0.0; - for (i = 1; i < 13; i++) - { - if ( ! getDouble(tok[i], &x[i-1]) ) - return error_setInpError(ERR_NUMBER, tok[i]); - } - - // --- read upper evap pattern if present - p = -1; - if ( ntoks > 13 ) - { - p = project_findObject(TIMEPATTERN, tok[13]); - if ( p < 0 ) return error_setInpError(ERR_NAME, tok[13]); - } - - // --- assign parameters to aquifer object - Aquifer[j].ID = id; - Aquifer[j].porosity = x[0]; - Aquifer[j].wiltingPoint = x[1]; - Aquifer[j].fieldCapacity = x[2]; - Aquifer[j].conductivity = x[3] / UCF(RAINFALL); - Aquifer[j].conductSlope = x[4]; - Aquifer[j].tensionSlope = x[5] / UCF(LENGTH); - Aquifer[j].upperEvapFrac = x[6]; - Aquifer[j].lowerEvapDepth = x[7] / UCF(LENGTH); - Aquifer[j].lowerLossCoeff = x[8] / UCF(RAINFALL); - Aquifer[j].bottomElev = x[9] / UCF(LENGTH); - Aquifer[j].waterTableElev = x[10] / UCF(LENGTH); - Aquifer[j].upperMoisture = x[11]; - Aquifer[j].upperEvapPat = p; - return 0; -} - -//============================================================================= - -int gwater_readGroundwaterParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// Purpose: reads groundwater inflow parameters for a subcatchment from -// a line of input data. -// -// Data format is: -// subcatch aquifer node surfElev a1 b1 a2 b2 a3 fixedDepth + -// (nodeElev bottomElev waterTableElev upperMoisture ) -// -{ - int i, j, k, m, n; - double x[11]; - TGroundwater* gw; - - // --- check that specified subcatchment, aquifer & node exist - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - j = project_findObject(SUBCATCH, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- check for enough tokens - if ( ntoks < 11 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that specified aquifer and node exists - k = project_findObject(AQUIFER, tok[1]); - if ( k < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n = project_findObject(NODE, tok[2]); - if ( n < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // -- read in the flow parameters - for ( i = 0; i < 7; i++ ) - { - if ( ! getDouble(tok[i+3], &x[i]) ) - return error_setInpError(ERR_NUMBER, tok[i+3]); - } - - // --- read in optional depth parameters - for ( i = 7; i < 11; i++) - { - x[i] = MISSING; - m = i + 3; - if ( ntoks > m && *tok[m] != '*' ) - { - if (! getDouble(tok[m], &x[i]) ) - return error_setInpError(ERR_NUMBER, tok[m]); - if ( i < 10 ) x[i] /= UCF(LENGTH); - } - } - - // --- create a groundwater flow object - if ( !Subcatch[j].groundwater ) - { - gw = (TGroundwater *) malloc(sizeof(TGroundwater)); - if ( !gw ) return error_setInpError(ERR_MEMORY, ""); - Subcatch[j].groundwater = gw; - } - else gw = Subcatch[j].groundwater; - - // --- populate the groundwater flow object with its parameters - gw->aquifer = k; - gw->node = n; - gw->surfElev = x[0] / UCF(LENGTH); - gw->a1 = x[1]; - gw->b1 = x[2]; - gw->a2 = x[3]; - gw->b2 = x[4]; - gw->a3 = x[5]; - gw->fixedDepth = x[6] / UCF(LENGTH); - gw->nodeElev = x[7]; //already converted to ft. - gw->bottomElev = x[8]; - gw->waterTableElev = x[9]; - gw->upperMoisture = x[10]; - return 0; -} - -//============================================================================= - -int gwater_readFlowExpression(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// Purpose: reads mathematical expression for lateral or deep groundwater -// flow for a subcatchment from a line of input data. -// -// Format is: subcatch LATERAL/DEEP -// where subcatch is the ID of the subcatchment, LATERAL is for lateral -// GW flow, DEEP is for deep GW flow and is any well-formed math -// expression. -// -{ - int i, j, k; - char exprStr[MAXLINE+1]; - MathExpr* expr; - - // --- return if too few tokens - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that subcatchment exists - j = project_findObject(SUBCATCH, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- check if expression is for lateral or deep GW flow - k = 1; - if ( match(tok[1], "LAT") ) k = 1; - else if ( match(tok[1], "DEEP") ) k = 2; - else return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- concatenate remaining tokens into a single string - sstrncpy(exprStr, tok[2], MAXLINE); - for ( i = 3; i < ntoks; i++) - { - sstrcat(exprStr, " ", MAXLINE+1); - sstrcat(exprStr, tok[i], MAXLINE+1); - } - - // --- delete any previous flow eqn. - if ( k == 1 ) mathexpr_delete(Subcatch[j].gwLatFlowExpr); - else mathexpr_delete(Subcatch[j].gwDeepFlowExpr); - - // --- create a parsed expression tree from the string expr - // (getVariableIndex is the function that converts a GW - // variable's name into an index number) - expr = mathexpr_create(exprStr, getVariableIndex); - if ( expr == NULL ) return error_setInpError(ERR_MATH_EXPR, ""); - - // --- save expression tree with the subcatchment - if ( k == 1 ) Subcatch[j].gwLatFlowExpr = expr; - else Subcatch[j].gwDeepFlowExpr = expr; - return 0; -} - -//============================================================================= - -void gwater_deleteFlowExpression(int j) -// -// Input: j = subcatchment index -// Output: none -// Purpose: deletes a subcatchment's custom groundwater flow expressions. -// -{ - mathexpr_delete(Subcatch[j].gwLatFlowExpr); - mathexpr_delete(Subcatch[j].gwDeepFlowExpr); -} - -//============================================================================= - -void gwater_validateAquifer(int j) -// -// Input: j = aquifer index -// Output: none -// Purpose: validates groundwater aquifer properties . -// -{ - int p; - - if ( Aquifer[j].porosity <= 0.0 - || Aquifer[j].fieldCapacity >= Aquifer[j].porosity - || Aquifer[j].wiltingPoint >= Aquifer[j].fieldCapacity - || Aquifer[j].conductivity <= 0.0 - || Aquifer[j].conductSlope < 0.0 - || Aquifer[j].tensionSlope < 0.0 - || Aquifer[j].upperEvapFrac < 0.0 - || Aquifer[j].lowerEvapDepth < 0.0 - || Aquifer[j].waterTableElev < Aquifer[j].bottomElev - || Aquifer[j].upperMoisture > Aquifer[j].porosity - || Aquifer[j].upperMoisture < Aquifer[j].wiltingPoint ) - report_writeErrorMsg(ERR_AQUIFER_PARAMS, Aquifer[j].ID); - - p = Aquifer[j].upperEvapPat; - if ( p >= 0 && Pattern[p].type != MONTHLY_PATTERN ) - { - report_writeErrorMsg(ERR_AQUIFER_PARAMS, Aquifer[j].ID); - } -} - -//============================================================================= - -void gwater_validate(int j) -{ - TAquifer a; // Aquifer data structure - TGroundwater* gw; // Groundwater data structure - - gw = Subcatch[j].groundwater; - if ( gw ) - { - a = Aquifer[gw->aquifer]; - - // ... use aquifer values for missing groundwater parameters - if ( gw->bottomElev == MISSING ) gw->bottomElev = a.bottomElev; - if ( gw->waterTableElev == MISSING ) gw->waterTableElev = a.waterTableElev; - if ( gw->upperMoisture == MISSING ) gw->upperMoisture = a.upperMoisture; - - // ... ground elevation can't be below water table elevation - if ( gw->surfElev < gw->waterTableElev ) - report_writeErrorMsg(ERR_GROUND_ELEV, Subcatch[j].ID); - } -} - -//============================================================================= - -void gwater_initState(int j) -// -// Input: j = subcatchment index -// Output: none -// Purpose: initializes state of subcatchment's groundwater. -// -{ - TAquifer a; // Aquifer data structure - TGroundwater* gw; // Groundwater data structure - - gw = Subcatch[j].groundwater; - if ( gw ) - { - a = Aquifer[gw->aquifer]; - - // ... initial moisture content - gw->theta = gw->upperMoisture; - if ( gw->theta >= a.porosity ) - { - gw->theta = a.porosity - XTOL; - } - - // ... initial depth of lower (saturated) zone - gw->lowerDepth = gw->waterTableElev - gw->bottomElev; - if ( gw->lowerDepth >= gw->surfElev - gw->bottomElev ) - { - gw->lowerDepth = gw->surfElev - gw->bottomElev - XTOL; - } - - // ... initial lateral groundwater outflow - gw->oldFlow = 0.0; - gw->newFlow = 0.0; - gw->evapLoss = 0.0; - - // ... initial available infiltration volume into upper zone - gw->maxInfilVol = (gw->surfElev - gw->waterTableElev) * - (a.porosity - gw->theta) / - subcatch_getFracPerv(j); - } -} - -//============================================================================= - -void gwater_getState(int j, double x[]) -// -// Input: j = subcatchment index -// Output: x[] = array of groundwater state variables -// Purpose: retrieves state of subcatchment's groundwater. -// -{ - TGroundwater* gw = Subcatch[j].groundwater; - x[0] = gw->theta; - x[1] = gw->bottomElev + gw->lowerDepth; - x[2] = gw->newFlow; - x[3] = gw->maxInfilVol; -} - -//============================================================================= - -void gwater_setState(int j, double x[]) -// -// Input: j = subcatchment index -// x[] = array of groundwater state variables -// Purpose: assigns values to a subcatchment's groundwater state. -// -{ - TGroundwater* gw = Subcatch[j].groundwater; - if ( gw == NULL ) return; - gw->theta = x[0]; - gw->lowerDepth = x[1] - gw->bottomElev; - gw->oldFlow = x[2]; - if ( x[3] != MISSING ) gw->maxInfilVol = x[3]; -} - -//============================================================================= - -double gwater_getVolume(int j) -// -// Input: j = subcatchment index -// Output: returns total volume of groundwater in ft/ft2 -// Purpose: finds volume of groundwater stored in upper & lower zones -// -{ - TAquifer a; - TGroundwater* gw; - double upperDepth; - gw = Subcatch[j].groundwater; - if ( gw == NULL ) return 0.0; - a = Aquifer[gw->aquifer]; - upperDepth = gw->surfElev - gw->bottomElev - gw->lowerDepth; - return (upperDepth * gw->theta) + (gw->lowerDepth * a.porosity); -} - -//============================================================================= - -void gwater_getGroundwater(int j, double evap, double infil, double tStep) -// -// Purpose: computes groundwater flow from subcatchment during current time step. -// Input: j = subcatchment index -// evap = pervious surface evaporation volume consumed (ft3) -// infil = surface infiltration volume (ft3) -// tStep = time step (sec) -// Output: none -// -{ - int n; // node exchanging groundwater - double x[2]; // upper moisture content & lower depth - double vUpper; // upper vol. available for percolation - double nodeFlow; // max. possible GW flow from node - - // --- save subcatchment's groundwater and aquifer objects to - // shared variables - GW = Subcatch[j].groundwater; - if ( GW == NULL ) return; - LatFlowExpr = Subcatch[j].gwLatFlowExpr; - DeepFlowExpr = Subcatch[j].gwDeepFlowExpr; - A = Aquifer[GW->aquifer]; - - // --- get fraction of total area that is pervious - FracPerv = subcatch_getFracPerv(j); - if ( FracPerv <= 0.0 ) return; - Area = Subcatch[j].area; - - // --- convert infiltration volume (ft3) to equivalent rate - // over entire GW (subcatchment) area - infil = infil / Area / tStep; - Infil = infil; - Tstep = tStep; - - // --- convert pervious surface evaporation already exerted (ft3) - // to equivalent rate over entire GW (subcatchment) area - evap = evap / Area / tStep; - - // --- convert max. surface evap rate (ft/sec) to a rate - // that applies to GW evap (GW evap can only occur - // through the pervious land surface area) - MaxEvap = Evap.rate * FracPerv; - - // --- available subsurface evaporation is difference between max. - // rate and pervious surface evap already exerted - AvailEvap = MAX((MaxEvap - evap), 0.0); - - // --- save total depth & outlet node properties to shared variables - TotalDepth = GW->surfElev - GW->bottomElev; - if ( TotalDepth <= 0.0 ) return; - n = GW->node; - - // --- establish min. water table height above aquifer bottom at which - // GW flow can occur (override node's invert if a value was provided - // in the GW object) - if ( GW->nodeElev != MISSING ) Hstar = GW->nodeElev - GW->bottomElev; - else Hstar = Node[n].invertElev - GW->bottomElev; - - // --- establish surface water height (relative to aquifer bottom) - // for drainage system node connected to the GW aquifer - if ( GW->fixedDepth > 0.0 ) - { - Hsw = GW->fixedDepth + Node[n].invertElev - GW->bottomElev; - } - else Hsw = Node[n].newDepth + Node[n].invertElev - GW->bottomElev; - - // --- store state variables (upper zone moisture content, lower zone - // depth) in work vector x - x[THETA] = GW->theta; - x[LOWERDEPTH] = GW->lowerDepth; - - // --- set limit on percolation rate from upper to lower GW zone - vUpper = (TotalDepth - x[LOWERDEPTH]) * (x[THETA] - A.fieldCapacity); - vUpper = MAX(0.0, vUpper); - MaxUpperPerc = vUpper / tStep; - - // --- set limit on GW flow out of aquifer based on volume of lower zone - MaxGWFlowPos = x[LOWERDEPTH]*A.porosity / tStep; - - // --- set limit on GW flow into aquifer from drainage system node - // based on min. of capacity of upper zone and drainage system - // inflow to the node - MaxGWFlowNeg = (TotalDepth - x[LOWERDEPTH]) * (A.porosity - x[THETA]) - / tStep; - nodeFlow = (Node[n].inflow + Node[n].newVolume/tStep) / Area; - MaxGWFlowNeg = -MIN(MaxGWFlowNeg, nodeFlow); - - // --- integrate eqns. for d(Theta)/dt and d(LowerDepth)/dt - // NOTE: ODE solver must have been initialized previously - odesolve_integrate(x, 2, 0, tStep, GWTOL, tStep, getDxDt); - - // --- keep state variables within allowable bounds - x[THETA] = MAX(x[THETA], A.wiltingPoint); - if ( x[THETA] >= A.porosity ) - { - x[THETA] = A.porosity - XTOL; - x[LOWERDEPTH] = TotalDepth - XTOL; - } - x[LOWERDEPTH] = MAX(x[LOWERDEPTH], 0.0); - if ( x[LOWERDEPTH] >= TotalDepth ) - { - x[LOWERDEPTH] = TotalDepth - XTOL; - } - - // --- save new values of state values - GW->theta = x[THETA]; - GW->lowerDepth = x[LOWERDEPTH]; - getFluxes(GW->theta, GW->lowerDepth); - GW->oldFlow = GW->newFlow; - GW->newFlow = GWFlow; - GW->evapLoss = UpperEvap + LowerEvap; - - //--- find max. infiltration volume (as depth over - // the pervious portion of the subcatchment) - // that upper zone can support in next time step - GW->maxInfilVol = (TotalDepth - x[LOWERDEPTH]) * - (A.porosity - x[THETA]) / FracPerv; - - // --- update GW mass balance - updateMassBal(Area, tStep); - - // --- update GW statistics - stats_updateGwaterStats(j, infil, GW->evapLoss, GWFlow, LowerLoss, - GW->theta, GW->lowerDepth + GW->bottomElev, tStep); -} - -//============================================================================= - -void updateMassBal(double area, double tStep) -// -// Input: area = subcatchment area (ft2) -// tStep = time step (sec) -// Output: none -// Purpose: updates GW mass balance with volumes of water fluxes. -// -{ - double vInfil; // infiltration volume - double vUpperEvap; // upper zone evap. volume - double vLowerEvap; // lower zone evap. volume - double vLowerPerc; // lower zone deep perc. volume - double vGwater; // volume of exchanged groundwater - double ft2sec = area * tStep; - - vInfil = Infil * ft2sec; - vUpperEvap = UpperEvap * ft2sec; - vLowerEvap = LowerEvap * ft2sec; - vLowerPerc = LowerLoss * ft2sec; - vGwater = 0.5 * (GW->oldFlow + GW->newFlow) * ft2sec; - massbal_updateGwaterTotals(vInfil, vUpperEvap, vLowerEvap, vLowerPerc, - vGwater); -} - -//============================================================================= - -void getFluxes(double theta, double lowerDepth) -// -// Input: upperVolume = vol. depth of upper zone (ft) -// upperDepth = depth of upper zone (ft) -// Output: none -// Purpose: computes water fluxes into/out of upper/lower GW zones. -// -{ - double upperDepth; - - // --- find upper zone depth - lowerDepth = MAX(lowerDepth, 0.0); - lowerDepth = MIN(lowerDepth, TotalDepth); - upperDepth = TotalDepth - lowerDepth; - - // --- save lower depth and theta to global variables - Hgw = lowerDepth; - Theta = theta; - - // --- find evaporation rate from both zones - getEvapRates(theta, upperDepth); - - // --- find percolation rate from upper to lower zone - UpperPerc = getUpperPerc(theta, upperDepth); - UpperPerc = MIN(UpperPerc, MaxUpperPerc); - - // --- find loss rate to deep GW - if ( DeepFlowExpr != NULL ) - LowerLoss = mathexpr_eval(DeepFlowExpr, getVariableValue) / - UCF(RAINFALL); - else - LowerLoss = A.lowerLossCoeff * lowerDepth / TotalDepth; - LowerLoss = MIN(LowerLoss, lowerDepth/Tstep); - - // --- find GW flow rate from lower zone to drainage system node - GWFlow = getGWFlow(lowerDepth); - if ( LatFlowExpr != NULL ) - { - GWFlow += mathexpr_eval(LatFlowExpr, getVariableValue) / UCF(GWFLOW); - } - if ( GWFlow >= 0.0 ) GWFlow = MIN(GWFlow, MaxGWFlowPos); - else GWFlow = MAX(GWFlow, MaxGWFlowNeg); -} - -//============================================================================= - -void getDxDt(double t, double* x, double* dxdt) -// -// Input: t = current time (not used) -// x = array of state variables -// Output: dxdt = array of time derivatives of state variables -// Purpose: computes time derivatives of upper moisture content -// and lower depth. -// -{ - double qUpper; // inflow - outflow for upper zone (ft/sec) - double qLower; // inflow - outflow for lower zone (ft/sec) - double denom; - - getFluxes(x[THETA], x[LOWERDEPTH]); - qUpper = Infil - UpperEvap - UpperPerc; - qLower = UpperPerc - LowerLoss - LowerEvap - GWFlow; - - // --- d(upper zone moisture)/dt = (net upper zone flow) / - // (upper zone depth) - denom = TotalDepth - x[LOWERDEPTH]; - if (denom > 0.0) - dxdt[THETA] = qUpper / denom; - else - dxdt[THETA] = 0.0; - - // --- d(lower zone depth)/dt = (net lower zone flow) / - // (upper zone moisture deficit) - denom = A.porosity - x[THETA]; - if (denom > 0.0) - dxdt[LOWERDEPTH] = qLower / denom; - else - dxdt[LOWERDEPTH] = 0.0; -} - -//============================================================================= - -void getEvapRates(double theta, double upperDepth) -// -// Input: theta = moisture content of upper zone -// upperDepth = depth of upper zone (ft) -// Output: none -// Purpose: computes evapotranspiration out of upper & lower zones. -// -{ - int p, month; - double f; - double lowerFrac, upperFrac; - - // --- no GW evaporation when infiltration is occurring - UpperEvap = 0.0; - LowerEvap = 0.0; - if ( Infil > 0.0 ) return; - - // --- get monthly-adjusted upper zone evap fraction - upperFrac = A.upperEvapFrac; - f = 1.0; - p = A.upperEvapPat; - if ( p >= 0 ) - { - month = datetime_monthOfYear(getDateTime(NewRunoffTime)); - f = Pattern[p].factor[month-1]; - } - upperFrac *= f; - - // --- upper zone evaporation requires that soil moisture - // be above the wilting point - if ( theta > A.wiltingPoint ) - { - // --- actual evap is upper zone fraction applied to max. potential - // rate, limited by the available rate after any surface evap - UpperEvap = upperFrac * MaxEvap; - UpperEvap = MIN(UpperEvap, AvailEvap); - } - - // --- check if lower zone evaporation is possible - if ( A.lowerEvapDepth > 0.0 ) - { - // --- find the fraction of the lower evaporation depth that - // extends into the saturated lower zone - lowerFrac = (A.lowerEvapDepth - upperDepth) / A.lowerEvapDepth; - lowerFrac = MAX(0.0, lowerFrac); - lowerFrac = MIN(lowerFrac, 1.0); - - // --- make the lower zone evap rate proportional to this fraction - // and the evap not used in the upper zone - LowerEvap = lowerFrac * (1.0 - upperFrac) * MaxEvap; - LowerEvap = MIN(LowerEvap, (AvailEvap - UpperEvap)); - } -} - -//============================================================================= - -double getUpperPerc(double theta, double upperDepth) -// -// Input: theta = moisture content of upper zone -// upperDepth = depth of upper zone (ft) -// Output: returns percolation rate (ft/sec) -// Purpose: finds percolation rate from upper to lower zone. -// -{ - double delta; // unfilled water content of upper zone - double dhdz; // avg. change in head with depth - double hydcon; // unsaturated hydraulic conductivity - - // --- no perc. from upper zone if no depth or moisture content too low - if ( upperDepth <= 0.0 || theta <= A.fieldCapacity ) return 0.0; - - // --- compute hyd. conductivity as function of moisture content - delta = theta - A.porosity; - hydcon = A.conductivity * exp(delta * A.conductSlope); - - // --- compute integral of dh/dz term - delta = theta - A.fieldCapacity; - dhdz = 1.0 + A.tensionSlope * 2.0 * delta / upperDepth; - - // --- compute upper zone percolation rate - HydCon = hydcon; - return hydcon * dhdz; -} - -//============================================================================= - -double getGWFlow(double lowerDepth) -// -// Input: lowerDepth = depth of lower zone (ft) -// Output: returns groundwater flow rate (ft/sec) -// Purpose: finds groundwater outflow from lower saturated zone. -// -{ - double q, t1, t2, t3; - - // --- water table must be above Hstar for flow to occur - if ( lowerDepth <= Hstar ) return 0.0; - - // --- compute groundwater component of flow - if ( GW->b1 == 0.0 ) t1 = GW->a1; - else t1 = GW->a1 * pow( (lowerDepth - Hstar)*UCF(LENGTH), GW->b1); - - // --- compute surface water component of flow - if ( GW->b2 == 0.0 ) t2 = GW->a2; - else if (Hsw > Hstar) - { - t2 = GW->a2 * pow( (Hsw - Hstar)*UCF(LENGTH), GW->b2); - } - else t2 = 0.0; - - // --- compute groundwater/surface water interaction term - t3 = GW->a3 * lowerDepth * Hsw * UCF(LENGTH) * UCF(LENGTH); - - // --- compute total groundwater flow - q = (t1 - t2 + t3) / UCF(GWFLOW); - if ( q < 0.0 && GW->a3 != 0.0 ) q = 0.0; - return q; -} - -//============================================================================= - -int getVariableIndex(char* s) -// -// Input: s = name of a groundwater variable -// Output: returns index of groundwater variable -// Purpose: finds position of GW variable in list of GW variable names. -// -{ - int k; - - k = findmatch(s, GWVarWords); - if ( k >= 0 ) return k; - return -1; -} - -//============================================================================= - -double getVariableValue(int varIndex) -// -// Input: varIndex = index of a GW variable -// Output: returns current value of GW variable -// Purpose: finds current value of a GW variable. -// -{ - switch (varIndex) - { - case gwvHGW: return Hgw * UCF(LENGTH); - case gwvHSW: return Hsw * UCF(LENGTH); - case gwvHCB: return Hstar * UCF(LENGTH); - case gwvHGS: return TotalDepth * UCF(LENGTH); - case gwvKS: return A.conductivity * UCF(RAINFALL); - case gwvK: return HydCon * UCF(RAINFALL); - case gwvTHETA:return Theta; - case gwvPHI: return A.porosity; - case gwvFI: return Infil * UCF(RAINFALL); - case gwvFU: return UpperPerc * UCF(RAINFALL); - case gwvA: return Area * UCF(LANDAREA); - default: return 0.0; - } -} diff --git a/src/hash.c b/src/hash.c deleted file mode 100644 index 4a73e6ccd..000000000 --- a/src/hash.c +++ /dev/null @@ -1,117 +0,0 @@ -//----------------------------------------------------------------------------- -// hash.c -// -// Implementation of a simple Hash Table for string storage & retrieval -// CASE INSENSITIVE -// -// Written by L. Rossman -// Last Updated on 6/19/03 -// -// The hash table data structure (HTable) is defined in "hash.h". -// Interface Functions: -// HTcreate() - creates a hash table -// HTinsert() - inserts a string & its index value into a hash table -// HTfind() - retrieves the index value of a string from a table -// HTfree() - frees a hash table -//----------------------------------------------------------------------------- - -#include -#include -#include "hash.h" -#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) - -/* Case-insensitive comparison of strings s1 and s2 */ -int samestr(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); - return(0); -} /* End of samestr */ - -/* Use Fletcher's checksum to compute 2-byte hash of string */ -unsigned int hash(const char *str) -{ - unsigned int sum1= 0, check1; - unsigned long sum2= 0L; - while( '\0' != *str ) - { - sum1 += UCHAR(*str); - str++; - if ( 255 <= sum1 ) sum1 -= 255; - sum2 += sum1; - } - check1= sum2; - check1 %= 255; - check1= 255 - (sum1+check1) % 255; - sum1= 255 - (sum1+check1) % 255; - return( ( ( check1 << 8 ) | sum1 ) % HTMAXSIZE); -} - -HTtable *HTcreate() -{ - int i; - HTtable *ht = (HTtable *) calloc(HTMAXSIZE, sizeof(HTtable)); - if (ht != NULL) for (i=0; i= HTMAXSIZE ) return(0); - entry = (struct HTentry *) malloc(sizeof(struct HTentry)); - if (entry == NULL) return(0); - entry->key = key; - entry->data = data; - entry->next = ht[i]; - ht[i] = entry; - return(1); -} - -int HTfind(HTtable *ht, const char *key) -{ - unsigned int i = hash(key); - struct HTentry *entry; - if ( i >= HTMAXSIZE ) return(NOTFOUND); - entry = ht[i]; - while (entry != NULL) - { - if ( samestr(entry->key,key) ) return(entry->data); - entry = entry->next; - } - return(NOTFOUND); -} - -char *HTfindKey(HTtable *ht, const char *key) -{ - unsigned int i = hash(key); - struct HTentry *entry; - if ( i >= HTMAXSIZE ) return(NULL); - entry = ht[i]; - while (entry != NULL) - { - if ( samestr(entry->key,key) ) return(entry->key); - entry = entry->next; - } - return(NULL); -} - -void HTfree(HTtable *ht) -{ - struct HTentry *entry, - *nextentry; - int i; - for (i=0; inext; - free(entry); - entry = nextentry; - } - } - free(ht); -} diff --git a/src/hash.h b/src/hash.h deleted file mode 100644 index ba42be00e..000000000 --- a/src/hash.h +++ /dev/null @@ -1,30 +0,0 @@ -//----------------------------------------------------------------------------- -// hash.h -// -// Header file for Hash Table module hash.c. -//----------------------------------------------------------------------------- - -#ifndef HASH_H -#define HASH_H - - -#define HTMAXSIZE 1999 -#define NOTFOUND -1 - -struct HTentry -{ - char *key; - int data; - struct HTentry *next; -}; - -typedef struct HTentry *HTtable; - -HTtable* HTcreate(void); -int HTinsert(HTtable *, char *, int); -int HTfind(HTtable *, const char *); -char* HTfindKey(HTtable *, const char *); -void HTfree(HTtable *); - - -#endif //HASH_H diff --git a/src/headers.h b/src/headers.h deleted file mode 100644 index 87139b060..000000000 --- a/src/headers.h +++ /dev/null @@ -1,20 +0,0 @@ -//----------------------------------------------------------------------------- -// headers.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Header files included in most SWMM5 modules. -// -// DO NOT CHANGE THE ORDER OF THE #INCLUDE STATEMENTS -//----------------------------------------------------------------------------- -#include "macros.h" -#include "objects.h" -#define EXTERN extern -#include "globals.h" -#include "funcs.h" -#include "error.h" -#include "text.h" -#include "keywords.h" diff --git a/src/hotstart.c b/src/hotstart.c deleted file mode 100644 index 69f7abcc9..000000000 --- a/src/hotstart.c +++ /dev/null @@ -1,544 +0,0 @@ -//----------------------------------------------------------------------------- -// hotstart.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Hot Start file functions. -// -// A SWMM hot start file contains the state of a SWMM project after -// a simulation has been run, allowing it to be used to initialize -// a subsequent simulation that picks up where the previous run ended. -// -// An abridged version of the hot start file (version 2) is available -// that contains only variables that appear in the binary output file -// (groundwater upper moisture and water table elevation, node depth, -// lateral inflow, and quality, and link flow, depth, setting and quality). -// -// When reading a previously saved hot start file checks are made to -// insure the the current SWMM project has the same number of major -// components (subcatchments, land uses, nodes, links, and pollutants) -// and unit system as does the hot start file. No test is made to -// insure that these components are of the same sub-type and maintain -// the same order as when the hot start file was created. -// -// Update History -// ============== -// Build 5.1.008: -// - Storage node hydraulic residence time (HRT) was added to the file. -// - Link control settings are now applied when reading a hot start file. -// - Runoff read from file assigned to newRunoff property instead of oldRunoff. -// - Array indexing bug when reading snowpack state from file fixed. -// Build 5.1.011: -// - Link control setting bug when reading a hot start file fixed. -// Build 5.1.015: -// - Support added for multiple infiltration methods within a project. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Local Variables -//----------------------------------------------------------------------------- -static int fileVersion; - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// hotstart_open (called by swmm_start in swmm5.c) -// hotstart_close (called by swmm_end in swmm5.c) - -//----------------------------------------------------------------------------- -// Function declarations -//----------------------------------------------------------------------------- -static int openHotstartFile1(void); -static int openHotstartFile2(void); -static void readRunoff(void); -static void saveRunoff(void); -static void readRouting(void); -static void saveRouting(void); -static int readFloat(float *x, FILE* f); -static int readDouble(double* x, FILE* f); - -//============================================================================= - -int hotstart_open() -{ - // --- open hot start files - if ( !openHotstartFile1() ) return FALSE; //input hot start file - if ( !openHotstartFile2() ) return FALSE; //output hot start file - return TRUE; -} - -//============================================================================= - -void hotstart_close() -{ - if ( Fhotstart2.file ) - { - saveRunoff(); - saveRouting(); - fclose(Fhotstart2.file); - } -} - -//============================================================================= - -int openHotstartFile1() -// -// Input: none -// Output: none -// Purpose: opens a previously saved routing hotstart file. -// -{ - int nSubcatch; - int nNodes; - int nLinks; - int nPollut; - int nLandUses; - int flowUnits; - char fStamp[] = "SWMM5-HOTSTART"; - char fileStamp[] = "SWMM5-HOTSTART"; - char fStampx[] = "SWMM5-HOTSTARTx"; - char fileStamp2[] = "SWMM5-HOTSTART2"; - char fileStamp3[] = "SWMM5-HOTSTART3"; - char fileStamp4[] = "SWMM5-HOTSTART4"; - - // --- try to open the file - if ( Fhotstart1.mode != USE_FILE ) return TRUE; - if ( (Fhotstart1.file = fopen(Fhotstart1.name, "r+b")) == NULL) - { - report_writeErrorMsg(ERR_HOTSTART_FILE_OPEN, Fhotstart1.name); - return FALSE; - } - - // --- check that file contains proper header records - fread(fStampx, sizeof(char), strlen(fileStamp2), Fhotstart1.file); - if ( strcmp(fStampx, fileStamp4) == 0 ) fileVersion = 4; - else if ( strcmp(fStampx, fileStamp3) == 0 ) fileVersion = 3; - else if ( strcmp(fStampx, fileStamp2) == 0 ) fileVersion = 2; - else - { - rewind(Fhotstart1.file); - fread(fStamp, sizeof(char), strlen(fileStamp), Fhotstart1.file); - if ( strcmp(fStamp, fileStamp) != 0 ) - { - report_writeErrorMsg(ERR_HOTSTART_FILE_FORMAT, ""); - return FALSE; - } - fileVersion = 1; - } - - nSubcatch = -1; - nNodes = -1; - nLinks = -1; - nPollut = -1; - nLandUses = -1; - flowUnits = -1; - if ( fileVersion >= 2 ) - { - fread(&nSubcatch, sizeof(int), 1, Fhotstart1.file); - } - else nSubcatch = Nobjects[SUBCATCH]; - if ( fileVersion >= 3 ) - { - fread(&nLandUses, sizeof(int), 1, Fhotstart1.file); - } - else nLandUses = Nobjects[LANDUSE]; - fread(&nNodes, sizeof(int), 1, Fhotstart1.file); - fread(&nLinks, sizeof(int), 1, Fhotstart1.file); - fread(&nPollut, sizeof(int), 1, Fhotstart1.file); - fread(&flowUnits, sizeof(int), 1, Fhotstart1.file); - if ( nSubcatch != Nobjects[SUBCATCH] - || nLandUses != Nobjects[LANDUSE] - || nNodes != Nobjects[NODE] - || nLinks != Nobjects[LINK] - || nPollut != Nobjects[POLLUT] - || flowUnits != FlowUnits ) - { - report_writeErrorMsg(ERR_HOTSTART_FILE_FORMAT, ""); - return FALSE; - } - - // --- read contents of the file and close it - if ( fileVersion >= 3 ) readRunoff(); - readRouting(); - fclose(Fhotstart1.file); - if ( ErrorCode ) return FALSE; - else return TRUE; -} - -//============================================================================= - -int openHotstartFile2() -// -// Input: none -// Output: none -// Purpose: opens a new routing hotstart file to save results to. -// -{ - int nSubcatch; - int nLandUses; - int nNodes; - int nLinks; - int nPollut; - int flowUnits; - char fileStamp[] = "SWMM5-HOTSTART4"; - - // --- try to open file - if ( Fhotstart2.mode != SAVE_FILE ) return TRUE; - if ( (Fhotstart2.file = fopen(Fhotstart2.name, "w+b")) == NULL) - { - report_writeErrorMsg(ERR_HOTSTART_FILE_OPEN, Fhotstart2.name); - return FALSE; - } - - // --- write file stamp & number of objects to file - nSubcatch = Nobjects[SUBCATCH]; - nLandUses = Nobjects[LANDUSE]; - nNodes = Nobjects[NODE]; - nLinks = Nobjects[LINK]; - nPollut = Nobjects[POLLUT]; - flowUnits = FlowUnits; - fwrite(fileStamp, sizeof(char), strlen(fileStamp), Fhotstart2.file); - fwrite(&nSubcatch, sizeof(int), 1, Fhotstart2.file); - fwrite(&nLandUses, sizeof(int), 1, Fhotstart2.file); - fwrite(&nNodes, sizeof(int), 1, Fhotstart2.file); - fwrite(&nLinks, sizeof(int), 1, Fhotstart2.file); - fwrite(&nPollut, sizeof(int), 1, Fhotstart2.file); - fwrite(&flowUnits, sizeof(int), 1, Fhotstart2.file); - return TRUE; -} - -//============================================================================= - -void saveRouting() -// -// Input: none -// Output: none -// Purpose: saves current state of all nodes and links to hotstart file. -// -{ - int i, j; - float x[3]; - - for (i = 0; i < Nobjects[NODE]; i++) - { - x[0] = (float)Node[i].newDepth; - x[1] = (float)Node[i].newLatFlow; - fwrite(x, sizeof(float), 2, Fhotstart2.file); - - if ( Node[i].type == STORAGE ) - { - j = Node[i].subIndex; - x[0] = (float)Storage[j].hrt; - fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); - } - - for (j = 0; j < Nobjects[POLLUT]; j++) - { - x[0] = (float)Node[i].newQual[j]; - fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); - } - } - for (i = 0; i < Nobjects[LINK]; i++) - { - x[0] = (float)Link[i].newFlow; - x[1] = (float)Link[i].newDepth; - x[2] = (float)Link[i].setting; - fwrite(x, sizeof(float), 3, Fhotstart2.file); - for (j = 0; j < Nobjects[POLLUT]; j++) - { - x[0] = (float)Link[i].newQual[j]; - fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); - } - } -} - -//============================================================================= - -void readRouting() -// -// Input: none -// Output: none -// Purpose: reads initial state of all nodes, links and groundwater objects -// from hotstart file. -// -{ - int i, j; - float x; - double xgw[4]; - FILE* f = Fhotstart1.file; - - // --- for file format 2, assign GW moisture content and lower depth - if ( fileVersion == 2 ) - { - // --- flow and available upper zone volume not used - xgw[2] = 0.0; - xgw[3] = MISSING; - for (i = 0; i < Nobjects[SUBCATCH]; i++) - { - // --- read moisture content and water table elevation as floats - if ( !readFloat(&x, f) ) return; - xgw[0] = x; - if ( !readFloat(&x, f) ) return; - xgw[1] = x; - - // --- set GW state - if ( Subcatch[i].groundwater != NULL ) gwater_setState(i, xgw); - } - } - - // --- read node states - for (i = 0; i < Nobjects[NODE]; i++) - { - if ( !readFloat(&x, f) ) return; - Node[i].newDepth = x; - if ( !readFloat(&x, f) ) return; - Node[i].newLatFlow = x; - - if ( fileVersion >= 4 && Node[i].type == STORAGE ) - { - if ( !readFloat(&x, f) ) return; - j = Node[i].subIndex; - Storage[j].hrt = x; - } - - for (j = 0; j < Nobjects[POLLUT]; j++) - { - if ( !readFloat(&x, f) ) return; - Node[i].newQual[j] = x; - } - - // --- read in zeros here for backwards compatibility - if ( fileVersion <= 2 ) - { - for (j = 0; j < Nobjects[POLLUT]; j++) - { - if ( !readFloat(&x, f) ) return; - } - } - } - - // --- read link states - for (i = 0; i < Nobjects[LINK]; i++) - { - if ( !readFloat(&x, f) ) return; - Link[i].newFlow = x; - if ( !readFloat(&x, f) ) return; - Link[i].newDepth = x; - if ( !readFloat(&x, f) ) return; - Link[i].setting = x; - - // --- set link's target setting to saved setting - Link[i].targetSetting = x; - link_setTargetSetting(i); - link_setSetting(i, 0.0); - - for (j = 0; j < Nobjects[POLLUT]; j++) - { - if ( !readFloat(&x, f) ) return; - Link[i].newQual[j] = x; - } - - } -} - -//============================================================================= - -void saveRunoff(void) -// -// Input: none -// Output: none -// Purpose: saves current state of all subcatchments to hotstart file. -// -{ - int i, j, k; - double x[6]; - FILE* f = Fhotstart2.file; - - for (i = 0; i < Nobjects[SUBCATCH]; i++) - { - // Ponded depths for each sub-area & total runoff (4 elements) - for (j = 0; j < 3; j++) x[j] = Subcatch[i].subArea[j].depth; - x[3] = Subcatch[i].newRunoff; - fwrite(x, sizeof(double), 4, f); - - // Infiltration state (max. of 6 elements) - for (j=0; j<6; j++) x[j] = 0.0; - infil_getState(i, x); - fwrite(x, sizeof(double), 6, f); - - // Groundwater state (4 elements) - if ( Subcatch[i].groundwater != NULL ) - { - gwater_getState(i, x); - fwrite(x, sizeof(double), 4, f); - } - - // Snowpack state (5 elements for each of 3 snow surfaces) - if ( Subcatch[i].snowpack != NULL ) - { - for (j=0; j<3; j++) - { - snow_getState(i, j, x); - fwrite(x, sizeof(double), 5, f); - } - } - - // Water quality - if ( Nobjects[POLLUT] > 0 ) - { - // Runoff quality - for (j=0; j 0 ) - { - // Runoff quality - for (j=0; j -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Imported variables -//----------------------------------------------------------------------------- -extern double Qcf[]; // flow units conversion factors - // (see swmm5.c) - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -static int IfaceFlowUnits; // flow units for routing interface file -static int IfaceStep; // interface file time step (sec) -static int NumIfacePolluts; // number of pollutants in interface file -static int* IfacePolluts; // indexes of interface file pollutants -static int NumIfaceNodes; // number of nodes on interface file -static int* IfaceNodes; // indexes of nodes on interface file -static double** OldIfaceValues; // interface flows & WQ at previous time -static double** NewIfaceValues; // interface flows & WQ at next time -static double IfaceFrac; // fraction of interface file time step -static DateTime OldIfaceDate; // previous date of interface values -static DateTime NewIfaceDate; // next date of interface values - -//----------------------------------------------------------------------------- -// External Functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// iface_readFileParams (called by input_readLine) -// iface_openRoutingFiles (called by routing_open) -// iface_closeRoutingFiles (called by routing_close) -// iface_getNumIfaceNodes (called by addIfaceInflows in routing.c) -// iface_getIfaceNode (called by addIfaceInflows in routing.c) -// iface_getIfaceFlow (called by addIfaceInflows in routing.c) -// iface_getIfaceQual (called by addIfaceInflows in routing.c) -// iface_saveOutletResults (called by output_saveResults) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static void openFileForOutput(void); -static void openFileForInput(void); -static int getIfaceFilePolluts(void); -static int getIfaceFileNodes(void); -static void setOldIfaceValues(void); -static void readNewIfaceValues(void); -static int isOutletNode(int node); - -//============================================================================= - -int iface_readFileParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads interface file information from a line of input data. -// -// Data format is: -// USE/SAVE FileType FileName -// -{ - char k; - int j; - char fname[MAXFNAME+1]; - - // --- determine file disposition and type - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - k = (char)findmatch(tok[0], FileModeWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); - j = findmatch(tok[1], FileTypeWords); - if ( j < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - if ( ntoks < 3 ) return 0; - sstrncpy(fname, tok[2], MAXFNAME); - - // --- process file name - switch ( j ) - { - case RAINFALL_FILE: - Frain.mode = k; - sstrncpy(Frain.name, addAbsolutePath(fname), MAXFNAME); - break; - - case RUNOFF_FILE: - Frunoff.mode = k; - sstrncpy(Frunoff.name, addAbsolutePath(fname), MAXFNAME); - break; - - case HOTSTART_FILE: - if ( k == USE_FILE ) - { - Fhotstart1.mode = k; - sstrncpy(Fhotstart1.name, addAbsolutePath(fname), MAXFNAME); - } - else if ( k == SAVE_FILE ) - { - Fhotstart2.mode = k; - sstrncpy(Fhotstart2.name, addAbsolutePath(fname), MAXFNAME); - } - break; - - case RDII_FILE: - Frdii.mode = k; - sstrncpy(Frdii.name, fname, MAXFNAME); - break; - - case INFLOWS_FILE: - if ( k != USE_FILE ) return error_setInpError(ERR_ITEMS, ""); - Finflows.mode = k; - sstrncpy(Finflows.name, addAbsolutePath(fname), MAXFNAME); - break; - - case OUTFLOWS_FILE: - if ( k != SAVE_FILE ) return error_setInpError(ERR_ITEMS, ""); - Foutflows.mode = k; - sstrncpy(Foutflows.name, addAbsolutePath(fname), MAXFNAME); - break; - } - return 0; -} - -//============================================================================= - -void iface_openRoutingFiles() -// -// Input: none -// Output: none -// Purpose: opens routing interface files. -// -{ - // --- initialize shared variables - NumIfacePolluts = 0; - IfacePolluts = NULL; - NumIfaceNodes = 0; - IfaceNodes = NULL; - OldIfaceValues = NULL; - NewIfaceValues = NULL; - - // --- check that inflows & outflows files are not the same - if ( Foutflows.mode != NO_FILE && Finflows.mode != NO_FILE ) - { - if ( strcomp(Foutflows.name, Finflows.name) ) - { - report_writeErrorMsg(ERR_ROUTING_FILE_NAMES, ""); - return; - } - } - - // --- open the file for reading or writing - if ( Foutflows.mode == SAVE_FILE ) openFileForOutput(); - if ( Finflows.mode == USE_FILE ) openFileForInput(); -} - -//============================================================================= - -void iface_closeRoutingFiles() -// -// Input: none -// Output: none -// Purpose: closes routing interface files. -// -{ - FREE(IfacePolluts); - FREE(IfaceNodes); - if ( OldIfaceValues != NULL ) project_freeMatrix(OldIfaceValues); - if ( NewIfaceValues != NULL ) project_freeMatrix(NewIfaceValues); - if ( Finflows.file ) fclose(Finflows.file); - if ( Foutflows.file ) fclose(Foutflows.file); -} - -//============================================================================= - -int iface_getNumIfaceNodes(DateTime currentDate) -// -// Input: currentDate = current date/time -// Output: returns number of interface nodes if data exists or -// 0 otherwise -// Purpose: reads inflow data from interface file at current date. -// -{ - // --- return 0 if file begins after current date - if ( OldIfaceDate > currentDate ) return 0; - - // --- keep updating new interface values until current date bracketed - while ( NewIfaceDate < currentDate && NewIfaceDate != NO_DATE ) - { - setOldIfaceValues(); - readNewIfaceValues(); - } - - // --- return 0 if no data available - if ( NewIfaceDate == NO_DATE ) return 0; - - // --- find fraction current date is bewteen old & new interface dates - IfaceFrac = (currentDate - OldIfaceDate) / (NewIfaceDate - OldIfaceDate); - IfaceFrac = MAX(0.0, IfaceFrac); - IfaceFrac = MIN(IfaceFrac, 1.0); - - // --- return number of interface nodes - return NumIfaceNodes; -} - -//============================================================================= - -int iface_getIfaceNode(int index) -// -// Input: index = interface file node index -// Output: returns project node index -// Purpose: finds index of project node associated with interface node index -// -{ - if ( index >= 0 && index < NumIfaceNodes ) return IfaceNodes[index]; - else return -1; -} - -//============================================================================= - -double iface_getIfaceFlow(int index) -// -// Input: index = interface file node index -// Output: returns inflow to node -// Purpose: finds interface flow for particular node index. -// -{ - double q1, q2; - - if ( index >= 0 && index < NumIfaceNodes ) - { - // --- interpolate flow between old and new values - q1 = OldIfaceValues[index][0]; - q2 = NewIfaceValues[index][0]; - return (1.0 - IfaceFrac)*q1 + IfaceFrac*q2; - } - else return 0.0; -} - -//============================================================================= - -double iface_getIfaceQual(int index, int pollut) -// -// Input: index = index of node on interface file -// pollut = index of pollutant on interface file -// Output: returns inflow pollutant concentration -// Purpose: finds interface concentration for particular node index & pollutant. -// -{ - int j; - double c1, c2; - - if ( index >= 0 && index < NumIfaceNodes ) - { - // --- find index of pollut on interface file - j = IfacePolluts[pollut]; - if ( j < 0 ) return 0.0; - - // --- interpolate flow between old and new values - // (remember that 1st col. of values matrix is for flow) - c1 = OldIfaceValues[index][j+1]; - c2 = NewIfaceValues[index][j+1]; - return (1.0 - IfaceFrac)*c1 + IfaceFrac*c2; - } - else return 0.0; -} - -//============================================================================= - -void iface_saveOutletResults(DateTime reportDate, FILE* file) -// -// Input: reportDate = reporting date/time -// file = ptr. to interface file -// Output: none -// Purpose: saves system outflows to routing interface file. -// -{ - int i, p, yr, mon, day, hr, min, sec; - char theDate[26]; - datetime_decodeDate(reportDate, &yr, &mon, &day); - datetime_decodeTime(reportDate, &hr, &min, &sec); - snprintf(theDate, 26, " %04d %02d %02d %02d %02d %02d ", - yr, mon, day, hr, min, sec); - for (i=0; i 0 ) - { - report_writeErrorMsg(err, Finflows.name); - return; - } - - // --- match nodes in file with those in project - err = getIfaceFileNodes(); - if ( err > 0 ) - { - report_writeErrorMsg(err, Finflows.name); - return; - } - - // --- create matrices for old & new interface flows & WQ values - OldIfaceValues = project_createMatrix(NumIfaceNodes, - 1+NumIfacePolluts); - NewIfaceValues = project_createMatrix(NumIfaceNodes, - 1+NumIfacePolluts); - if ( OldIfaceValues == NULL || NewIfaceValues == NULL ) - { - report_writeErrorMsg(ERR_MEMORY, ""); - return; - } - - // --- read in new interface flows & WQ values - readNewIfaceValues(); - OldIfaceDate = NewIfaceDate; -} - -//============================================================================= - -int getIfaceFilePolluts() -// -// Input: none -// Output: returns an error code -// Purpose: reads names of pollutants saved on the inflows interface file. -// -{ - int i, j; - char line[MAXLINE+1]; // line from inflows interface file - char s1[MAXLINE+1]; // general string variable - char s2[MAXLINE+1]; - - // --- read number of pollutants (minus FLOW) - fgets(line, MAXLINE, Finflows.file); - NumIfacePolluts = -1; - if (sscanf(line, "%d", &NumIfacePolluts)) - NumIfacePolluts--; - if ( NumIfacePolluts < 0 ) return ERR_ROUTING_FILE_FORMAT; - - // --- read flow units - fgets(line, MAXLINE, Finflows.file); - if (sscanf(line, "%s %s", s1, s2) < 2) - return ERR_ROUTING_FILE_FORMAT; - if ( !strcomp(s1, "FLOW") ) - return ERR_ROUTING_FILE_FORMAT; - IfaceFlowUnits = findmatch(s2, FlowUnitWords); - if ( IfaceFlowUnits < 0 ) - return ERR_ROUTING_FILE_FORMAT; - - // --- allocate memory for pollutant index array - if ( Nobjects[POLLUT] > 0 ) - { - IfacePolluts = (int *) calloc(Nobjects[POLLUT], sizeof(int)); - if ( !IfacePolluts ) return ERR_MEMORY; - for (i=0; i 0 && Nobjects[POLLUT] > 0 ) - { - // --- check each pollutant name on file with project's pollutants - for (i=0; i -#include -#include "headers.h" -#include "infil.h" - -//----------------------------------------------------------------------------- -// Local Variables -//----------------------------------------------------------------------------- -typedef union TInfil { - THorton horton; - TGrnAmpt grnAmpt; - TCurveNum curveNum; -} TInfil; -TInfil *Infil; - -static double Fumax; // saturated water volume in upper soil zone (ft) -static double InfilFactor; - -//----------------------------------------------------------------------------- -// External Functions (declared in infil.h) -//----------------------------------------------------------------------------- -// infil_create (called by createObjects in project.c) -// infil_delete (called by deleteObjects in project.c) -// infil_readParams (called by input_readLine) -// infil_initState (called by subcatch_initState) -// infil_getState (called by writeRunoffFile in hotstart.c) -// infil_setState (called by readRunoffFile in hotstart.c) -// infil_getInfil (called by getSubareaRunoff in subcatch.c) - -// Called locally and by storage node methods in node.c -// grnampt_setParams -// grnampt_initState -// grnampt_getInfil - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int horton_setParams(THorton *infil, double p[]); -static void horton_initState(THorton *infil); -static void horton_getState(THorton *infil, double x[]); -static void horton_setState(THorton *infil, double x[]); -static double horton_getInfil(THorton *infil, double tstep, double irate, - double depth); -static double modHorton_getInfil(THorton *infil, double tstep, double irate, - double depth); - -static void grnampt_getState(TGrnAmpt *infil, double x[]); -static void grnampt_setState(TGrnAmpt *infil, double x[]); -static double grnampt_getUnsatInfil(TGrnAmpt *infil, double tstep, - double irate, double depth, int modelType); -static double grnampt_getSatInfil(TGrnAmpt *infil, double tstep, - double irate, double depth); -static double grnampt_getF2(double f1, double c1, double ks, double ts); - -static int curvenum_setParams(TCurveNum *infil, double p[]); -static void curvenum_initState(TCurveNum *infil); -static void curvenum_getState(TCurveNum *infil, double x[]); -static void curvenum_setState(TCurveNum *infil, double x[]); -static double curvenum_getInfil(TCurveNum *infil, double tstep, double irate, - double depth); - -//============================================================================= - -void infil_create(int n) -// -// Purpose: creates an array of infiltration objects. -// Input: n = number of subcatchments -// Output: none -// -{ - Infil = (TInfil *) calloc(n, sizeof(TInfil)); - if (Infil == NULL) ErrorCode = ERR_MEMORY; - InfilFactor = 1.0; - return; -} - -//============================================================================= - -void infil_delete() -// -// Purpose: deletes infiltration objects associated with subcatchments -// Input: none -// Output: none -// -{ - FREE(Infil); -} - -//============================================================================= - -int infil_readParams(int m, char* tok[], int ntoks) -// -// Input: m = default infiltration model -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: sets infiltration parameters from a line of input data. -// -// Format of data line is: -// subcatch p1 p2 ... (infilMethod) -{ - int i, j, n, status; - double x[5]; - - // --- check that subcatchment exists - j = project_findObject(SUBCATCH, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- check for infiltration method keyword is last token - i = findmatch(tok[ntoks-1], InfilModelWords); - if ( i >= 0 ) - { - m = i; - --ntoks; - } - - // --- number of input tokens depends on infiltration model m - if ( m == HORTON ) n = 5; - else if ( m == MOD_HORTON ) n = 5; - else if ( m == GREEN_AMPT ) n = 4; - else if ( m == MOD_GREEN_AMPT ) n = 4; - else if ( m == CURVE_NUMBER ) n = 4; - else return 0; - - if ( ntoks < n ) return error_setInpError(ERR_ITEMS, ""); - - // --- parse numerical values from tokens - for (i = 0; i < 5; i++) x[i] = 0.0; - for (i = 1; i < n; i++) - { - if (!getDouble(tok[i], &x[i - 1])) - return error_setInpError(ERR_NUMBER, tok[i]); - } - - // --- special case for Horton infil. - last parameter is optional - if ( (m == HORTON || m == MOD_HORTON) && ntoks > n ) - { - if ( ! getDouble(tok[n], &x[n-1]) ) - return error_setInpError(ERR_NUMBER, tok[n]); - } - - // --- assign parameter values to infil, infilModel object - Subcatch[j].infil = j; - Subcatch[j].infilModel = m; - switch (m) - { - case HORTON: - case MOD_HORTON: status = horton_setParams(&Infil[j].horton, x); - break; - case GREEN_AMPT: - case MOD_GREEN_AMPT: - status = grnampt_setParams(&Infil[j].grnAmpt, x); - break; - case CURVE_NUMBER: status = curvenum_setParams(&Infil[j].curveNum, x); - break; - default: status = TRUE; - } - if ( !status ) return error_setInpError(ERR_INFIL_PARAMS, ""); - return 0; -} - -//============================================================================= - -void infil_initState(int j) -// -// Input: j = subcatchment index -// Output: none -// Purpose: initializes state of infiltration for a subcatchment. -// -{ - switch (Subcatch[j].infilModel) - { - case HORTON: - case MOD_HORTON: horton_initState(&Infil[j].horton); break; - case GREEN_AMPT: - case MOD_GREEN_AMPT: - grnampt_initState(&Infil[j].grnAmpt); break; - case CURVE_NUMBER: curvenum_initState(&Infil[j].curveNum); break; - } -} - -//============================================================================= - -void infil_getState(int j, double x[]) -// -// Input: j = subcatchment index -// Output: x = subcatchment's infiltration state -// Purpose: retrieves the current infiltration state for a subcatchment. -// -{ - switch (Subcatch[j].infilModel) - { - case HORTON: - case MOD_HORTON: horton_getState(&Infil[j].horton, x); break; - case GREEN_AMPT: - case MOD_GREEN_AMPT: - grnampt_getState(&Infil[j].grnAmpt, x); break; - case CURVE_NUMBER: curvenum_getState(&Infil[j].curveNum, x); break; - } -} - -//============================================================================= - -void infil_setState(int j, double x[]) -// -// Input: j = subcatchment index -// m = infiltration method code -// Output: none -// Purpose: sets the current infiltration state for a subcatchment. -// -{ - switch (Subcatch[j].infilModel) - { - case HORTON: - case MOD_HORTON: horton_setState(&Infil[j].horton, x); break; - case GREEN_AMPT: - case MOD_GREEN_AMPT: - grnampt_setState(&Infil[j].grnAmpt, x); break; - case CURVE_NUMBER: curvenum_setState(&Infil[j].curveNum, x); break; - } -} - -//============================================================================= - -void infil_setInfilFactor(int j) -// -// Input: j = subcatchment index -// Output: none -// Purpose: assigns a value to the infiltration adjustment factor. -{ - int m; - int p; - - // ... set factor to the global conductivity adjustment factor - InfilFactor = Adjust.hydconFactor; - - // ... override global factor with subcatchment's adjustment if assigned - if (j >= 0) - { - p = Subcatch[j].infilPattern; - if (p >= 0 && Pattern[p].type == MONTHLY_PATTERN) - { - m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; - InfilFactor = Pattern[p].factor[m]; - } - } -} - -//============================================================================= - -double infil_getInfil(int j, double tstep, double rainfall, - double runon, double depth) -// -// Input: j = subcatchment index -// tstep = runoff time step (sec) -// rainfall = rainfall rate (ft/sec) -// runon = runon rate from other sub-areas or subcatchments (ft/sec) -// depth = depth of surface water on subcatchment (ft) -// Output: returns infiltration rate (ft/sec) -// Purpose: computes infiltration rate depending on infiltration method. -// -{ - switch (Subcatch[j].infilModel) - { - case HORTON: - return horton_getInfil(&Infil[j].horton, tstep, rainfall+runon, depth); - - case MOD_HORTON: - return modHorton_getInfil(&Infil[j].horton, tstep, rainfall+runon, - depth); - - case GREEN_AMPT: - case MOD_GREEN_AMPT: - return grnampt_getInfil(&Infil[j].grnAmpt, tstep, rainfall+runon, depth, - Subcatch[j].infilModel); - - case CURVE_NUMBER: - depth += runon * tstep; - return curvenum_getInfil(&Infil[j].curveNum, tstep, rainfall, depth); - - default: - return 0.0; - } -} - -//============================================================================= - -int horton_setParams(THorton *infil, double p[]) -// -// Input: infil = ptr. to Horton infiltration object -// p[] = array of parameter values -// Output: returns TRUE if parameters are valid, FALSE otherwise -// Purpose: assigns Horton infiltration parameters to a subcatchment. -// -{ - int k; - for (k = 0; k < 5; k++) if ( p[k] < 0.0 ) return FALSE; - - // --- max. & min. infil rates (ft/sec) - infil->f0 = p[0] / UCF(RAINFALL); - infil->fmin = p[1] / UCF(RAINFALL); - - // --- convert decay const. to 1/sec - infil->decay = p[2] / 3600.; - - // --- convert drying time (days) to a regeneration const. (1/sec) - // assuming that former is time to reach 98% dry along an - // exponential drying curve - if (p[3] == 0.0 ) p[3] = TINY; - infil->regen = -log(1.0-0.98) / p[3] / SECperDAY; - - // --- optional max. infil. capacity (ft) (p[4] = 0 if no value supplied) - infil->Fmax = p[4] / UCF(RAINDEPTH); - if ( infil->f0 < infil->fmin ) return FALSE; - return TRUE; -} - -//============================================================================= - -void horton_initState(THorton *infil) -// -// Input: infil = ptr. to Horton infiltration object -// Output: none -// Purpose: initializes time on Horton infiltration curve for a subcatchment. -// -{ - infil->tp = 0.0; - infil->Fe = 0.0; -} - -//============================================================================= - -void horton_getState(THorton *infil, double x[]) -{ - x[0] = infil->tp; - x[1] = infil->Fe; -} - -void horton_setState(THorton *infil, double x[]) -{ - infil->tp = x[0]; - infil->Fe = x[1]; -} - -//============================================================================= - -double horton_getInfil(THorton *infil, double tstep, double irate, double depth) -// -// Input: infil = ptr. to Horton infiltration object -// tstep = runoff time step (sec), -// irate = net "rainfall" rate (ft/sec), -// = rainfall + snowmelt + runon - evaporation -// depth = depth of ponded water (ft). -// Output: returns infiltration rate (ft/sec) -// Purpose: computes Horton infiltration for a subcatchment. -// -{ - // --- assign local variables - int iter; - double fa, fp = 0.0; - double Fp, F1, t1, tlim, ex, kt; - double FF, FF1, r; - double f0 = infil->f0 * InfilFactor; - double fmin = infil->fmin * InfilFactor; - double Fmax = infil->Fmax; - double tp = infil->tp; - double df = f0 - fmin; - double kd = infil->decay; - double kr = infil->regen * Evap.recoveryFactor; - - // --- special cases of no infil. or constant infil - if ( df < 0.0 || kd < 0.0 || kr < 0.0 ) return 0.0; - if ( df == 0.0 || kd == 0.0 ) - { - fp = f0; - fa = irate + depth / tstep; - if ( fp > fa ) fp = fa; - return MAX(0.0, fp); - } - - // --- compute water available for infiltration - fa = irate + depth / tstep; - - // --- case where there is water to infiltrate - if ( fa > ZERO ) - { - // --- compute average infil. rate over time step - t1 = tp + tstep; // future cumul. time - tlim = 16.0 / kd; // for tp >= tlim, f = fmin - if ( tp >= tlim ) - { - Fp = fmin * tp + df / kd; - F1 = Fp + fmin * tstep; - } - else - { - Fp = fmin * tp + df / kd * (1.0 - exp(-kd * tp)); - F1 = fmin * t1 + df / kd * (1.0 - exp(-kd * t1)); - } - fp = (F1 - Fp) / tstep; - fp = MAX(fp, fmin); - - // --- limit infil rate to available infil - if ( fp > fa ) fp = fa; - - // --- if fp on flat portion of curve then increase tp by tstep - if ( t1 > tlim ) tp = t1; - - // --- if infil < available capacity then increase tp by tstep - else if ( fp < fa ) tp = t1; - - // --- if infil limited by available capacity then - // solve F(tp) - F1 = 0 using Newton-Raphson method - else - { - F1 = Fp + fp * tstep; - tp = tp + tstep / 2.0; - for ( iter=1; iter<=20; iter++ ) - { - kt = MIN( 60.0, kd*tp ); - ex = exp(-kt); - FF = fmin * tp + df / kd * (1.0 - ex) - F1; - FF1 = fmin + df * ex; - r = FF / FF1; - tp = tp - r; - if ( fabs(r) <= 0.001 * tstep ) break; - } - } - - // --- limit cumulative infiltration to Fmax - if ( Fmax > 0.0 ) - { - if ( infil->Fe + fp * tstep > Fmax ) - fp = (Fmax - infil->Fe) / tstep; - fp = MAX(fp, 0.0); - infil->Fe += fp * tstep; - } - } - - // --- case where infil. capacity is regenerating; update tp. - else if (kr > 0.0) - { - r = exp(-kr * tstep); - tp = 1.0 - exp(-kd * tp); - tp = -log(1.0 - r*tp) / kd; - - // reduction in cumulative infiltration - if ( Fmax > 0.0 ) - { - infil->Fe = fmin*tp + (df/kd)*(1.0 - exp(-kd*tp)); - } - } - infil->tp = tp; - return fp; -} - -//============================================================================= - -double modHorton_getInfil(THorton *infil, double tstep, double irate, - double depth) -// -// Input: infil = ptr. to Horton infiltration object -// tstep = runoff time step (sec), -// irate = net "rainfall" rate (ft/sec), -// = rainfall + snowmelt + runon -// depth = depth of ponded water (ft). -// Output: returns infiltration rate (ft/sec) -// Purpose: computes modified Horton infiltration for a subcatchment. -// -{ - // --- assign local variables - double f = 0.0; - double fp, fa; - double f0 = infil->f0 * InfilFactor; - double fmin = infil->fmin * InfilFactor; - double df = f0 - fmin; - double kd = infil->decay; - double kr = infil->regen * Evap.recoveryFactor; - - // --- special cases of no or constant infiltration - if ( df < 0.0 || kd < 0.0 || kr < 0.0 ) return 0.0; - if ( df == 0.0 || kd == 0.0 ) - { - fp = f0; - fa = irate + depth / tstep; - if ( fp > fa ) fp = fa; - return MAX(0.0, fp); - } - - // --- compute water available for infiltration - fa = irate + depth / tstep; - - // --- case where there is water to infiltrate - if ( fa > ZERO ) - { - // --- saturated condition - if ( infil->Fmax > 0.0 && infil->Fe >= infil->Fmax ) return 0.0; - - // --- potential infiltration - fp = f0 - kd * infil->Fe; - fp = MAX(fp, fmin); - - // --- actual infiltration - f = MIN(fa, fp); - - // --- new cumulative infiltration minus seepage - infil->Fe += MAX((f - fmin), 0.0) * tstep; - if ( infil->Fmax > 0.0 ) infil->Fe = MAX(infil->Fe, infil->Fmax); - } - - // --- reduce cumulative infiltration for dry condition - else if (kr > 0.0) - { - infil->Fe *= exp(-kr * tstep); - infil->Fe = MAX(infil->Fe, 0.0); - } - return f; -} - -//============================================================================= - -void grnampt_getParams(int j, double p[]) -// -// Input: j = subcatchment index -// p[] = array of parameter values -// Output: none -// Purpose: retrieves Green-Ampt infiltration parameters for a subcatchment. -// -{ - p[0] = Infil[j].grnAmpt.S * UCF(RAINDEPTH); // Capillary suction head (ft) - p[1] = Infil[j].grnAmpt.Ks * UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) - p[2] = Infil[j].grnAmpt.IMDmax; // Max. init. moisture deficit -} - -//============================================================================= - -int grnampt_setParams(TGrnAmpt *infil, double p[]) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// p[] = array of parameter values -// Output: returns TRUE if parameters are valid, FALSE otherwise -// Purpose: assigns Green-Ampt infiltration parameters to a subcatchment. -// -{ - double ksat; // sat. hyd. conductivity in in/hr - - if ( p[0] < 0.0 || p[1] <= 0.0 || p[2] < 0.0 || p[2] > 1.0) return FALSE; - infil->S = p[0] / UCF(RAINDEPTH); // Capillary suction head (ft) - infil->Ks = p[1] / UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) - infil->IMDmax = p[2]; // Max. init. moisture deficit - - // --- find depth of upper soil zone (ft) using Mein's eqn. - ksat = infil->Ks * 12. * 3600.; - infil->Lu = 4.0 * sqrt(ksat) / 12.; - return TRUE; -} - -//============================================================================= - -void grnampt_initState(TGrnAmpt *infil) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// Output: none -// Purpose: initializes state of Green-Ampt infiltration for a subcatchment. -// -{ - if (infil == NULL) return; - infil->IMD = infil->IMDmax; - infil->Fu = 0.0; - infil->F = 0.0; - infil->Sat = FALSE; - infil->T = 0.0; -} - -void grnampt_getState(TGrnAmpt *infil, double x[]) -{ - x[0] = infil->IMD; - x[1] = infil->F; - x[2] = infil->Fu; - x[3] = infil->Sat; - x[4] = infil->T; -} - -void grnampt_setState(TGrnAmpt *infil, double x[]) -{ - infil->IMD = x[0]; - infil->F = x[1]; - infil->Fu = x[2]; - infil->Sat = (char)x[3]; - infil->T = x[4]; -} - -//============================================================================= - -double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, - double depth, int modelType) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// tstep = time step (sec), -// irate = net "rainfall" rate to upper zone (ft/sec); -// = rainfall + snowmelt + runon, -// does not include ponded water (added on below) -// depth = depth of ponded water (ft) -// modelType = either GREEN_AMPT or MOD_GREEN_AMPT -// Output: returns infiltration rate (ft/sec) -// Purpose: computes Green-Ampt infiltration for a subcatchment -// or a storage node. -// -{ - // --- find saturated upper soil zone water volume - Fumax = infil->IMDmax * infil->Lu * sqrt(InfilFactor); - - // --- reduce time until next event - infil->T -= tstep; - - // --- use different procedures depending on upper soil zone saturation - if ( infil->Sat ) return grnampt_getSatInfil(infil, tstep, irate, depth); - else return grnampt_getUnsatInfil(infil, tstep, irate, depth, modelType); -} - -//============================================================================= - -double grnampt_getUnsatInfil(TGrnAmpt *infil, double tstep, double irate, - double depth, int modelType) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// tstep = runoff time step (sec), -// irate = net "rainfall" rate to upper zone (ft/sec); -// = rainfall + snowmelt + runon, -// does not include ponded water (added on below) -// depth = depth of ponded water (ft) -// modelType = either GREEN_AMPT or MOD_GREEN_AMPT -// Output: returns infiltration rate (ft/sec) -// Purpose: computes Green-Ampt infiltration when upper soil zone is -// unsaturated. -// -{ - double ia, c1, F2, dF, Fs, kr, ts; - double ks = infil->Ks * InfilFactor; - double lu = infil->Lu * sqrt(InfilFactor); - - // --- get available infiltration rate (rainfall + ponded water) - ia = irate + depth / tstep; - if ( ia < ZERO ) ia = 0.0; - - // --- no rainfall so recover upper zone moisture - if ( ia == 0.0 ) - { - if ( infil->Fu <= 0.0 ) return 0.0; - kr = lu / 90000.0 * Evap.recoveryFactor; - dF = kr * Fumax * tstep; - infil->F -= dF; - infil->Fu -= dF; - if ( infil->Fu <= 0.0 ) - { - infil->Fu = 0.0; - infil->F = 0.0; - infil->IMD = infil->IMDmax; - return 0.0; - } - - // --- if new wet event begins then reset IMD & F - if ( infil->T <= 0.0 ) - { - infil->IMD = (Fumax - infil->Fu) / lu; - infil->F = 0.0; - } - return 0.0; - } - - // --- rainfall does not exceed Ksat - if ( ia <= ks ) - { - dF = ia * tstep; - infil->F += dF; - infil->Fu += dF; - infil->Fu = MIN(infil->Fu, Fumax); - if ( modelType == GREEN_AMPT && infil->T <= 0.0 ) - { - infil->IMD = (Fumax - infil->Fu) / lu; - infil->F = 0.0; - } - return ia; - } - - // --- rainfall exceeds Ksat; renew time to drain upper zone - infil->T = 5400.0 / lu / Evap.recoveryFactor; - - // --- find volume needed to saturate surface layer - Fs = ks * (infil->S + depth) * infil->IMD / (ia - ks); - - // --- surface layer already saturated - if ( infil->F > Fs ) - { - infil->Sat = TRUE; - return grnampt_getSatInfil(infil, tstep, irate, depth); - } - - // --- surface layer remains unsaturated - if ( infil->F + ia*tstep < Fs ) - { - dF = ia * tstep; - infil->F += dF; - infil->Fu += dF; - infil->Fu = MIN(infil->Fu, Fumax); - return ia; - } - - // --- surface layer becomes saturated during time step; - // --- compute portion of tstep when saturated - ts = tstep - (Fs - infil->F) / ia; - if ( ts <= 0.0 ) ts = 0.0; - - // --- compute new total volume infiltrated - c1 = (infil->S + depth) * infil->IMD; - F2 = grnampt_getF2(Fs, c1, ks, ts); - if ( F2 > Fs + ia*ts ) F2 = Fs + ia*ts; - - // --- compute infiltration rate - dF = F2 - infil->F; - infil->F = F2; - infil->Fu += dF; - infil->Fu = MIN(infil->Fu, Fumax); - infil->Sat = TRUE; - return dF / tstep; -} - -//============================================================================= - -double grnampt_getSatInfil(TGrnAmpt *infil, double tstep, double irate, - double depth) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// tstep = runoff time step (sec), -// irate = net "rainfall" rate to upper zone (ft/sec); -// = rainfall + snowmelt + runon, -// does not include ponded water (added on below) -// depth = depth of ponded water (ft). -// Output: returns infiltration rate (ft/sec) -// Purpose: computes Green-Ampt infiltration when upper soil zone is -// saturated. -// -{ - double ia, c1, dF, F2; - double ks = infil->Ks * InfilFactor; - double lu = infil->Lu * sqrt(InfilFactor); - - // --- get available infiltration rate (rainfall + ponded water) - ia = irate + depth / tstep; - if ( ia < ZERO ) return 0.0; - - // --- re-set new event recovery time - infil->T = 5400.0 / lu / Evap.recoveryFactor; - - // --- solve G-A equation for new cumulative infiltration volume (F2) - c1 = (infil->S + depth) * infil->IMD; - F2 = grnampt_getF2(infil->F, c1, ks, tstep); - dF = F2 - infil->F; - - // --- all available water infiltrates -- set saturated state to false - if ( dF > ia * tstep ) - { - dF = ia * tstep; - infil->Sat = FALSE; - } - - // --- update total infiltration and upper zone moisture deficit - infil->F += dF; - infil->Fu += dF; - infil->Fu = MIN(infil->Fu, Fumax); - return dF / tstep; -} - -//============================================================================= - -double grnampt_getF2(double f1, double c1, double ks, double ts) -// -// Input: f1 = old infiltration volume (ft) -// c1 = head * moisture deficit (ft) -// ks = sat. hyd. conductivity (ft/sec) -// ts = time step (sec) -// Output: returns infiltration volume at end of time step (ft) -// Purpose: computes new infiltration volume over a time step -// using Green-Ampt formula for saturated upper soil zone. -// -{ - int i; - double f2 = f1; - double f2min; - double df2; - double c2; - - // --- find min. infil. volume - f2min = f1 + ks * ts; - - // --- use min. infil. volume for 0 moisture deficit - if ( c1 == 0.0 ) return f2min; - - // --- use direct form of G-A equation for small time steps - // and c1/f1 < 100 - if ( ts < 10.0 && f1 > 0.01 * c1 ) - { - f2 = f1 + ks * (1.0 + c1/f1) * ts; - return MAX(f2, f2min); - } - - // --- use Newton-Raphson method to solve integrated G-A equation - // (convergence limit reduced from that used in previous releases) - c2 = c1 * log(f1 + c1) - ks * ts; - for ( i = 1; i <= 20; i++ ) - { - df2 = (f2 - f1 - c1 * log(f2 + c1) + c2) / (1.0 - c1 / (f2 + c1) ); - if ( fabs(df2) < 0.00001 ) - { - return MAX(f2, f2min); - } - f2 -= df2; - } - return f2min; -} - -//============================================================================= - -int curvenum_setParams(TCurveNum *infil, double p[]) -// -// Input: infil = ptr. to Curve Number infiltration object -// p[] = array of parameter values -// Output: returns TRUE if parameters are valid, FALSE otherwise -// Purpose: assigns Curve Number infiltration parameters to a subcatchment. -// -{ - - // --- convert Curve Number to max. infil. capacity - if ( p[0] < 10.0 ) p[0] = 10.0; - if ( p[0] > 99.0 ) p[0] = 99.0; - infil->Smax = (1000.0 / p[0] - 10.0) / 12.0; - if ( infil->Smax < 0.0 ) return FALSE; - - // --- convert drying time (days) to a regeneration const. (1/sec) - if ( p[2] > 0.0 ) infil->regen = 1.0 / (p[2] * SECperDAY); - else return FALSE; - - // --- compute inter-event time from regeneration const. as in Green-Ampt - infil->Tmax = 0.06 / infil->regen; - - return TRUE; -} - -//============================================================================= - -void curvenum_initState(TCurveNum *infil) -// -// Input: infil = ptr. to Curve Number infiltration object -// Output: none -// Purpose: initializes state of Curve Number infiltration for a subcatchment. -// -{ - infil->S = infil->Smax; - infil->P = 0.0; - infil->F = 0.0; - infil->T = 0.0; - infil->Se = infil->Smax; - infil->f = 0.0; -} - -void curvenum_getState(TCurveNum *infil, double x[]) -{ - x[0] = infil->S; - x[1] = infil->P; - x[2] = infil->F; - x[3] = infil->T; - x[4] = infil->Se; - x[5] = infil->f; -} - -void curvenum_setState(TCurveNum *infil, double x[]) -{ - infil->S = x[0]; - infil->P = x[1]; - infil->F = x[2]; - infil->T = x[3]; - infil->Se = x[4]; - infil->f = x[5]; -} - -//============================================================================= - -double curvenum_getInfil(TCurveNum *infil, double tstep, double irate, - double depth) -// -// Input: infil = ptr. to Curve Number infiltration object -// tstep = runoff time step (sec), -// irate = rainfall rate (ft/sec); -// depth = depth of runon + ponded water (ft) -// Output: returns infiltration rate (ft/sec) -// Purpose: computes infiltration rate using the Curve Number method. -// Note: this function treats runon from other subcatchments as part -// of the ponded depth and not as an effective rainfall rate. -{ - double F1; // new cumulative infiltration (ft) - double f1 = 0.0; // new infiltration rate (ft/sec) - double fa = irate + depth/tstep; // max. available infil. rate (ft/sec) - - // --- case where there is rainfall - if ( irate > ZERO ) - { - // --- check if new rain event - if ( infil->T >= infil->Tmax ) - { - infil->P = 0.0; - infil->F = 0.0; - infil->f = 0.0; - infil->Se = infil->S; - } - infil->T = 0.0; - - // --- update cumulative precip. - infil->P += irate * tstep; - - // --- find potential new cumulative infiltration - F1 = infil->P * (1.0 - infil->P / (infil->P + infil->Se)); - - // --- compute potential infiltration rate - f1 = (F1 - infil->F) / tstep; - if ( f1 < 0.0 || infil->S <= 0.0 ) f1 = 0.0; - - } - - // --- case of no rainfall - else - { - // --- if there is ponded water then use previous infil. rate - if ( depth > MIN_TOTAL_DEPTH && infil->S > 0.0 ) - { - f1 = infil->f; - if ( f1*tstep > infil->S ) f1 = infil->S / tstep; - } - - // --- otherwise update inter-event time - else infil->T += tstep; - } - - // --- if there is some infiltration - if ( f1 > 0.0 ) - { - // --- limit infil. rate to max. available rate - f1 = MIN(f1, fa); - f1 = MAX(f1, 0.0); - - // --- update actual cumulative infiltration - infil->F += f1 * tstep; - - // --- reduce infil. capacity if a regen. constant was supplied - if ( infil->regen > 0.0 ) - { - infil->S -= f1 * tstep; - if ( infil->S < 0.0 ) infil->S = 0.0; - } - } - - // --- otherwise regenerate infil. capacity - else - { - infil->S += infil->regen * infil->Smax * tstep * Evap.recoveryFactor; - if ( infil->S > infil->Smax ) infil->S = infil->Smax; - } - infil->f = f1; - return f1; -} diff --git a/src/infil.h b/src/infil.h deleted file mode 100644 index f5ddf31aa..000000000 --- a/src/infil.h +++ /dev/null @@ -1,112 +0,0 @@ -//----------------------------------------------------------------------------- -// infil.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Public interface for infiltration functions. -// -// Update History -// ============== -// Build 5.1.010: -// - New Modified Green Ampt infiltration option added. -// Build 5.1.013: -// - New function infil_setInfilFactor() added. -// Build 5.1.015: -// - Support added for multiple infiltration methods within a project. -//----------------------------------------------------------------------------- - -#ifndef INFIL_H -#define INFIL_H - -//--------------------- -// Enumerated Constants -//--------------------- -enum InfilType { - HORTON, // Horton infiltration - MOD_HORTON, // Modified Horton infiltration - GREEN_AMPT, // Green-Ampt infiltration - MOD_GREEN_AMPT, // Modified Green-Ampt infiltration - CURVE_NUMBER}; // SCS Curve Number infiltration - -//--------------------- -// Horton Infiltration -//--------------------- -typedef struct -{ - double f0; // initial infil. rate (ft/sec) - double fmin; // minimum infil. rate (ft/sec) - double Fmax; // maximum total infiltration (ft); - double decay; // decay coeff. of infil. rate (1/sec) - double regen; // regeneration coeff. of infil. rate (1/sec) - //----------------------------- - double tp; // present time on infiltration curve (sec) - double Fe; // cumulative infiltration (ft) -} THorton; - - -//------------------------- -// Green-Ampt Infiltration -//------------------------- -typedef struct -{ - double S; // avg. capillary suction (ft) - double Ks; // saturated conductivity (ft/sec) - double IMDmax; // max. soil moisture deficit (ft/ft) - //----------------------------- - double IMD; // current initial soil moisture deficit - double F; // current cumulative infiltrated volume (ft) - double Fu; // current upper zone infiltrated volume (ft) - double Lu; // depth of upper soil zone (ft) - double T; // time until start of next rain event (sec) - char Sat; // saturation flag -} TGrnAmpt; - - -//-------------------------- -// Curve Number Infiltration -//-------------------------- -typedef struct -{ - double Smax; // max. infiltration capacity (ft) - double regen; // infil. capacity regeneration constant (1/sec) - double Tmax; // maximum inter-event time (sec) - //----------------------------- - double S; // current infiltration capacity (ft) - double F; // current cumulative infiltration (ft) - double P; // current cumulative precipitation (ft) - double T; // current inter-event time (sec) - double Se; // current event infiltration capacity (ft) - double f; // previous infiltration rate (ft/sec) - -} TCurveNum; - -//----------------------------------------------------------------------------- -// Exported Variables -//----------------------------------------------------------------------------- -extern THorton* HortInfil; -extern TGrnAmpt* GAInfil; -extern TCurveNum* CNInfil; - -//----------------------------------------------------------------------------- -// Infiltration Methods -//----------------------------------------------------------------------------- -void infil_create(int n); -void infil_delete(void); -int infil_readParams(int m, char* tok[], int ntoks); -void infil_initState(int j); -void infil_getState(int j, double x[]); -void infil_setState(int j, double x[]); -void infil_setInfilFactor(int j); -double infil_getInfil(int area, double tstep, double rainfall, double runon, - double depth); - -void grnampt_getParams(int j, double p[]); -int grnampt_setParams(TGrnAmpt *infil, double p[]); -void grnampt_initState(TGrnAmpt *infil); -double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, - double depth, int modelType); - -#endif diff --git a/src/inflow.c b/src/inflow.c deleted file mode 100644 index 7cb5e250f..000000000 --- a/src/inflow.c +++ /dev/null @@ -1,484 +0,0 @@ -//----------------------------------------------------------------------------- -// inflow.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Manages any Direct External or Dry Weather Flow inflows -// that have been assigned to nodes of the drainage system. -// -// Update History -// ============== -// Build 5.2.0: -// - Removed references to unused extIfaceInflow member of ExtInflow struct. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// External Functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// inflow_initDwfPattern (called createObjects in project.c) -// inflow_readExtInflow (called by input_readLine) -// inflow_readDwfInflow (called by input_readLine) -// inflow_deleteExtInflows (called by deleteObjects in project.c) -// inflow_deleteDwfInflows (called by deleteObjects in project.c) -// inflow_getExtInflow (called by addExternalInflows in routing.c) -// inflow_setExtInflow (called by setNodeInflow in swmm5.c) -// inflow_getDwfInflow (called by addDryWeatherInflows in routing.c) - -//----------------------------------------------------------------------------- -// Local Functions -//----------------------------------------------------------------------------- -double getPatternFactor(int p, int month, int day, int hour); - - -int inflow_readExtInflow(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error message -// Purpose: reads parameters of a direct external inflow from a line of input. -// -// Formats of data line are: -// nodeID FLOW tSeriesID (FLOW 1.0 scaleFactor baseline basePat) -// nodeID pollutID tSeriesID (CONCEN/MASS unitsFactor scaleFactor baseline basePat) -// -{ - int j; // object index - int param; // FLOW (-1) or pollutant index - int type = CONCEN_INFLOW; // FLOW, CONCEN or MASS inflow - int tseries = -1; // time series index - int basePat = -1; // baseline pattern - double cf = 1.0; // units conversion factor - double sf = 1.0; // scaling factor - double baseline = 0.0; // baseline value - - // --- find index of node receiving the inflow - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - j = project_findObject(NODE, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- find index of inflow pollutant or use -1 for FLOW - param = project_findObject(POLLUT, tok[1]); - if ( param < 0 ) - { - if ( match(tok[1], w_FLOW) ) param = -1; - else return error_setInpError(ERR_NAME, tok[1]); - } - - // --- find index of inflow time series (if supplied) in data base - if ( strlen(tok[2]) > 0 ) - { - tseries = project_findObject(TSERIES, tok[2]); - if ( tseries < 0 ) return error_setInpError(ERR_NAME, tok[2]); - Tseries[tseries].refersTo = EXTERNAL_INFLOW; - } - - // --- assign type & cf values for a FLOW inflow - if (param == -1) - { - type = FLOW_INFLOW; - cf = 1.0/UCF(FLOW); - } - - // --- do the same for a pollutant inflow - if ( ntoks >= 4 && param > -1) - { - if ( match(tok[3], w_CONCEN) ) type = CONCEN_INFLOW; - else if ( match(tok[3], w_MASS) ) type = MASS_INFLOW; - else return error_setInpError(ERR_KEYWORD, tok[3]); - if ( ntoks >= 5 && type == MASS_INFLOW ) - { - if ( ! getDouble(tok[4], &cf) ) - { - return error_setInpError(ERR_NUMBER, tok[4]); - } - if ( cf <= 0.0 ) return error_setInpError(ERR_NUMBER, tok[4]); - } - } - - // --- get sf and baseline values - if ( ntoks >= 6 ) - { - if ( ! getDouble(tok[5], &sf) ) - { - return error_setInpError(ERR_NUMBER, tok[5]); - } - } - if ( ntoks >= 7 ) - { - if ( ! getDouble(tok[6], &baseline) ) - { - return error_setInpError(ERR_NUMBER, tok[6]); - } - } - - // --- get baseline time pattern - if ( ntoks >= 8 ) - { - basePat = project_findObject(TIMEPATTERN, tok[7]); - if ( basePat < 0 ) return error_setInpError(ERR_NAME, tok[7]); - } - - // --- include LperFT3 term in conversion factor for MASS_INFLOW - if ( type == MASS_INFLOW ) cf /= LperFT3; - - return(inflow_setExtInflow(j, param, type, tseries, basePat, - cf, baseline, sf)); -} - -//============================================================================= - -int inflow_setExtInflow(int j, int param, int type, int tseries, int basePat, - double cf, double baseline, double sf) -// Purpose: This function assigns property values to the inflow object -// Inputs: j = Node index -// param = FLOW (-1) or pollutant index -// type = FLOW, CONCEN or MASS inflow -// tSeries = time series index -// basePat = baseline pattern -// cf = units conversion factor -// baseline = baseline inflow value -// sf = scaling factor -// Return: returns Error Code - -{ - TExtInflow* inflow; // external inflow object - - // --- check if an external inflow object for this constituent already exists - inflow = Node[j].extInflow; - while ( inflow ) - { - if ( inflow->param == param ) break; - inflow = inflow->next; - } - - // --- if it doesn't exist, then create it - if ( inflow == NULL ) - { - inflow = (TExtInflow *) malloc(sizeof(TExtInflow)); - if ( inflow == NULL ) - { - return error_setInpError(ERR_MEMORY, ""); - } - inflow->next = Node[j].extInflow; - Node[j].extInflow = inflow; - } - - // --- assign property values to the inflow object - inflow->param = param; - inflow->type = type; - inflow->tSeries = tseries; - inflow->cFactor = cf; - inflow->sFactor = sf; - inflow->baseline = baseline; - inflow->basePat = basePat; - return 0; -} - -//============================================================================= - -void inflow_deleteExtInflows(int j) -// -// Input: j = node index -// Output: none -// Purpose: deletes all time series inflow data for a node. -// -{ - TExtInflow* inflow1; - TExtInflow* inflow2; - inflow1 = Node[j].extInflow; - while ( inflow1 ) - { - inflow2 = inflow1->next; - free(inflow1); - inflow1 = inflow2; - } -} - -//============================================================================= - -double inflow_getExtInflow(TExtInflow* inflow, DateTime aDate) -// -// Input: inflow = external inflow data structure -// aDate = current simulation date/time -// Output: returns current value of external inflow parameter -// Purpose: retrieves the value of an external inflow at a specific -// date and time. -// -{ - int month, day, hour; - int p = inflow->basePat; // baseline pattern - int k = inflow->tSeries; // time series index - double cf = inflow->cFactor; // units conversion factor - double sf = inflow->sFactor; // scaling factor - double blv = inflow->baseline; // baseline value - double tsv = 0.0; // time series value - - if ( p >= 0 ) - { - month = datetime_monthOfYear(aDate) - 1; - day = datetime_dayOfWeek(aDate) - 1; - hour = datetime_hourOfDay(aDate); - blv *= getPatternFactor(p, month, day, hour); - } - if ( k >= 0 ) tsv = table_tseriesLookup(&Tseries[k], aDate, FALSE) * sf; - return cf * (tsv + blv); -} - -//============================================================================= - -int inflow_readDwfInflow(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error message -// Purpose: reads dry weather inflow parameters from line of input data. -// -// Format of data line is: -// nodeID FLOW/pollutID avgValue (pattern1 pattern2 ... pattern4) -// -{ - int i; - int j; // node index - int k; // pollutant index (-1 for flow) - int m; // time pattern index - int pats[4]; // time pattern index array - double x; // avg. DWF value - TDwfInflow* inflow; // dry weather flow inflow object - - // --- find index of node receiving the inflow - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - j = project_findObject(NODE, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- find index of inflow pollutant (-1 for FLOW) - k = project_findObject(POLLUT, tok[1]); - if ( k < 0 ) - { - if ( match(tok[1], w_FLOW) ) k = -1; - else return error_setInpError(ERR_NAME, tok[1]); - } - - // --- get avg. value of DWF inflow - if ( !getDouble(tok[2], &x) ) - return error_setInpError(ERR_NUMBER, tok[2]); - if ( k == -1 ) x /= UCF(FLOW); - - // --- get time patterns assigned to the inflow - for (i=0; i<4; i++) pats[i] = -1; - for (i=3; i<7; i++) - { - if ( i >= ntoks ) break; - if ( strlen(tok[i]) == 0 ) continue; - m = project_findObject(TIMEPATTERN, tok[i]); - if ( m < 0 ) return error_setInpError(ERR_NAME, tok[i]); - pats[i-3] = m; - } - - // --- check if inflow for this constituent already exists - inflow = Node[j].dwfInflow; - while ( inflow ) - { - if ( inflow->param == k ) break; - inflow = inflow->next; - } - - // --- if it doesn't exist, then create it - if ( inflow == NULL ) - { - inflow = (TDwfInflow *) malloc(sizeof(TDwfInflow)); - if ( inflow == NULL ) return error_setInpError(ERR_MEMORY, ""); - inflow->next = Node[j].dwfInflow; - Node[j].dwfInflow = inflow; - } - - // --- assign property values to the inflow object - inflow->param = k; - inflow->avgValue = x; - for (i=0; i<4; i++) inflow->patterns[i] = pats[i]; - return 0; -} - -//============================================================================= - -void inflow_deleteDwfInflows(int j) -// -// Input: j = node index -// Output: none -// Purpose: deletes all dry weather inflow data for a node. -// -{ - TDwfInflow* inflow1; - TDwfInflow* inflow2; - inflow1 = Node[j].dwfInflow; - while ( inflow1 ) - { - inflow2 = inflow1->next; - free(inflow1); - inflow1 = inflow2; - } -} - -//============================================================================= - -void inflow_initDwfInflow(TDwfInflow* inflow) -// -// Input: inflow = dry weather inflow data structure -// Output: none -// Purpose: initialzes a dry weather inflow by ordering its time patterns. -// -// This function sorts the user-supplied time patterns for a dry weather -// inflow in the order of the PatternType enumeration (monthly, daily, -// weekday hourly, weekend hourly) to help speed up pattern processing. -// -{ - int i, p; - int tmpPattern[4]; // index of each type of DWF pattern - - // --- assume no patterns were supplied - for (i=0; i<4; i++) tmpPattern[i] = -1; - - // --- assign supplied patterns to proper position (by type) in tmpPattern - for (i=0; i<4; i++) - { - p = inflow->patterns[i]; - if ( p >= 0 ) tmpPattern[Pattern[p].type] = p; - } - - // --- re-fill inflow pattern array by pattern type - for (i=0; i<4; i++) inflow->patterns[i] = tmpPattern[i]; -} - -//============================================================================= - -double inflow_getDwfInflow(TDwfInflow* inflow, int month, int day, int hour) -// -// Input: inflow = dry weather inflow data structure -// month = current month of year of simulation -// day = current day of week of simulation -// hour = current hour of day of simulation -// Output: returns value of dry weather inflow parameter -// Purpose: computes dry weather inflow value at a specific point in time. -// -{ - int p1, p2; // pattern index - double f = 1.0; // pattern factor - - p1 = inflow->patterns[MONTHLY_PATTERN]; - if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); - p1 = inflow->patterns[DAILY_PATTERN]; - if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); - p1 = inflow->patterns[HOURLY_PATTERN]; - p2 = inflow->patterns[WEEKEND_PATTERN]; - if ( p2 >= 0 ) - { - if ( day == 0 || day == 6 ) - f *= getPatternFactor(p2, month, day, hour); - else if ( p1 >= 0 ) - f *= getPatternFactor(p1, month, day, hour); - } - else if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); - return f * inflow->avgValue; - -} - -//============================================================================= - -void inflow_initDwfPattern(int j) -// -// Input: j = time pattern index -// Output: none -// Purpose: initialzes a dry weather inflow time pattern. -// -{ - int i; - for (i=0; i<24; i++) Pattern[j].factor[i] = 1.0; - Pattern[j].count = 0; - Pattern[j].type = -1; - Pattern[j].ID = NULL; -} - -//============================================================================= - -int inflow_readDwfPattern(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error message -// Purpose: reads values of a time pattern from a line of input data. -// -// Format of data line is: -// patternID patternType value(1) value(2) ... -// patternID value(n) value(n+1) .... (for continuation lines) -{ - int i, j, k, n = 1; - - // --- check for minimum number of tokens - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that pattern exists in database - j = project_findObject(TIMEPATTERN, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- check if this is first line of pattern - // (ID pointer will not have been assigned yet) - if ( Pattern[j].ID == NULL ) - { - // --- assign ID pointer & pattern type - Pattern[j].ID = project_findID(TIMEPATTERN, tok[0]); - k = findmatch(tok[1], PatternTypeWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - Pattern[j].type = k; - n = 2; - } - - // --- start reading pattern factors from rest of line - while ( ntoks > n && Pattern[j].count < 24 ) - { - i = Pattern[j].count; - if ( !getDouble(tok[n], &Pattern[j].factor[i]) ) - return error_setInpError(ERR_NUMBER, tok[n]); - Pattern[j].count++; - n++; - } - return 0; -} - -//============================================================================= - -double getPatternFactor(int p, int month, int day, int hour) -// -// Input: p = time pattern index -// month = current month of year of simulation -// day = current day of week of simulation -// hour = current hour of day of simulation -// Output: returns value of a time pattern multiplier -// Purpose: computes time pattern multiplier for a specific point in time. -{ - switch ( Pattern[p].type ) - { - case MONTHLY_PATTERN: - if ( month >= 0 && month < 12 ) return Pattern[p].factor[month]; - break; - case DAILY_PATTERN: - if ( day >= 0 && day < 7 ) return Pattern[p].factor[day]; - break; - case HOURLY_PATTERN: - if ( hour >= 0 && hour < 24 ) return Pattern[p].factor[hour]; - break; - case WEEKEND_PATTERN: - if ( day == 0 || day == 6 ) - { - if ( hour >= 0 && hour < 24 ) return Pattern[p].factor[hour]; - } - break; - } - return 1.0; -} diff --git a/src/inlet.c b/src/inlet.c deleted file mode 100644 index e9d1aaa59..000000000 --- a/src/inlet.c +++ /dev/null @@ -1,1945 +0,0 @@ -//----------------------------------------------------------------------------- -// inlet.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Street/Channel Inlet Functions -// -// Computes capture efficiency of inlets placed in Street conduits -// or Rectangular/Trapezoidal channels using FHWA HEC-22 methods (see -// Brown, S.A. et al., Urban Drainage Design Manual, Federal Highway -// Administration Hydraulic Engineering Circular No. 22, 3rd Edition, -// FHWA-NHI-10-009, August 2013). -// -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" - -// Grate inlet -typedef struct -{ - int type; // type of grate used - double length; // length (parallel to flow) (ft) - double width; // width (perpendicular to flow) (ft) - double fracOpenArea; // fraction of grate area that is open - double splashVeloc; // splash-over velocity (ft/s) -} TGrateInlet; - -// Slotted drain inlet -typedef struct -{ - double length; // length (parallel to flow) (ft) - double width; // width (perpendicular to flow) (ft) -} TSlottedInlet; - -// Curb opening inlet -typedef struct -{ - double length; // length of curb opening (ft) - double height; // height of curb opening (ft) - int throatAngle; // type of throat angle -} TCurbInlet; - -// Custom inlet -typedef struct -{ - int onGradeCurve; // flow diversion curve index - int onSagCurve; // flow rating curve index -} TCustomInlet; - -// Inlet design object -typedef struct -{ - char * ID; // name assigned to inlet design - int type; // type of inlet used (grate, curb, etc) - TGrateInlet grateInlet; // length = 0 if not used - TSlottedInlet slottedInlet; // length = 0 if not used - TCurbInlet curbInlet; // length = 0 if not used - int customCurve; // curve index = -1 if not used -} TInletDesign; - - -// Inlet performance statistics -typedef struct -{ - int flowPeriods; // # periods with approach flow - int capturePeriods; // # periods with captured flow - int backflowPeriods; // # periods with backflow - double peakFlow; // peak flow seen by inlet (cfs) - double peakFlowCapture; // capture efficiency at peak flow - double avgFlowCapture; // average capture efficiency - double bypassFreq; // frequency of bypass flow -} TInletStats; - -// Inlet list object -struct TInlet -{ - int linkIndex; // index of conduit link with the inlet - int designIndex; // index of inlet's design - int nodeIndex; // index of node receiving captured flow - int numInlets; // # inlets on each side of street or in channel - int placement; // whether inlet is on-grade or on-sag - double clogFactor; // fractional degree of inlet clogging - double flowLimit; // inlet flow restriction (cfs) - double localDepress; // local gutter depression (ft) - double localWidth; // local depression width (ft) - - double flowFactor; // flow = flowFactor * (flow spread)^2.67 - double flowCapture; // captured flow rate (cfs) - double backflow; // backflow from capture node (cfs) - double backflowRatio; // inlet backflow / capture node overflow - TInletStats stats; // inlet performance statistics - TInlet * nextInlet; // next inlet in list -}; - -// Shared inlet variables -TInletDesign * InletDesigns; // array of available inlet designs -int InletDesignCount; // number of inlet designs -int UsesInlets; // TRUE if project uses inlets - -//----------------------------------------------------------------------------- -// Enumerations -//----------------------------------------------------------------------------- - -enum InletType { - GRATE_INLET, CURB_INLET, COMBO_INLET, SLOTTED_INLET, - DROP_GRATE_INLET, DROP_CURB_INLET, CUSTOM_INLET -}; - -enum GrateType { - P50, P50x100, P30, CURVED_VANE, TILT_BAR_45, - TILT_BAR_30, RETICULINE, GENERIC -}; - -enum InletPlacementType { AUTOMATIC, ON_GRADE, ON_SAG }; - -enum ThroatAngleType { HORIZONTAL_THROAT, INCLINED_THROAT, VERTICAL_THROAT }; - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static char* InletTypeWords[] = - {"GRATE", "CURB", "", "SLOTTED", "DROP_GRATE", "DROP_CURB", "CUSTOM", NULL}; - -static char* GrateTypeWords[] = - {"P_BAR-50", "P_BAR-50x100", "P_BAR-30", "CURVED_VANE", "TILT_BAR-45", "TILT_BAR-30", - "RETICULINE", "GENERIC", NULL}; - -static char* ThroatAngleWords[] = - {"HORIZONTAL", "INCLINED", "VERTICAL", NULL}; - -static char *PlacementTypeWords[] = - {"AUTOMATIC", "ON_GRADE", "ON_SAG"}; - -// Coefficients for cubic polynomials fitted to Splash Over Velocity v. -// Grate Length curves in Chart 5B of HEC-22 manual taken from Denver -// UDFCD manual. -static const double SplashCoeffs[][4] = { - {2.22, 4.03, 0.65, 0.06}, //P_BAR-50 - {0.74, 2.44, 0.27, 0.02}, //P_BAR-50x100 - {1.76, 3.12, 0.45, 0.03}, //P_BAR-30 - {0.30, 4.85, 1.31, 0.15}, //Curved_Vane - {0.99, 2.64, 0.36, 0.03}, //Tilt_Bar-45 - {0.51, 2.34, 0.2, 0.01}, //Tilt_Bar-30 - {0.28, 2.28, 0.18, 0.01}}; //Reticuline - -// Grate opening ratios (Chart 9B of HEC-22 manual) -static const double GrateOpeningRatios[] = { - 0.90, //P_BAR-50 - 0.80, //P_BAR-50x100 - 0.60, //P_BAR-30 - 0.35, //Curved_Vane - 0.17, //Tilt_Bar-45 (assumed) - 0.34, //Tilt_Bar-30 - 0.80, //Reticuline - 1.00}; //Generic - -//----------------------------------------------------------------------------- -// Imported Variables -//----------------------------------------------------------------------------- -extern TLinkStats* LinkStats; // defined in STATS.C -extern TNodeStats* NodeStats; // defined in STATS.C - -//----------------------------------------------------------------------------- -// Local Shared Variables -//----------------------------------------------------------------------------- -// Variables as named in the HEC-22 manual. -static double Sx; // street cross slope -static double SL; // conduit longitudinal slope -static double Sw; // gutter + cross slope -static double a; // street gutter depression (ft) -static double W; // street gutter width (ft) -static double T; // top width of flow spread (ft) -static double n; // Manning's roughness coeff. - -// Additional variables -static int Nsides; // 1- or 2-sided street -static double Tcrown; // distance from street curb to crown (ft) -static double Beta; // = 1.486 * sqrt(SL) / n -static double Qfactor; // factor f in Izzard's eqn. Q = f*T^2.67 -static TXsect* xsect; // cross-section data of inlet's conduit -static double* InletFlow; // captured inlet flow received by each node -static TInlet* FirstInlet; // head of list of deployed inlets - -//----------------------------------------------------------------------------- -// External functions (declared in inlet.h) -//----------------------------------------------------------------------------- -// inlet_create called by createObjects in project.c -// inlet_delete called by deleteObjects in project.c -// inlet_readDesignParams called by parseLine in input.c -// inlet_readUsageParams called by parseLine in input.c -// inlet_validate called by project_validate -// inlet_findCapturedFlows called by routing_execute -// inlet_adjustQualInflows called by routing_execute -// inlet_adjustQualOutflows called by routing execute -// inlet_writeStatsReport called by statsrpt_writeReport -// inlet_capturedFlow called by findLinkMassFlow in qualrout.c - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int readGrateInletParams(int inletIndex, char* tok[], int ntoks); -static int readCurbInletParams(int inletIndex, char* tok[], int ntoks); -static int readSlottedInletParams(int inletIndex, char* tok[], int ntoks); -static int readCustomInletParams(int inletIndex, char* tok[], int ntoks); - -static void initInletStats(TInlet* inlet); -static void updateInletStats(TInlet* inlet, double q); -static void writeStreetStatsHeader(); -static void writeStreetStats(int link); - -static void getBackflowRatios(); -static double getInletArea(TInlet* inlet); - -static int getInletPlacement(TInlet* inlet, int node); -static void getConduitGeometry(TInlet* inlet); -static double getFlowSpread(double flow); -static double getEo(double slopeRatio, double spread, double gutterWidth); - -static double getCustomCapturedFlow(TInlet* inlet, double flow, double depth); -static double getOnGradeCapturedFlow(TInlet* inlet, double flow, double depth); -static double getOnGradeInletCapture(int inletIndex, double flow, double depth); -static double getGrateInletCapture(int inletIndex, double flow); -static double getCurbInletCapture(double flow, double length); - -static double getGutterFlowRatio(double gutterWidth); -static double getGutterAreaRatio(double grateWidth, double area); -static double getSplashOverVelocity(int grateType, double grateLength); - -static double getOnSagCapturedFlow(TInlet* inlet, double flow, double depth); -static double getOnSagInletCapture(int inletIndex, double depth); -static void findOnSagGrateFlows(int inletIndex, double depth, - double *weirFlow, double *orificeFlow); -static void findOnSagCurbFlows(int inletIndex, double depth, - double openingLength, double *weirFlow, - double *orificeFlow); -static double getCurbOrificeFlow(double flowDepth, double openingHeight, - double openingLength, int throatAngle); -static double getOnSagSlottedFlow(int inletIndex, double depth); - -//============================================================================= - -int inlet_create(int numInlets) -// -// Input: numInlets = number of inlet designs to create -// Output: none -// Purpose: creats a collection of inlet designs. -// -{ - int i; - - InletDesigns = NULL; - InletFlow = NULL; - InletDesignCount = 0; - UsesInlets = FALSE; - FirstInlet = NULL; - InletDesigns = (TInletDesign *)calloc(numInlets, sizeof(TInletDesign)); - if (InletDesigns == NULL) return ERR_MEMORY; - InletDesignCount = numInlets; - - InletFlow = (double *)calloc(Nobjects[NODE], sizeof(double)); - if (InletFlow == NULL) return ERR_MEMORY; - - for (i = 0; i < InletDesignCount; i++) - { - InletDesigns[i].customCurve = -1; - InletDesigns[i].curbInlet.length = 0.0; - InletDesigns[i].grateInlet.length = 0.0; - InletDesigns[i].slottedInlet.length = 0.0; - InletDesigns[i].type = CUSTOM_INLET; - } - return 0; -} - -//============================================================================= - -void inlet_delete() -// -// Input: none -// Output: none -// Purpose: frees all memory allocated for inlet analysis. -// -{ - TInlet* inlet = FirstInlet; - TInlet* nextInlet; - while (inlet) - { - nextInlet = inlet->nextInlet; - free(inlet); - inlet = nextInlet; - } - FirstInlet = NULL; - FREE(InletFlow); - FREE(InletDesigns); -} - -//============================================================================= - -int inlet_readDesignParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts a set of inlet design parameters from a tokenized line -// of the [INLETS] section of a SWMM input file. -// -// Format of input line is: -// ID GRATE Length Width GrateType (OpenArea) (SplashVeloc) -// ID CURB Length Height (ThroatType) -// ID SLOTTED Length Width -// ID DROP_GRATE Length Width GrateType (OpenArea) (SplashVeloc) -// ID DROP_CURB Length Height -// ID CUSTOM CurveID -// -{ - int i; - - // --- check for minimum number of tokens - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that design ID already registered in project - i = project_findObject(INLET, tok[0]); - if ( i < 0 ) return error_setInpError(ERR_NAME, tok[0]); - InletDesigns[i].ID = project_findID(INLET, tok[0]); - - // --- retrieve type of inlet design - InletDesigns[i].type = findmatch(tok[1], InletTypeWords); - - // --- read inlet's design parameters - switch (InletDesigns[i].type) - { - case GRATE_INLET: - case DROP_GRATE_INLET: - return readGrateInletParams(i, tok, ntoks); - case CURB_INLET: - case DROP_CURB_INLET: - return readCurbInletParams(i, tok, ntoks); - case SLOTTED_INLET: - return readSlottedInletParams(i, tok, ntoks); - case CUSTOM_INLET: - return readCustomInletParams(i, tok, ntoks); - default: return error_setInpError(ERR_KEYWORD, tok[1]); - } - return 0; -} -//============================================================================= - -int inlet_readUsageParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts inlet usage parameters from a tokenized line -// of the [INLET_USAGE] section of a SWMM input file. -// -// Format of input line is: -// linkID inletID nodeID (#Inlets %Clog Qmax aLocal wLocal placement) -// where -// linkID = ID name of link containing the inlet -// inletID = ID name of inlet design being used -// nodeID = ID name of node receiving captured flow -// #Inlets = number of identical inlets used (default = 1) -// %Clog = percent that inlet is clogged -// Qmax = maximum flow that inlet can capture (default = 0 (no limit)) -// aLocal = local gutter depression (ft or m) (default = 0) -// wLocal = width of local gutter depression (ft or m) (default = 0) -// placement = ON_GRADE, ON_SAG, or AUTO (the default) -// -{ - int linkIndex, designIndex, nodeIndex, numInlets = 1; - int placement = AUTOMATIC; - double flowLimit = 0.0, pctClogged = 0.0; - double aLocal = 0.0, wLocal = 0.0; - TInlet* inlet; - - // --- check that inlet's link exists - if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); - linkIndex = project_findObject(LINK, tok[0]); - if (linkIndex < 0) return error_setInpError(ERR_NAME, tok[0]); - - // --- check that inlet design type exists - designIndex = project_findObject(INLET, tok[1]); - if (designIndex < 0) return error_setInpError(ERR_NAME, tok[1]); - - // --- check that receiving node exists - nodeIndex = project_findObject(NODE, tok[2]); - if (nodeIndex < 0) return error_setInpError(ERR_NAME, tok[2]); - - // --- get number of inlets - if (ntoks > 3) - if (!getInt(tok[3], &numInlets) || numInlets < 1) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- get flow limit & percent clogged - if (ntoks > 4) - { - if (!getDouble(tok[4], &pctClogged) || pctClogged < 0.0 - || pctClogged > 99.) - return error_setInpError(ERR_NUMBER, tok[4]); - } - if (ntoks > 5) - if (!getDouble(tok[5], &flowLimit) || flowLimit < 0.0) - return error_setInpError(ERR_NUMBER, tok[5]); - - // --- get local depression parameters - if (ntoks > 6) - if (!getDouble(tok[6], &aLocal) || aLocal < 0.0) - return error_setInpError(ERR_NUMBER, tok[6]); - if (ntoks > 7) - if (!getDouble(tok[7], &wLocal) || wLocal < 0.0) - return error_setInpError(ERR_NUMBER, tok[7]); - - // --- get inlet placement - if (ntoks > 8) - { - placement = findmatch(tok[8], PlacementTypeWords); - if (placement < 0) return error_setInpError(ERR_KEYWORD, tok[8]); - } - - // --- create an inlet usage object for the link - inlet = Link[linkIndex].inlet; - if (inlet == NULL) - { - inlet = (TInlet *)malloc(sizeof(TInlet)); - if (!inlet) return error_setInpError(ERR_MEMORY, ""); - Link[linkIndex].inlet = inlet; - inlet->nextInlet = FirstInlet; - FirstInlet = inlet; - } - - // --- save inlet usage parameters - inlet->linkIndex = linkIndex; - inlet->designIndex = designIndex; - inlet->nodeIndex = nodeIndex; - inlet->numInlets = numInlets; - inlet->placement = placement; - inlet->clogFactor = 1.0 - (pctClogged / 100.); - inlet->flowLimit = flowLimit / UCF(FLOW); - inlet->localDepress = aLocal / UCF(LENGTH); - inlet->localWidth = wLocal / UCF(LENGTH); - inlet->flowFactor = 0.0; - inlet->backflowRatio = 0.0; - initInletStats(inlet); - UsesInlets = TRUE; - return 0; -} - -//============================================================================= - -void inlet_validate() -// -// Input: none -// Output: none -// Purpose: checks that inlets have been assigned to conduits with proper -// cross section shapes and counts the number of inlets that each -// node receives either bypased or captured flow from. -// -{ - int i, j, inletType, inletValid; - TInlet* inlet; - TInlet* prevInlet; - - // --- traverse the list of inlets placed in conduits - if (!UsesInlets) return; - prevInlet = FirstInlet; - inlet = FirstInlet; - while (inlet) - { - // --- check that inlet's conduit can accept the inlet's type - inletValid = FALSE; - i = inlet->linkIndex; - xsect = &Link[i].xsect; - inletType = InletDesigns[inlet->designIndex].type; - if (inletType == CUSTOM_INLET) - { - j = InletDesigns[inlet->designIndex].customCurve; - if (j >= 0) - { - if (Curve[j].curveType == DIVERSION_CURVE || - Curve[j].curveType == RATING_CURVE) - inletValid = TRUE; - } - } - else if ((xsect->type == TRAPEZOIDAL || xsect->type == RECT_OPEN) && - (inletType == DROP_GRATE_INLET || - inletType == DROP_CURB_INLET)) - inletValid = TRUE; - else if (xsect->type == STREET_XSECT && - inletType != DROP_GRATE_INLET && - inletType != DROP_CURB_INLET) - inletValid = TRUE; - - // --- if inlet placement is valid then - if (inletValid) - { - // --- record that receptor node has inlets - Node[Link[i].node2].inlet = BYPASS; - Node[inlet->nodeIndex].inlet = CAPTURE; - - // --- initialize inlet's backflow - inlet->backflow = 0.0; - - // --- compute street inlet's flow factor for Izzard's eqn. - // (used in Q = flowFactor * Spread^2.67 equation) - getConduitGeometry(inlet); - inlet->flowFactor = (0.56/n) * pow(SL,0.5) * pow(Sx,1.67); - - // --- save reference to current inlet & continue to next inlet - prevInlet = inlet; - inlet = inlet->nextInlet; - } - - // --- if inlet placement is not valid then issue a warning message - // and remove the inlet from the conduit - else - { - report_writeWarningMsg(WARN12, Link[i].ID); - if (inlet == FirstInlet) - { - FirstInlet = inlet->nextInlet; - prevInlet = FirstInlet; - free(inlet); - inlet = FirstInlet; - } - else - { - prevInlet->nextInlet = inlet->nextInlet; - free(inlet); - inlet = prevInlet->nextInlet; - } - Link[i].inlet = NULL; - } - } - - // --- determine how capture node's overflow is split between its inlets - getBackflowRatios(); -} - -//============================================================================= - -void inlet_findCapturedFlows(double tStep) -// -// Input: tStep = current flow routing time step (sec) -// Output: none -// Purpose: computes flow captured by each inlet and adjusts the -// lateral flows of the inlet's bypass and capture nodes accordingly. -// -// This function is called after regular lateral flows to all nodes have been -// set but before a flow routing step has been taken. -{ - int i, j, m, placement; - double q; - TInlet *inlet; - - // --- For non-DW routing find conduit flow into each node - // (used to limit max. amount of on-sag capture) - if (!UsesInlets) return; - memset(InletFlow, 0, Nobjects[NODE]*sizeof(double)); - if (RouteModel != DW) - { - for (j = 0; j < Nobjects[NODE]; j++) - Node[j].inflow = MAX(0., Node[j].newLatFlow); - for (i = 0; i < Nobjects[LINK]; i++) - Node[Link[i].node2].inflow += MAX(0.0, Link[i].newFlow); - } - - // --- loop through each inlet - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - // --- identify indexes of inlet's bypass (j) and capture (m) nodes - i = inlet->linkIndex; - j = Link[i].node2; - m = inlet->nodeIndex; - - // --- get inlet's placement (ON_GRADE or ON_SAG) - placement = getInletPlacement(inlet, j); - - // --- find flow captured by a Custom inlet - if (InletDesigns[inlet->designIndex].type == CUSTOM_INLET) - { - q = fabs(Link[i].newFlow); - inlet->flowCapture = getCustomCapturedFlow(inlet, q, Node[j].newDepth); - } - - // --- find flow captured by on-grade inlet - else if (placement == ON_GRADE) - { - q = fabs(Link[i].newFlow); - inlet->flowCapture = getOnGradeCapturedFlow(inlet, q, Node[j].newDepth); - } - - // --- find flow captured by on-sag inlet - else - { - q = Node[j].inflow; - inlet->flowCapture = getOnSagCapturedFlow(inlet, q, Node[j].newDepth); - } - if (fabs(inlet->flowCapture) < FUDGE) inlet->flowCapture = 0.0; - - // --- add to total flow captured by inlet's node - InletFlow[j] += inlet->flowCapture; - - // --- capture node's overflow becomes inlet's backflow - inlet->backflow = Node[m].overflow * inlet->backflowRatio; - if (fabs(inlet->backflow) < FUDGE) inlet->backflow = 0.0; - } - - // --- make second pass through each inlet - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - // --- identify indexes of inlet's bypass (j) and capture (m) nodes - i = inlet->linkIndex; - j = Link[i].node2; - m = inlet->nodeIndex; - - // --- for on-sag placement under non-DW routing, captured flow - // is limited to inlet's share of bypass node's inflow plus - // any stored volume - if (RouteModel != DW && getInletPlacement(inlet, j) == ON_SAG) - { - q = Node[j].newVolume / tStep; - q += MAX(Node[j].inflow, 0.0); - if (InletFlow[j] > q) - inlet->flowCapture *= q / InletFlow[j]; - } - - // --- adjust lateral flows at bypass and capture nodes - // (subtract captured flow from bypass node, add it to capture - // node, and add any backflow to bypass node) - Node[j].newLatFlow -= (inlet->flowCapture - inlet->backflow); - Node[m].newLatFlow += inlet->flowCapture; - - // --- update inlet's performance if reporting has begun - if (getDateTime(NewRoutingTime) > ReportStart) - updateInletStats(inlet, fabs(Link[i].newFlow)); - } -} - -//============================================================================= - -void inlet_adjustQualInflows() -// -// Input: none -// Output: none -// Purpose: adjusts accumulated flow rates and pollutant mass inflows at each -// inlet's bypass and capture nodes after a flow routing step has -// been taken prior to a quality routing step. -// -{ - int i, j, m, p; - double qNet; - TInlet* inlet; - - if (!UsesInlets) return; - if (IgnoreQuality || Nobjects[POLLUT] == 0) return; - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - // --- identify indexes of inlet's bypass (j) and capture (m) nodes - i = inlet->linkIndex; - j = Link[i].node2; - m = inlet->nodeIndex; - - // --- there's a net flow from the bypass to the capture node - qNet = inlet->flowCapture - inlet->backflow; - if (qNet > 0.0) - { - // --- add net capture flow to capture node's accumulated flow - // inflow for quality routing - Node[m].qualInflow += qNet; - - // --- and do the same for pollutant mass flows - // (Node[m].newQual is the mass inflow accumulator for node m) - for (p = 0; p < Nobjects[POLLUT]; p++) - Node[m].newQual[p] += qNet * Node[j].oldQual[p]; - } - - // --- there's a net backflow from the capture to the bypass node - else - { - // --- add the backflow flow rate and pollutant mass flow to the - // bypass node's accumulated flow and pollutant mass inflow - qNet = -qNet; - Node[j].qualInflow += qNet; - for (p = 0; p < Nobjects[POLLUT]; p++) - Node[j].newQual[p] += qNet * Node[m].oldQual[p]; - } - } -} - -//============================================================================= - -void inlet_adjustQualOutflows() -// -// Input: none -// Output: none -// Purpose: adjusts mass balance totals after a complete routing step has been -// taken so as not to treat inlet transfer flows as system outflows. -// -{ - int j, p; - double q, w; - TInlet* inlet; - - // --- these variables, declared in massbal.c, accumulate system-wide flow and - // pollutant mass fluxes over a time step to use in mass balances - extern TRoutingTotals StepFlowTotals; - extern TRoutingTotals* StepQualTotals; - - // --- examine each node - for (j = 0; j < Nobjects[NODE]; j++) - { - // --- node receives captured flow from an inlet - if (Node[j].inlet == CAPTURE) - { - // --- node also has an overflow (e.g., it's a surcharged sewer node) - q = Node[j].overflow; - if (q > 0.0) - { - // --- remove overflow from system flooding total since it does - // not leave the system (it is sent to inlet's bypass node) - StepFlowTotals.flooding -= q; - - // --- also remove pollutant overflow mass from system totals - if (!IgnoreQuality) - for (p = 0; p < Nobjects[POLLUT]; p++) - { - w = q * Node[j].newQual[p]; - StepQualTotals[p].flooding -= w; - } - } - } - } - - // --- for WQ analysis, examine each inlet's bypass node - if (!IgnoreQuality && Nobjects[POLLUT] > 0) - { - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - j = Link[inlet->linkIndex].node2; - - // --- inlet has net positive flow capture leading to - // node having a net negative lateral inflow - q = inlet->flowCapture - inlet->backflow; - if (q > 0.0 && Node[j].newLatFlow < 0.0) - - // --- remove the pollutant mass in the captured flow from - // the system totals since it does not leave the system - // (it is sent to the inlet's capture node) - for (p = 0; p < Nobjects[POLLUT]; p++) - { - w = q * Node[j].newQual[p]; - StepQualTotals[p].outflow -= w; - } - } - } -} - -//============================================================================= - -void inlet_writeStatsReport() -// -// Input: none -// Output: none -// Purpose: writes table of street & inlet flow statistics to SWMM's report file. -// -{ - int j, header = FALSE; - - if (Nobjects[STREET] == 0) return; - for (j = 0; j < Nobjects[LINK]; j++) - { - if (Link[j].xsect.type == STREET_XSECT) - { - if (!header) - { - writeStreetStatsHeader(); - header = TRUE; - } - writeStreetStats(j); - } - } - report_writeLine(""); -} - -//============================================================================= - -double inlet_capturedFlow(int i) -// -// Input: i = a link index -// Output: returns captured flow rate (cfs) -// Purpose: gets the current flow captured by an inlet. -// -{ - if (Link[i].inlet) return Link[i].inlet->flowCapture; - return 0.0; -} - -//============================================================================= - -int readGrateInletParams(int i, char* tok[], int ntoks) -{ -// -// Input: i = inlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts a grate's inlet parameters from a set of string tokens. -// - int grateType; - double width, length, areaRatio = 0.0, vSplash = 0.0; - - // --- check for enough tokens - if (ntoks < 5) return error_setInpError(ERR_ITEMS, ""); - - // --- retrieve length & width - if (!getDouble(tok[2], &length) || length <= 0.0) - return error_setInpError(ERR_NUMBER, tok[2]); - if (!getDouble(tok[3], &width) || width <= 0.0) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- retrieve grate type - grateType = findmatch(tok[4], GrateTypeWords); - if (grateType < 0) return error_setInpError(ERR_KEYWORD, tok[4]); - - // --- only read open area & splash velocity for GENERIC type grate - if (grateType == GENERIC) - { - if (ntoks < 6) return error_setInpError(ERR_ITEMS, ""); - if (!getDouble(tok[5], &areaRatio) || areaRatio <= 0.0 - || areaRatio > 1.0) return error_setInpError(ERR_NUMBER, tok[5]); - if (ntoks > 6) - { - if (!getDouble(tok[6], &vSplash) || vSplash < 0.0) - return error_setInpError(ERR_NUMBER, tok[6]); - } - } - - // --- save grate inlet parameters - InletDesigns[i].grateInlet.length = length / UCF(LENGTH); - InletDesigns[i].grateInlet.width = width / UCF(LENGTH); - InletDesigns[i].grateInlet.type = grateType; - InletDesigns[i].grateInlet.fracOpenArea = areaRatio; - InletDesigns[i].grateInlet.splashVeloc = vSplash / UCF(LENGTH); - - // --- check if grate is part of a combo inlet - if (InletDesigns[i].type == GRATE_INLET && - InletDesigns[i].curbInlet.length > 0.0) - InletDesigns[i].type = COMBO_INLET; - return 0; -} - -//============================================================================= - -int readCurbInletParams(int i, char* tok[], int ntoks) -// -// Input: i = inlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts curb opening inlet parameters from a set of string tokens. -// -{ - int throatAngle; - double height, length; - - // --- check for enough tokens - if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); - - // --- retrieve length & width of opening - if (!getDouble(tok[2], &length) || length <= 0.0) - return error_setInpError(ERR_NUMBER, tok[2]); - if (!getDouble(tok[3], &height) || height <= 0.0) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- retrieve type of throat angle for curb inlet - throatAngle = VERTICAL_THROAT; - if (InletDesigns[i].type == CURB_INLET && ntoks > 4) - { - throatAngle = findmatch(tok[4], ThroatAngleWords); - if (throatAngle < 0) return error_setInpError(ERR_KEYWORD, tok[4]); - } - - // ---- save curb opening inlet parameters - InletDesigns[i].curbInlet.length = length / UCF(LENGTH); - InletDesigns[i].curbInlet.height = height / UCF(LENGTH); - InletDesigns[i].curbInlet.throatAngle = throatAngle; - - // --- check if curb inlet is part of a combo inlet - if (InletDesigns[i].type == CURB_INLET && - InletDesigns[i].grateInlet.length > 0.0) - InletDesigns[i].type = COMBO_INLET; - return 0; -} - -//============================================================================= - -int readSlottedInletParams(int i, char* tok[], int ntoks) -// -// Input: i = inlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts slotted drain inlet parameters from a set of string tokens. -// -{ - double width, length; - - // --- check for enough tokens - if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); - - // --- retrieve length and width - if (!getDouble(tok[2], &length) || length <= 0.0) - return error_setInpError(ERR_NUMBER, tok[2]); - if (!getDouble(tok[3], &width) || width <= 0.0) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- save slotted inlet parameters - InletDesigns[i].slottedInlet.length = length / UCF(LENGTH); - InletDesigns[i].slottedInlet.width = width / UCF(LENGTH); - return 0; -} - -//============================================================================= - -int readCustomInletParams(int i, char* tok[], int ntoks) -// -// Input: i = inlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts custom inlet parameters from a set of string tokens. -// -{ - int c; // capture curve index - - if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); - else - { - c = project_findObject(CURVE, tok[2]); - if (c < 0) return error_setInpError(ERR_NAME, tok[2]); - } - InletDesigns[i].customCurve = c; - return 0; -} - -//============================================================================= - -void initInletStats(TInlet* inlet) -// -// Input: inlet = an inlet object placed in a conduit link -// Output: none -// Purpose: initializes the performance statistics of an inlet. -// -{ - if (inlet) - { - inlet->flowCapture = 0.0; - inlet->backflow = 0.0; - inlet->stats.flowPeriods = 0; - inlet->stats.capturePeriods = 0; - inlet->stats.backflowPeriods = 0; - inlet->stats.peakFlow = 0.0; - inlet->stats.peakFlowCapture = 0; - inlet->stats.avgFlowCapture = 0; - inlet->stats.bypassFreq = 0; - } -} - -//============================================================================= - -void updateInletStats(TInlet* inlet, double q) -// -// Input: inlet = an inlet object placed in a conduit link -// q = inlet's approach flow (cfs) -// Output: none -// Purpose: updates the performance statistics of an inlet. -// -{ - double qCapture = inlet->flowCapture, - qBackflow = inlet->backflow, - qNet = qCapture - qBackflow, - qBypass = q - qNet, - fCapture = 0.0; - - // --- check for no flow condition - if (q < MIN_RUNOFF_FLOW && qBackflow <= 0.0) return; - inlet->stats.flowPeriods++; - - // --- there is positive net flow from inlet to capture node - if (qNet > 0.0) - { - inlet->stats.capturePeriods++; - fCapture = qNet / q; - fCapture = MIN(fCapture, 1.0); - inlet->stats.avgFlowCapture += fCapture; - if (qBypass > MIN_RUNOFF_FLOW) inlet->stats.bypassFreq++; - } - - // --- otherwise inlet receives backflow from capture node - else inlet->stats.backflowPeriods++; - - // --- update peak flow stats - if (q > inlet->stats.peakFlow) - { - inlet->stats.peakFlow = q; - inlet->stats.peakFlowCapture = fCapture * 100.0; - } -} - -//============================================================================= - -void writeStreetStatsHeader() -// -// Input: none -// Output: none -// Purpose: writes column headers for Street Flow Summary table to SWMM's report file. -// -{ - report_writeLine(""); - report_writeLine("*******************"); - report_writeLine("Street Flow Summary"); - report_writeLine("*******************"); - report_writeLine(""); - fprintf(Frpt.file, -"\n ----------------------------------------------------------------------------------------------------------------------" -"\n Peak Maximum Maximum Peak Flow Average Bypass BackFlow" -"\n Flow Spread Depth Inlet Inlet Capture Capture Frequency Frequency"); - if (UnitSystem == US) fprintf(Frpt.file, -"\n Street Conduit %3s ft ft Design Location %% %% %% %%", - FlowUnitWords[FlowUnits]); - else fprintf(Frpt.file, -"\n Street Conduit %3s m m Design Location %% %% %% %%", - FlowUnitWords[FlowUnits]); - fprintf(Frpt.file, -"\n ----------------------------------------------------------------------------------------------------------------------"); -} - -//============================================================================= - -void writeStreetStats(int link) -// -// Input: link = index of a conduit link containing an inlet -// Output: none -// Purpose: writes flow statistics for a Street conduit and its inlet to -// SWMM's report file. -// -{ - int k, t, placement; - double maxSpread, maxDepth, maxFlow; - double fp, cp, afc = 0.0, bpf = 0.0; - TInlet* inlet; - - // --- retrieve street parameters - k = Link[link].subIndex; - t = Link[link].xsect.transect; - inlet = Link[link].inlet; - - // --- get recorded max flow and depth - maxFlow = LinkStats[link].maxFlow; - maxDepth = LinkStats[link].maxDepth; - - // --- SWMM's spread (flow width) at max depth - maxSpread = xsect_getWofY(&Link[link].xsect, maxDepth) / Street[t].sides; - maxSpread = MIN(maxSpread, Street[t].width); -/* - // HEC-22's spread based on max flow (doesn't account for backwater) - Sx = Street[t].slope; - a = Street[t].gutterDepression; - W = Street[t].gutterWidth; - n = Street[t].roughness; - Qfactor = (0.56 / n) * sqrt(Conduit[k].slope) * pow(Sx, 1.67); - maxSpread = getFlowSpread(maxFlow / Street[t].sides); - maxSpread = MIN(maxSpread, Street[t].width); -*/ - // --- write street stats - fprintf(Frpt.file, "\n %-16s", Link[link].ID); - fprintf(Frpt.file, " %9.3f", maxFlow * UCF(FLOW)); - fprintf(Frpt.file, " %9.3f", maxSpread * UCF(LENGTH)); - fprintf(Frpt.file, " %9.3f", maxDepth * UCF(LENGTH)); - - // --- write inlet stats - if (inlet) - { - fprintf(Frpt.file, " %-16s", InletDesigns[inlet->designIndex].ID); - placement = getInletPlacement(inlet, Link[inlet->linkIndex].node2); - if (placement == ON_GRADE) - fprintf(Frpt.file, " ON-GRADE"); - else - fprintf(Frpt.file, " ON-SAG "); - fp = inlet->stats.flowPeriods / 100.0; - if (fp > 0.0) - { - cp = inlet->stats.capturePeriods / 100.0; - fprintf(Frpt.file, " %9.2f", inlet->stats.peakFlowCapture); - if (cp > 0.0) - { - afc = inlet->stats.avgFlowCapture / cp; - bpf = inlet->stats.bypassFreq / cp; - } - fprintf(Frpt.file, " %9.2f", afc); - fprintf(Frpt.file, " %9.2f", bpf); - fprintf(Frpt.file, " %9.2f", inlet->stats.backflowPeriods / fp); - } - } -} - -//============================================================================= - -int getInletPlacement(TInlet* inlet, int j) -// -// Input: inlet = an inlet object placed in a conduit link -// j = index of inlet's bypass node -// Output: returns type of inlet placement -// Purpose: determines actual placement for an inlet with AUTOMATIC placement. -// -{ - if (inlet->placement == AUTOMATIC) - { - if (Node[j].degree > 0) return ON_GRADE; - else return ON_SAG; - } - else return inlet->placement; -} - -//============================================================================= - -void getConduitGeometry(TInlet* inlet) -// -// Input: inlet = an inlet object placed in a conduit link -// Output: none -// Purpose: assigns properties of an inlet's conduit to -// module-level shared variables used by other functions. -// -{ - int linkIndex = inlet->linkIndex; - int t, k = Link[linkIndex].subIndex; - - SL = Conduit[k].slope; // longitudinal slope - Beta = Conduit[k].beta; // 1.486 * sqrt(SL) / n - xsect = &Link[linkIndex].xsect; - - // --- if conduit has a Street cross section - if (xsect->type == STREET_XSECT) - { - t = xsect->transect; - Sx = Street[t].slope; // street cross slope - a = Street[t].gutterDepression; // gutter depression - W = Street[t].gutterWidth; // gutter width - n = Street[t].roughness; // street roughness - Nsides = Street[t].sides; // 1 or 2 sided street - Tcrown = Street[t].width; // distance from curb to crown - Qfactor = inlet->flowFactor; // factor used in Izzard's eqn. - - // --- add inlet's local depression to street's continuous depression - if (inlet && inlet->localDepress * inlet->localWidth > 0) - { - a += inlet->localDepress; // inlet depression - W = inlet->localWidth; // inlet depressed width - } - - // --- slope of depressed gutter section - if (W * a > 0.0) Sw = Sx + a / W; - else Sw = Sx; - } - - // --- conduit has rectangular or trapezoidal cross section - else - { - a = 0.0; - W = 0.0; - n = Conduit[k].roughness; - Nsides = 1; - Sx = 0.01; - Sw = Sx; - } -} - -//============================================================================= - -double getFlowSpread(double Q) -// -// Input: Q = conduit flow rate (cfs) -// Output: returns width of flow spread (ft) -// Purpose: computes width of flow spread across a Street cross section using -// HEC-22 equations derived from Izzard's form of the Manning eqn. -// -{ - int iter; - double f, f1, Sr, Ts1, Ts2, Tw, Qs, Eo; - - f = Qfactor; // = (0.56/n) * SL^0.5 * Sx^1.67 - - // --- no depressed curb - if (a == 0.0) - { - Ts1 = pow(Q / f, 0.375); //HEC-22 Eq(4-2) - } - else - { - // --- check if spread is within curb width - f1 = f * pow((a / W) / Sx, 1.67); - Tw = pow(Q / f1, 0.375); //HEC-22 Eq(4-2) - if (Tw <= W) Ts1 = Tw; - else - { - // --- spread extends beyond curb width - Sr = (Sx + a / W) / Sx; - iter = 1; - Ts1 = pow(Q / f, 0.375) - W; - if (Ts1 <= 0) Ts1 = Tw - W; - while (iter < 11) - { - Eo = getEo(Sr, Ts1, W); - Qs = (1.0 - Eo) * Q; //HEC-22 Eq(4-6) - Ts2 = pow(Qs / f, 0.375); //HEC-22 Eq(4-2) - if (fabs(Ts2 - Ts1) < 0.01) break; - Ts1 = Ts2; - iter++; - } - Ts1 = Ts2 + W; - } - } - return MIN(Ts1, Tcrown); -} - -//============================================================================= - -double getEo(double Sr, double Ts, double w) -// -// Input: Sr = ratio of gutter slope to street cross slope -// Ts = amount of flow spread outside of gutter width (ft) -// w = gutter width (ft) -// Output: returns ratio of gutter flow to total flow in street cross section -// Purpose: solves HEC-22 Eq. (4-4) for Eo with Ts/w substituted for -// (T/w) - 1 where Ts = T - w. -// -{ - double x; - x = Sr / (Ts / w); - x = pow((1.0 + x), 2.67) - 1.0; - x = 1.0 + Sr / x; - return 1.0 / x; -} - -//============================================================================= - -double getOnGradeCapturedFlow(TInlet* inlet, double q, double d) -// -// Input: inlet = an inlet object placed in a conduit link -// q = flow in link prior to any inlet capture (cfs) -// d = flow depth seen by inlet (ft) -// Output: returns flow captured by the inlet (cfs) -// Purpose: computes flow captured by an inlet placed on-grade. -// -// An inlet object placed in a conduit can have multiple inlets of -// the same type distributed along the conduit's length that all -// send their captured flow to the same sewer node. This function -// finds the total captured flow as each individual inlet is analyzed -// sequentially, where its approach flow has been reduced by the -// amount of flow captured by prior inlets. -{ - int i, - linkIndex; // index of link containing inlets - double qApproach, // single inlet's approach flow (cfs) - qc, // single inlet's captured flow (cfs) - qCaptured, // total flow captured by link's inlets (cfs) - qBypassed, // total flow bypassed by link's inlets (cfs) - qMax; // max. flow that a single inlet can capture (cfs) - - if (inlet->numInlets == 0) return 0.0; - linkIndex = inlet->linkIndex; - - // --- check that link has flow - qApproach = q; - if (qApproach < MIN_RUNOFF_FLOW) return 0.0; - - // --- store conduit geometry in shared variables - getConduitGeometry(inlet); - - // --- adjust flow for 2-sided street - qApproach /= Nsides; - qBypassed = qApproach; - qCaptured = 0.0; - - // --- set limit on max. flow captured per inlet - qMax = BIG; - if (inlet->flowLimit > 0.0) qMax = inlet->flowLimit; - - // --- evaluate each inlet - for (i = 1; i <= inlet->numInlets; i++) - { - qc = getOnGradeInletCapture(inlet->designIndex, qBypassed, d) * - inlet->clogFactor; - qc = MIN(qc, qMax); - qc = MIN(qc, qBypassed); - qCaptured += qc; - qBypassed -= qc; - if (qBypassed < MIN_RUNOFF_FLOW) break; - } - return qCaptured *= Nsides; -} - -//============================================================================= - -double getOnGradeInletCapture(int i, double Q, double d) -// -// Input: i = an InletDesigns index -// Q = flow rate seen by inlet (cfs) -// d = flow depth seen by inlet (ft) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by a single on-grade inlet. -// -{ - double Q1 = Q, Qc = 0.0, Lsweep = 0.0, Lcurb = 0.0, Lgrate = 0.0; - - // --- drop curb inlet (in non-Street conduit) only operates in on sag mode - if (InletDesigns[i].type == DROP_CURB_INLET) - { - Qc = getOnSagInletCapture(i, d); - return MIN(Qc, Q); - } - - // --- drop grate inlet (in non-Street conduit) - if (InletDesigns[i].type == DROP_GRATE_INLET) - { - Qc = getGrateInletCapture(i, Q); - return MIN(Qc, Q); - } - - // --- Remaining inlet types apply to Street conduits - - // --- find flow spread - T = getFlowSpread(Q); - - // --- slotted inlet (behaves as a curb opening inlet per HEC-22) - if (InletDesigns[i].type == SLOTTED_INLET) - { - Qc = getCurbInletCapture(Q, InletDesigns[i].slottedInlet.length); - return MIN(Qc, Q); - } - - Lcurb = InletDesigns[i].curbInlet.length; - Lgrate = InletDesigns[i].grateInlet.length; - - // --- curb opening inlet - if (Lcurb > 0.0) - { - Lsweep = Lcurb - Lgrate; - if (Lsweep > 0.0) - { - Qc = getCurbInletCapture(Q1, Lsweep); - Q1 -= Qc; - } - } - - // --- grate inlet - if (Lgrate > 0.0 && Q1 > 0.0) - { - if (Q1 != Q) T = getFlowSpread(Q1); - Qc += getGrateInletCapture(i, Q1); - } - return Qc; -} - -//============================================================================= - -double getGrateInletCapture(int i, double Q) -// -// Input: i = inlet type index -// Q = flow rate seen by inlet (cfs) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by an on-grade grate inlet. -// -{ - int grateType; - double Lg, // grate length (ft) - Wg, // grate width (ft) - A, // total cross section flow area (ft2) - Y, // flow depth (ft) - Eo, // ratio of gutter to total flow - V, // flow velocity (ft/s) - Vo, // splash-over velocity (ft/s) - Qo = Q, // flow over street area (cfs) - Rf = 1.0, // ratio of intercepted to total frontal flow - Rs = 0.0; // ratio of intercepted to total side flow - -// xsect, a, W, & Sx were from getConduitGeometry(). T was from getFlowSpread(). - - Lg = InletDesigns[i].grateInlet.length; - Wg = InletDesigns[i].grateInlet.width; - - // --- flow ratio for drop inlet - if (xsect->type == TRAPEZOIDAL || xsect->type == RECT_OPEN) - { - A = xsect_getAofS(xsect, Q / Beta); - Y = xsect_getYofA(xsect, A); - T = xsect_getWofY(xsect, Y); - Eo = Beta * pow(Y*Wg, 1.67) / pow(Wg + 2*Y, 0.67) / Q; - if (Wg > 0.99*xsect->yBot && xsect->type == TRAPEZOIDAL && xsect->sBot > 0.0) - { - Wg = xsect->yBot; - Sx = 1.0 / xsect->sBot; - } - } - - // --- flow ratio & area for conventional street gutter - else if (a == 0.0) - { - A = T * T * Sx / 2.0; - Eo = getGutterFlowRatio(Wg); // flow ratio based on grate width - if (T >= Tcrown) Qo = Qfactor * pow(Tcrown, 2.67); - } - - // --- flow ratio & area for composite street gutter - else - { - // --- spread confined to gutter - if (T <= W) A = T * T * Sw / 2.0; - - // --- spread beyond gutter width - else A = (T * T * Sx + a * W) / 2.0; - - // flow ratio based on gutter width corrected for grate width - Eo = getGutterFlowRatio(W); - if (Eo < 1.0) - { - if (T >= Tcrown) - Qo = Qfactor * pow(Tcrown, 2.67) / (1.0 - Eo); - Eo = Eo * getGutterAreaRatio(Wg, A); //HEC-22 Eq(4-20a) - } - } - - // --- flow and splash-over velocities - V = Qo / A; - grateType = InletDesigns[i].grateInlet.type; - if (grateType < 0 || grateType == GENERIC) - Vo = InletDesigns[i].grateInlet.splashVeloc; - else - Vo = getSplashOverVelocity(grateType, Lg); - - // --- frontal flow capture efficiency - if (V > Vo) Rf = 1.0 - 0.09 * (V - Vo); //HEC-22 Eq(4-18) - - // --- side flow capture efficiency - if (Eo < 1.0) - { - Rs = 1.0 / (1.0 + (0.15 * pow(V, 1.8) / - Sx / pow(Lg, 2.3))); //HEC-22 Eq(4-19) - } - - // --- return total flow captured - return Q * (Rf * Eo + Rs * (1.0 - Eo)); //HEC-22 Eq(4-21) -} - -//============================================================================= - -double getCurbInletCapture(double Q, double L) -// -// Input: Q = flow rate seen by inlet (cfs) -// L = length of inlet opening (ft) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by an on-sag inlet. -// -{ - double Se = Sx, // equivalent gutter slope - Lt, // length for full capture - Sr, // ratio of gutter slope to cross slope - Eo = 0.0, // ratio of gutter to total flow - E = 1.0; // capture efficiency - -// a, W, Sx, Sw, SL, & n were from getConduitGeometry(). T was from getFlowSpread(). - - // --- for depressed gutter section - if (a > 0.0) - { - Sr = Sw / Sx; - Eo = getEo(Sr, T-W, W); - Se = Sx + Sw * Eo; //HEC-22 Eq(4-24) - } - - // --- opening length for full capture - Lt = 0.6 * pow(Q, 0.42) * pow(SL, 0.3) * - pow(1.0/(n*Se), 0.6); //HEC-22 Eq(4-22a) - - // --- capture efficiency for actual opening length - if (L < Lt) - { - E = 1.0 - (L/Lt); - E = 1 - pow(E, 1.8); //HEC-22 Eq(4-23) - } - E = MIN(E, 1.0); - E = MAX(E, 0.0); - return E * Q; -} - -//============================================================================= - -double getGutterFlowRatio(double w) -// -// Input: w = gutter width (ft) -// Output: returns a flow ratio -// Purpose: computes the ratio of flow over a width of gutter to the total -// flow in a street cross section. -// -{ - if (T <= w) return 1.0; - else if (a > 0.0) - return getEo(Sw / Sx, T - w, w); - else - return 1.0 - pow((1.0 - w / T), 2.67); //HEC-22 Eq(4-16) -} - -//============================================================================= - -double getGutterAreaRatio(double Wg, double A) -// -// Input: Wg = width of grate inlet (ft) -// A = total flow area (ft2) -// Output: returns an area ratio -// Purpose: computes the ratio of the flow area above a grate to the flow -// area above depressed gutter in a street cross section. -// -{ - double As, // flow area beyond gutter width (ft2) - Ag; // flow area over grate width (ft2) - - if (Wg >= W) return 1.0; - if (T <= Wg) return 1.0; - if (T <= W) return Wg / T; - As = 0.5 * SQR((T - W)) * Sx; - Ag = Wg * ( (T * Sx) + a - (Wg * Sw / 2.) ); - return Ag / (A - As); -} - -//============================================================================= - -double getSplashOverVelocity(int grateType, double L) -// -// Input: grateType = grate inlet type code -// L = length of grate inlet (ft) -// Output: returns a splash over velocity -// Purpose: computes the splash over velocity for a standard type of grate -// inlet as a function of its length. -// -{ - return SplashCoeffs[grateType][0] + - SplashCoeffs[grateType][1] * L - - SplashCoeffs[grateType][2] * L * L + - SplashCoeffs[grateType][3] * L * L * L; -} - -//============================================================================= - -double getOnSagCapturedFlow(TInlet* inlet, double q, double d) -// -// Input: inlet = an inlet object placed in a conduit link -// q = flow in link prior to any inlet capture (cfs) -// d = flow depth seen by inlet (ft) -// Output: returns flow captured by the inlet (cfs) -// Purpose: computes flow captured by an inlet placed on-sag. -// -{ - int linkIndex, designIndex, totalInlets; - double qCaptured = 0.0, qMax = HUGE; - - if (inlet->numInlets == 0) return 0.0; - totalInlets = Nsides * inlet->numInlets; - linkIndex = inlet->linkIndex; - designIndex = inlet->designIndex; - - // --- store conduit geometry in shared variables - getConduitGeometry(inlet); - - // --- set flow limit per inlet - if (inlet->flowLimit > 0.0) - qMax = inlet->flowLimit; - - // --- find nominal flow captured by inlet - qCaptured = getOnSagInletCapture(designIndex, fabs(d)); - - // --- find actual flow captured by the inlet - qCaptured *= inlet->clogFactor; - qCaptured = MIN(qCaptured, qMax); - qCaptured *= (double)totalInlets; - return qCaptured; -} - -//============================================================================= - -double getOnSagInletCapture(int i, double d) -// -// Input: i = inlet type index -// d = water level seen by inlet (ft) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by an on-sag inlet. -// -{ - double Lsweep = 0.0, Lcurb = 0.0, Lgrate = 0.0; - double Qsw = 0.0, //Sweeper curb opening weir flow - Qso = 0.0, //Sweeper curb opening orifice flow - Qgw = 0.0, //Grate weir flow - Qgo = 0.0, //Grate orifice flow - Qcw = 0.0, //Curb opening weir flow - Qco = 0.0; //Curb opening orifice flow - - if (InletDesigns[i].slottedInlet.length > 0.0) - return getOnSagSlottedFlow(i, d); - - Lgrate = InletDesigns[i].grateInlet.length; - if (Lgrate > 0.0) findOnSagGrateFlows(i, d, &Qgw, &Qgo); - - Lcurb = InletDesigns[i].curbInlet.length; - if (Lcurb > 0.0) - { - Lsweep = Lcurb - Lgrate; - if (Lsweep > 0.0) findOnSagCurbFlows(i, d, Lsweep, &Qsw, &Qso); - if (Qgo > 0.0) findOnSagCurbFlows(i, d, Lgrate, &Qcw, &Qco); - } - return Qgw + Qgo + Qsw + Qso + Qco; -} - -//============================================================================= - -void findOnSagGrateFlows(int i, double d, double *Qw, double *Qo) -// -// Input: i = inlet type index -// d = water level seen by inlet (ft) -// Output: Qw = flow captured in weir mode (cfs) -// Qo = flow captured in orifice mode (cfs) -// Purpose: finds the flow captured by an on-sag grate inlet. -// -{ - int grateType = InletDesigns[i].grateInlet.type; - double Lg = InletDesigns[i].grateInlet.length; - double Wg = InletDesigns[i].grateInlet.width; - double P, // grate perimeter (ft) - Ao, // grate opening area (ft2) - di; // average flow depth across grate (ft) - - // --- for drop grate inlets - if (InletDesigns[i].type == DROP_GRATE_INLET) - { - di = d; - P = 2.0 * (Lg + Wg); - } - - // --- for gutter grate inlets: - else - { - // --- check for spread within grate width - if (d <= Wg * Sw) - Wg = d / Sw; - - // --- avergage depth over grate - di = d - (Wg / 2.0) * Sw; - - // --- effective grate perimeter - P = Lg + 2.0 * Wg; - } - - if (grateType == GENERIC) - Ao = Lg * Wg * InletDesigns[i].grateInlet.fracOpenArea; - else - Ao = Lg * Wg * GrateOpeningRatios[grateType]; - - // --- weir flow applies (based on depth where result of - // weir eqn. equals result of orifice eqn.) - - if (d <= 1.79 * Ao / P) - { - *Qw = 3.0 * P * pow(di, 1.5); //HEC-22 Eq(4-26) - } - - // --- orifice flow applies - else - { - *Qo = 0.67 * Ao * sqrt(2.0 * 32.16 * di); //HEC-22 Eq(4-27) - } -} - -//============================================================================= - -void findOnSagCurbFlows(int i, double d, double L, double *Qw, double *Qo) -// -// Input: i = inlet type index -// d = water level seen by inlet (ft) -// L = length of curb opening (ft) -// Output: Qw = flow captured in weir mode (cfs) -// Qo = flow captured in orifice mode (cfs) -// Purpose: finds the flow captured by an on-sag curb opening inlet. -// -{ - int throatAngle = InletDesigns[i].curbInlet.throatAngle; - double h = InletDesigns[i].curbInlet.height; - double Qweir, Qorif, P; - double dweir, dorif, r; - - // --- check for orifice flow - if (L <= 0.0) return; - if (InletDesigns[i].type == DROP_CURB_INLET) L = L * 4.0; - dorif = 1.4 * h; - if (d > dorif) - { - *Qo = getCurbOrificeFlow(d, h, L, throatAngle); - return; - } - - // --- for uniform cross slope or very long opening - if (a == 0.0 || L > 12.0) - { - // --- check for weir flow - dweir = h; - if (d < dweir) - { - *Qw = 3.0 * L * pow(d, 1.5); //HEC-22 Eq(4-30) - return; - } - else Qweir = 3.0 * L * pow(dweir, 1.5); - } - - // --- for depressed gutter - else - { - // --- check for weir flow - P = L + 1.8 * W; - dweir = h + a; - if (d < dweir) - { - *Qw = 2.3 * P * pow(d, 1.5); //HEC-22 Eq(4-28) - return; - } - else Qweir = 2.3 * P * pow(dweir, 1.5); - } - - // --- interpolate between Qweir at depth dweir and Qorif at depth dorif - Qorif = getCurbOrificeFlow(dorif, h, L, throatAngle); - r = (d - dweir) / (dorif - dweir); - *Qw = (1.0 -r) * Qweir; - *Qo = r * Qorif; -} - -//============================================================================= - -double getCurbOrificeFlow(double di, double h, double L, int throatAngle) -// -// Input: di = water level at lip of inlet opening (ft) -// h = height of curb opening (ft) -// L = length of curb opening (ft) -// throatAngle = type of throat angle in curb opening -// Output: return flow captured by inlet (cfs) -// Purpose: finds the flow captured by an on-sag curb opening inlet under -// orifice flow conditions. -// -{ - double d = di; - if (throatAngle == HORIZONTAL_THROAT) - d = di - h / 2.0; - else if (throatAngle == INCLINED_THROAT) - d = di + (h / 2.0) * 0.7071; - return 0.67 * h * L * sqrt(2.0 * 32.16 * d); //HEC-22 Eq(4-31a) -} - -//============================================================================= - -double getOnSagSlottedFlow(int i, double d) -// -// Input: i = inlet type index -// d = water level seen by inlet (ft) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by an on-sag slotted inlet. -// -// Note: weir flow = orifice flow at d = 2.587 * inlet width -{ - double L = InletDesigns[i].slottedInlet.length; - double w = InletDesigns[i].slottedInlet.width; - - if (d <= 2.587 * w) - return 2.48 * L * pow(d, 1.5); //HEC-22 Eq(4-32) - else - return 0.8 * L * w * sqrt(64.32 * d); //HEC-22 Eq(4-33) -} - -//============================================================================= - -void getBackflowRatios() -// -// Input: none -// Output: overflow ratio for each inlet -// Purpose: finds the fraction of the overflow produced by an inlet's capture -// node that becomes backflow into the inlet. -// -// Note: when a capture node receives flow from two or more inlets -// its backflow is divided among the inlets based on: -// i) the fraction of total open area for standard inlets -// ii) the fraction of total number of inlets for custom inlets -{ - TInlet* inlet; - double area; - double f; - int n; - - // --- info for each node receiving flow from an inlet - typedef struct - { - int numInletLinks; // total # inlet links - int numStdInletLinks; // total # standard inlet links - int numCustomInlets; // # custom inlets - double totalInletArea; // open area of standard inlets - } TInletNode; - TInletNode* inletNodes = (TInletNode *) calloc(Nobjects[NODE], sizeof(TInletNode)); - if (inletNodes == NULL) return; - - // --- Finds each inlet's contribution to its capture node - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - n = inlet->nodeIndex; - inletNodes[n].numInletLinks++; - area = getInletArea(inlet); - if (area > 0.0) - { - inletNodes[n].numStdInletLinks++; - inletNodes[n].totalInletArea += area; - } - else - inletNodes[n].numCustomInlets += inlet->numInlets; - } - - // --- find fraction of capture node's overflow that becomes inlet backflow - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - // --- f is ratio of links with standard inlets to all inlet links - // connected to receptor node n - n = inlet->nodeIndex; - f = (double) inletNodes[n].numStdInletLinks / - (double) inletNodes[n].numInletLinks; - - // --- backflow ratio depends if inlet is standard or custom (area = 0) - area = getInletArea(inlet); - if (area == 0.0) - inlet->backflowRatio = (double)inlet->numInlets / - (double)inletNodes[n].numCustomInlets * (1. - f); - else - inlet->backflowRatio = area / inletNodes[n].totalInletArea * f; - } - free(inletNodes); -} - -//============================================================================= - -double getInletArea(TInlet* inlet) -// -// Input: inlet = an inlet object placed in a conduit link -// Output: returns the unclogged open area of the inlet (ft2) -// Purpose: finds the total open flow area inlets placed in a conduit. -// -{ - double area = 0.0; - double curbLength; - int i = inlet->designIndex; - int grateType = InletDesigns[i].grateInlet.type; - - if (InletDesigns[i].grateInlet.length > 0.0) - { - area = InletDesigns[i].grateInlet.length * InletDesigns[i].grateInlet.width; - if (grateType == GENERIC) - area *= InletDesigns[i].grateInlet.fracOpenArea; - else - area *= GrateOpeningRatios[grateType]; - } - - curbLength = InletDesigns[i].curbInlet.length - InletDesigns[i].grateInlet.length; - if (curbLength > 0.0) - area += curbLength * InletDesigns[i].curbInlet.height; - - if (InletDesigns[i].slottedInlet.length > 0.0) - area = InletDesigns[i].slottedInlet.length * InletDesigns[i].slottedInlet.width; - return area * inlet->numInlets * inlet->clogFactor; -} - -//============================================================================= - -double getCustomCapturedFlow(TInlet* inlet, double q, double d) -{ - int i = inlet->designIndex; // inlet's position in InletDesigns array - int j; // counter for replicate inlets - int sides = 1; // number of sides for inlet's street (1 or 2) - int c; // an index into the Curve array - double qApproach, // inlet's approach flow (cfs) - qBypassed, // inlet's bypassed flow (cfs) - qCaptured, // inlet's captured flow (cfs) - qIncrement, // increment to captured flow (cfs) - qMax = HUGE; // user-supplied flow capture limit (cfs) - - if (inlet->numInlets == 0) return 0.0; - - // --- set limit on max. flow captured per inlet - qMax = BIG; - if (inlet->flowLimit > 0.0) qMax = inlet->flowLimit; - - // --- get number of sides to a street xsection - xsect = &Link[inlet->linkIndex].xsect; - if (xsect->type == STREET_XSECT) - sides = Street[xsect->transect].sides; - - // --- adjust flow for 2-sided street - qApproach = q / sides; - qBypassed = qApproach; - qCaptured = 0.0; - - // --- get index of inlet's capture curve - c = InletDesigns[i].customCurve; - if (c >= 0) - { - // --- curve is captured flow v. approach flow - if (Curve[c].curveType == DIVERSION_CURVE) - { - // --- add up incrmental capture of each replicate inlet - for (j = 1; j <= inlet->numInlets; j++) - { - qIncrement = inlet->clogFactor * - table_lookupEx(&Curve[c], qBypassed * UCF(FLOW)) / UCF(FLOW); - qIncrement = MIN(qIncrement, qMax); - qIncrement = MIN(qIncrement, qBypassed); - qCaptured += qIncrement; - qBypassed -= qIncrement; - if (qBypassed < MIN_RUNOFF_FLOW) break; - } - } - - // --- curve is captured flow v. downstream node depth - else if (Curve[c].curveType == RATING_CURVE) - { - qCaptured = inlet->numInlets * inlet->clogFactor * - table_lookupEx(&Curve[c], d * UCF(LENGTH)) / UCF(FLOW); - } - qCaptured *= sides; - } - return qCaptured; -} diff --git a/src/inlet.h b/src/inlet.h deleted file mode 100644 index ca7830725..000000000 --- a/src/inlet.h +++ /dev/null @@ -1,30 +0,0 @@ -//----------------------------------------------------------------------------- -// inlet.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Street/Channel Inlet Functions -// -//----------------------------------------------------------------------------- -#ifndef INLET_H -#define INLET_H - -typedef struct TInlet TInlet; - -int inlet_create(int nInlets); -void inlet_delete(); -int inlet_readDesignParams(char* tok[], int ntoks); -int inlet_readUsageParams(char* tok[], int ntoks); -void inlet_validate(); - -void inlet_findCapturedFlows(double tStep); -void inlet_adjustQualInflows(); -void inlet_adjustQualOutflows(); - -void inlet_writeStatsReport(); -double inlet_capturedFlow(int link); - -#endif diff --git a/src/input.c b/src/input.c deleted file mode 100644 index 3613297b9..000000000 --- a/src/input.c +++ /dev/null @@ -1,928 +0,0 @@ -//----------------------------------------------------------------------------- -// input.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Input data processing functions. -// -// Update History -// ============== -// Build 5.1.007: -// - Support added for climate adjustment input data. -// Build 5.1.011: -// - Support added for reading hydraulic event dates. -// Build 5.1.015: -// - Support added for multiple infiltration methods within a project. -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for named variables & math expressions in control rules. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" -#include "lid.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const int MAXERRS = 100; // Max. input errors reported - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -static char *Tok[MAXTOKS]; // String tokens from line of input -static int Ntokens; // Number of tokens in line of input -static int Mobjects[MAX_OBJ_TYPES]; // Working number of objects of each type -static int Mnodes[MAX_NODE_TYPES]; // Working number of node objects -static int Mlinks[MAX_LINK_TYPES]; // Working number of link objects -static int Mevents; // Working number of event periods - -//----------------------------------------------------------------------------- -// External Functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// input_countObjects (called by swmm_open in swmm5.c) -// input_readData (called by swmm_open in swmm5.c) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int addObject(int objType, char* id); -static int getTokens(char *s); -static int parseLine(int sect, char* line); -static int readOption(char* line); -static int readTitle(char* line); -static int readControl(char* tok[], int ntoks); -static int readNode(int type); -static int readLink(int type); -static int readEvent(char* tok[], int ntoks); - -//============================================================================= - -int input_countObjects() -// -// Input: none -// Output: returns error code -// Purpose: reads input file to determine number of system objects. -// -{ - char line[MAXLINE+1]; // line from input data file - char wLine[MAXLINE+1]; // working copy of input line - char *tok; // first string token of line - int sect = -1, newsect; // input data sections - int errcode = 0; // error code - int errsum = 0; // number of errors found - int i; - long lineCount = 0; - - // --- initialize number of objects & set default values - if ( ErrorCode ) return ErrorCode; - error_setInpError(0, ""); - for (i = 0; i < MAX_OBJ_TYPES; i++) Nobjects[i] = 0; - for (i = 0; i < MAX_NODE_TYPES; i++) Nnodes[i] = 0; - for (i = 0; i < MAX_LINK_TYPES; i++) Nlinks[i] = 0; - controls_init(); - - // --- make pass through data file counting number of each object - while ( fgets(line, MAXLINE, Finp.file) != NULL ) - { - // --- skip blank lines & those beginning with a comment - lineCount++; - sstrncpy(wLine, line, MAXLINE); // make working copy of line - tok = strtok(wLine, SEPSTR); // get first text token on line - if ( tok == NULL ) continue; - if ( *tok == ';' ) continue; - - // --- check if line begins with a new section heading - if ( *tok == '[' ) - { - // --- look for heading in list of section keywords - newsect = findmatch(tok, SectWords); - if ( newsect >= 0 ) - { - sect = newsect; - continue; - } - else - { - sect = -1; - errcode = ERR_KEYWORD; - } - } - - // --- if in OPTIONS section then read the option setting - // otherwise add object and its ID name (tok) to project - if ( sect == s_OPTION ) errcode = readOption(line); - else if ( sect >= 0 ) errcode = addObject(sect, tok); - - // --- report any error found - if ( errcode ) - { - report_writeInputErrorMsg(errcode, sect, line, lineCount); - errsum++; - if (errsum >= MAXERRS ) break; - } - } - - // --- set global error code if input errors were found - if ( errsum > 0 ) ErrorCode = ERR_INPUT; - return ErrorCode; -} - -//============================================================================= - -int input_readData() -// -// Input: none -// Output: returns error code -// Purpose: reads input file to determine input parameters for each object. -// -{ - char line[MAXLINE+1]; // line from input data file - char wLine[MAXLINE+1]; // working copy of input line - char* comment; // ptr. to start of comment in input line - int sect, newsect; // data sections - int inperr, errsum; // error code & total error count - int lineLength; // number of characters in input line - int i; - long lineCount = 0; - - // --- initialize working item count arrays - // (final counts in Mobjects, Mnodes & Mlinks should - // match those in Nobjects, Nnodes and Nlinks). - if ( ErrorCode ) return ErrorCode; - error_setInpError(0, ""); - for (i = 0; i < MAX_OBJ_TYPES; i++) Mobjects[i] = 0; - for (i = 0; i < MAX_NODE_TYPES; i++) Mnodes[i] = 0; - for (i = 0; i < MAX_LINK_TYPES; i++) Mlinks[i] = 0; - Mevents = 0; - - // --- initialize starting date for all time series - for ( i = 0; i < Nobjects[TSERIES]; i++ ) - { - Tseries[i].lastDate = StartDate + StartTime; - } - - // --- read each line from input file - sect = 0; - errsum = 0; - rewind(Finp.file); - while ( fgets(line, MAXLINE, Finp.file) != NULL ) - { - // --- make copy of line and scan for tokens - lineCount++; - sstrncpy(wLine, line, MAXLINE); - Ntokens = getTokens(wLine); - - // --- skip blank lines and comments - if ( Ntokens == 0 ) continue; - if ( *Tok[0] == ';' ) continue; - - // --- check if max. line length exceeded - lineLength = (int)strlen(line); - if ( lineLength >= MAXLINE ) - { - // --- don't count comment if present - comment = strchr(line, ';'); - if ( comment ) lineLength = (int)(comment - line); // Pointer math here - if ( lineLength >= MAXLINE ) - { - inperr = ERR_LINE_LENGTH; - report_writeInputErrorMsg(inperr, sect, line, lineCount); - errsum++; - } - } - - // --- check if at start of a new input section - if (*Tok[0] == '[') - { - // --- match token against list of section keywords - newsect = findmatch(Tok[0], SectWords); - if (newsect >= 0) - { - // --- SPECIAL CASE FOR TRANSECTS - // finish processing the last set of transect data - if ( sect == s_TRANSECT ) - transect_validate(Nobjects[TRANSECT]-1); - - // --- begin a new input section - sect = newsect; - continue; - } - else - { - inperr = error_setInpError(ERR_KEYWORD, Tok[0]); - report_writeInputErrorMsg(inperr, sect, line, lineCount); - errsum++; - break; - } - } - - // --- otherwise parse tokens from input line - else - { - inperr = parseLine(sect, line); - if ( inperr > 0 ) - { - errsum++; - if ( errsum > MAXERRS ) report_writeLine(FMT19); - else report_writeInputErrorMsg(inperr, sect, line, lineCount); - } - } - - // --- stop if reach end of file or max. error count - if (errsum > MAXERRS) break; - } /* End of while */ - - // --- check for errors - if (errsum > 0) ErrorCode = ERR_INPUT; - return ErrorCode; -} - -//============================================================================= - -int addObject(int objType, char* id) -// -// Input: objType = object type index -// id = object's ID string -// Output: returns an error code -// Purpose: adds a new object to the project. -// -{ - int errcode = 0; - switch( objType ) - { - case s_RAINGAGE: - if ( !project_addObject(GAGE, id, Nobjects[GAGE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[GAGE]++; - break; - - case s_SUBCATCH: - if ( !project_addObject(SUBCATCH, id, Nobjects[SUBCATCH]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[SUBCATCH]++; - break; - - case s_AQUIFER: - if ( !project_addObject(AQUIFER, id, Nobjects[AQUIFER]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[AQUIFER]++; - break; - - case s_UNITHYD: - // --- the same Unit Hydrograph can span several lines - if ( project_findObject(UNITHYD, id) < 0 ) - { - if ( !project_addObject(UNITHYD, id, Nobjects[UNITHYD]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[UNITHYD]++; - } - break; - - case s_SNOWMELT: - // --- the same Snowmelt object can appear on several lines - if ( project_findObject(SNOWMELT, id) < 0 ) - { - if ( !project_addObject(SNOWMELT, id, Nobjects[SNOWMELT]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[SNOWMELT]++; - } - break; - - case s_JUNCTION: - if ( !project_addObject(NODE, id, Nobjects[NODE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[NODE]++; - Nnodes[JUNCTION]++; - break; - - case s_OUTFALL: - if ( !project_addObject(NODE, id, Nobjects[NODE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[NODE]++; - Nnodes[OUTFALL]++; - break; - - case s_STORAGE: - if ( !project_addObject(NODE, id, Nobjects[NODE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[NODE]++; - Nnodes[STORAGE]++; - break; - - case s_DIVIDER: - if ( !project_addObject(NODE, id, Nobjects[NODE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[NODE]++; - Nnodes[DIVIDER]++; - break; - - case s_CONDUIT: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[CONDUIT]++; - break; - - case s_PUMP: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[PUMP]++; - break; - - case s_ORIFICE: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[ORIFICE]++; - break; - - case s_WEIR: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[WEIR]++; - break; - - case s_OUTLET: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[OUTLET]++; - break; - - case s_POLLUTANT: - if ( !project_addObject(POLLUT, id, Nobjects[POLLUT]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[POLLUT]++; - break; - - case s_LANDUSE: - if ( !project_addObject(LANDUSE, id, Nobjects[LANDUSE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LANDUSE]++; - break; - - case s_PATTERN: - // --- a time pattern can span several lines - if ( project_findObject(TIMEPATTERN, id) < 0 ) - { - if ( !project_addObject(TIMEPATTERN, id, Nobjects[TIMEPATTERN]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[TIMEPATTERN]++; - } - break; - - case s_CURVE: - // --- a Curve can span several lines - if ( project_findObject(CURVE, id) < 0 ) - { - if ( !project_addObject(CURVE, id, Nobjects[CURVE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[CURVE]++; - - // --- check for a conduit shape curve - id = strtok(NULL, SEPSTR); - if ( findmatch(id, CurveTypeWords) == SHAPE_CURVE ) - Nobjects[SHAPE]++; - } - break; - - case s_TIMESERIES: - // --- a Time Series can span several lines - if ( project_findObject(TSERIES, id) < 0 ) - { - if ( !project_addObject(TSERIES, id, Nobjects[TSERIES]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[TSERIES]++; - } - break; - - case s_CONTROL: - if ( match(id, w_RULE) ) Nobjects[CONTROL]++; - else controls_addToCount(id); - break; - - case s_TRANSECT: - // --- for TRANSECTS, ID name appears as second entry on X1 line - if ( match(id, "X1") ) - { - id = strtok(NULL, SEPSTR); - if ( id ) - { - if ( !project_addObject(TRANSECT, id, Nobjects[TRANSECT]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[TRANSECT]++; - } - } - break; - - case s_LID_CONTROL: - // --- an LID object can span several lines - if ( project_findObject(LID, id) < 0 ) - { - if ( !project_addObject(LID, id, Nobjects[LID]) ) - { - errcode = error_setInpError(ERR_DUP_NAME, id); - } - Nobjects[LID]++; - } - break; - - case s_EVENT: NumEvents++; break; - - case s_STREET: - if ( !project_addObject(STREET, id, Nobjects[STREET]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[STREET]++; - break; - - case s_INLET: - // --- an INLET object can span several lines - if (project_findObject(INLET, id) < 0) - { - if ( !project_addObject(INLET, id, Nobjects[INLET]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[INLET]++; - } - break; - } - return errcode; -} - -//============================================================================= - -int parseLine(int sect, char *line) -// -// Input: sect = current section of input file -// *line = line of text read from input file -// Output: returns error code or 0 if no error found -// Purpose: parses contents of a tokenized line of text read from input file. -// -{ - int j, err; - switch (sect) - { - case s_TITLE: - return readTitle(line); - - case s_RAINGAGE: - j = Mobjects[GAGE]; - err = gage_readParams(j, Tok, Ntokens); - Mobjects[GAGE]++; - return err; - - case s_TEMP: - return climate_readParams(Tok, Ntokens); - - case s_EVAP: - return climate_readEvapParams(Tok, Ntokens); - - case s_ADJUST: - return climate_readAdjustments(Tok, Ntokens); - - case s_SUBCATCH: - j = Mobjects[SUBCATCH]; - err = subcatch_readParams(j, Tok, Ntokens); - Mobjects[SUBCATCH]++; - return err; - - case s_SUBAREA: - return subcatch_readSubareaParams(Tok, Ntokens); - - case s_INFIL: - return infil_readParams(InfilModel, Tok, Ntokens); - - case s_AQUIFER: - j = Mobjects[AQUIFER]; - err = gwater_readAquiferParams(j, Tok, Ntokens); - Mobjects[AQUIFER]++; - return err; - - case s_GROUNDWATER: - return gwater_readGroundwaterParams(Tok, Ntokens); - - case s_GWF: - return gwater_readFlowExpression(Tok, Ntokens); - - case s_SNOWMELT: - return snow_readMeltParams(Tok, Ntokens); - - case s_JUNCTION: - return readNode(JUNCTION); - - case s_OUTFALL: - return readNode(OUTFALL); - - case s_STORAGE: - return readNode(STORAGE); - - case s_DIVIDER: - return readNode(DIVIDER); - - case s_CONDUIT: - return readLink(CONDUIT); - - case s_PUMP: - return readLink(PUMP); - - case s_ORIFICE: - return readLink(ORIFICE); - - case s_WEIR: - return readLink(WEIR); - - case s_OUTLET: - return readLink(OUTLET); - - case s_XSECTION: - return link_readXsectParams(Tok, Ntokens); - - case s_TRANSECT: - return transect_readParams(&Mobjects[TRANSECT], Tok, Ntokens); - - case s_LOSSES: - return link_readLossParams(Tok, Ntokens); - - case s_POLLUTANT: - j = Mobjects[POLLUT]; - err = landuse_readPollutParams(j, Tok, Ntokens); - Mobjects[POLLUT]++; - return err; - - case s_LANDUSE: - j = Mobjects[LANDUSE]; - err = landuse_readParams(j, Tok, Ntokens); - Mobjects[LANDUSE]++; - return err; - - case s_BUILDUP: - return landuse_readBuildupParams(Tok, Ntokens); - - case s_WASHOFF: - return landuse_readWashoffParams(Tok, Ntokens); - - case s_COVERAGE: - return subcatch_readLanduseParams(Tok, Ntokens); - - case s_INFLOW: - return inflow_readExtInflow(Tok, Ntokens); - - case s_DWF: - return inflow_readDwfInflow(Tok, Ntokens); - - case s_PATTERN: - return inflow_readDwfPattern(Tok, Ntokens); - - case s_RDII: - return rdii_readRdiiInflow(Tok, Ntokens); - - case s_UNITHYD: - return rdii_readUnitHydParams(Tok, Ntokens); - - case s_LOADING: - return subcatch_readInitBuildup(Tok, Ntokens); - - case s_TREATMENT: - return treatmnt_readExpression(Tok, Ntokens); - - case s_CURVE: - return table_readCurve(Tok, Ntokens); - - case s_TIMESERIES: - return table_readTimeseries(Tok, Ntokens); - - case s_CONTROL: - return readControl(Tok, Ntokens); - - case s_REPORT: - return report_readOptions(Tok, Ntokens); - - case s_FILE: - return iface_readFileParams(Tok, Ntokens); - - case s_LID_CONTROL: - return lid_readProcParams(Tok, Ntokens); - - case s_LID_USAGE: - return lid_readGroupParams(Tok, Ntokens); - - case s_EVENT: - return readEvent(Tok, Ntokens); - - case s_STREET: - return street_readParams(Tok, Ntokens); - - case s_INLET: - return inlet_readDesignParams(Tok, Ntokens); - - case s_INLET_USAGE: - return inlet_readUsageParams(Tok, Ntokens); - - default: return 0; - } -} - -//============================================================================= - -int readControl(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// Purpose: reads a line of input for a control rule. -// -{ - int index; - int keyword; - - // --- check for minimum number of tokens - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - - if (match(tok[0], w_VARIABLE)) - return controls_addVariable(tok, ntoks); - if (match(tok[0], w_EXPRESSION)) - return controls_addExpression(tok, ntoks); - - // --- get index of control rule keyword - keyword = findmatch(tok[0], RuleKeyWords); - if ( keyword < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); - - // --- if line begins a new control rule, add rule ID to the database - if ( keyword == 0 ) - { - if ( !project_addObject(CONTROL, tok[1], Mobjects[CONTROL]) ) - { - return error_setInpError(ERR_DUP_NAME, Tok[1]); - } - Mobjects[CONTROL]++; - } - - // --- get index of last control rule processed - index = Mobjects[CONTROL] - 1; - if ( index < 0 ) return error_setInpError(ERR_RULE, ""); - - // --- add current line as a new clause to the control rule - return controls_addRuleClause(index, keyword, Tok, Ntokens); -} - -//============================================================================= - -int readOption(char* line) -// -// Input: line = line of input data -// Output: returns error code -// Purpose: reads an input line containing a project option. -// -{ - Ntokens = getTokens(line); - if ( Ntokens < 2 ) return 0; - return project_readOption(Tok[0], Tok[1]); -} - -//============================================================================= - -int readTitle(char* line) -// -// Input: line = line from input file -// Output: returns error code -// Purpose: reads project title from line of input. -// -{ - int i, n; - for (i = 0; i < MAXTITLE; i++) - { - // --- find next empty Title entry - if ( strlen(Title[i]) == 0 ) - { - // --- strip line feed character from input line - n = (int)strlen(line); - if (line[n-1] == 10) line[n-1] = ' '; - - // --- copy input line into Title entry - sstrncpy(Title[i], line, MAXMSG); - break; - } - } - return 0; -} - -//============================================================================= - -int readNode(int type) -// -// Input: type = type of node -// Output: returns error code -// Purpose: reads data for a node from a line of input. -// -{ - int j = Mobjects[NODE]; - int k = Mnodes[type]; - int err = node_readParams(j, type, k, Tok, Ntokens); - Mobjects[NODE]++; - Mnodes[type]++; - return err; -} - -//============================================================================= - -int readLink(int type) -// -// Input: type = type of link -// Output: returns error code -// Purpose: reads data for a link from a line of input. -// -{ - int j = Mobjects[LINK]; - int k = Mlinks[type]; - int err = link_readParams(j, type, k, Tok, Ntokens); - Mobjects[LINK]++; - Mlinks[type]++; - return err; -} - -//============================================================================= - -int readEvent(char* tok[], int ntoks) -{ - DateTime x[4]; - - if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); - if ( !datetime_strToDate(tok[0], &x[0]) ) - return error_setInpError(ERR_DATETIME, tok[0]); - if ( !datetime_strToTime(tok[1], &x[1]) ) - return error_setInpError(ERR_DATETIME, tok[1]); - if ( !datetime_strToDate(tok[2], &x[2]) ) - return error_setInpError(ERR_DATETIME, tok[2]); - if ( !datetime_strToTime(tok[3], &x[3]) ) - return error_setInpError(ERR_DATETIME, tok[3]); - - Event[Mevents].start = x[0] + x[1]; - Event[Mevents].end = x[2] + x[3]; - if ( Event[Mevents].start >= Event[Mevents].end ) - return error_setInpError(ERR_DATETIME, " - start date exceeds end date"); - Mevents++; - return 0; -} - -//============================================================================= - -int findmatch(char *s, char *keyword[]) -// -// Input: s = character string -// keyword = array of keyword strings -// Output: returns index of matching keyword or -1 if no match found -// Purpose: finds match between string and array of keyword strings. -// -{ - int i = 0; - while (keyword[i] != NULL) - { - if (match(s, keyword[i])) return(i); - i++; - } - return(-1); -} - -//============================================================================= - -int match(char *str, char *substr) -// -// Input: str = character string being searched -// substr = sub-string being searched for -// Output: returns 1 if sub-string found, 0 if not -// Purpose: sees if a sub-string of characters appears in a string -// (not case sensitive). -// -{ - int i,j,k; - - // --- fail if substring is empty - if (!substr[0]) return(0); - - // --- skip leading blanks of str - for (k = 0; str[k]; k++) - { - if (str[k] != ' ') break; - } - - // --- check if substr matches remainder of str - for (i = k,j = 0; substr[j]; i++,j++) - { - if (!str[i] || UCHAR(str[i]) != UCHAR(substr[j])) return(0); - } - return(1); -} - -//============================================================================= - -int getInt(char *s, int *y) -// -// Input: s = a character string -// Output: y = converted value of s, -// returns 1 if conversion successful, 0 if not -// Purpose: converts a string to an integer number. -// -{ - double x; - if ( getDouble(s, &x) ) - { - if ( x < 0.0 ) x -= 0.01; - else x += 0.01; - *y = (int)x; - return 1; - } - *y = 0; - return 0; -} - -//============================================================================= - -int getFloat(char *s, float *y) -// -// Input: s = a character string -// Output: y = converted value of s, -// returns 1 if conversion successful, 0 if not -// Purpose: converts a string to a single precision floating point number. -// -{ - char *endptr; - *y = (float) strtod(s, &endptr); - if (*endptr > 0) return(0); - return(1); -} - -//============================================================================= - -int getDouble(char *s, double *y) -// -// Input: s = a character string -// Output: y = converted value of s, -// returns 1 if conversion successful, 0 if not -// Purpose: converts a string to a double precision floating point number. -// -{ - char *endptr; - *y = strtod(s, &endptr); - if (*endptr > 0) return(0); - return(1); -} - -//============================================================================= - -int getTokens(char *s) -// -// Input: s = a character string -// Output: returns number of tokens found in s -// Purpose: scans a string for tokens, saving pointers to them -// in shared variable Tok[]. -// -// Notes: Tokens can be separated by the characters listed in SEPSTR -// (spaces, tabs, newline, carriage return) which is defined -// in CONSTS.H. Text between quotes is treated as a single token. -// -{ - int n; - size_t len, m; - char *c; - - // --- begin with no tokens - for (n = 0; n < MAXTOKS; n++) Tok[n] = NULL; - n = 0; - - // --- truncate s at start of comment - c = strchr(s,';'); - if (c) *c = '\0'; - len = (int)strlen(s); - - // --- scan s for tokens until nothing left - while (len > 0 && n < MAXTOKS) - { - m = strcspn(s,SEPSTR); // find token length - if (m == 0) s++; // no token found - else - { - if (*s == '"') // token begins with quote - { - s++; // start token after quote - len--; // reduce length of s - m = strcspn(s,"\"\n"); // find end quote or new line - } - s[m] = '\0'; // null-terminate the token - Tok[n] = s; // save pointer to token - n++; // update token count - s += m+1; // begin next token - } - len -= m+1; // update length of s - } - return n; -} - -//============================================================================= diff --git a/src/inputrpt.c b/src/inputrpt.c deleted file mode 100644 index 46a6f952f..000000000 --- a/src/inputrpt.c +++ /dev/null @@ -1,354 +0,0 @@ -//----------------------------------------------------------------------------- -// inputrpt.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Report writing functions for input data summary. -// -// Update History -// ============== -// Build 5.2.0: -// - Support added for reporting Street geometry tables. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" -#include "lid.h" - -#define WRITE(x) (report_writeLine((x))) - -//============================================================================= - -void inputrpt_writeInput() -// -// Input: none -// Output: none -// Purpose: writes summary of input data to report file. -// -{ - int m; - int i, k; - int lidCount = 0; - if ( ErrorCode ) return; - - WRITE(""); - WRITE("*************"); - WRITE("Element Count"); - WRITE("*************"); - fprintf(Frpt.file, "\n Number of rain gages ...... %d", Nobjects[GAGE]); - fprintf(Frpt.file, "\n Number of subcatchments ... %d", Nobjects[SUBCATCH]); - fprintf(Frpt.file, "\n Number of nodes ........... %d", Nobjects[NODE]); - fprintf(Frpt.file, "\n Number of links ........... %d", Nobjects[LINK]); - fprintf(Frpt.file, "\n Number of pollutants ...... %d", Nobjects[POLLUT]); - fprintf(Frpt.file, "\n Number of land uses ....... %d", Nobjects[LANDUSE]); - - if ( Nobjects[POLLUT] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("*****************"); - WRITE("Pollutant Summary"); - WRITE("*****************"); - fprintf(Frpt.file, - "\n Ppt. GW Kdecay"); - fprintf(Frpt.file, - "\n Name Units Concen. Concen. 1/days CoPollutant"); - fprintf(Frpt.file, - "\n -----------------------------------------------------------------------"); - for (i = 0; i < Nobjects[POLLUT]; i++) - { - fprintf(Frpt.file, "\n %-20s %5s%10.2f%10.2f%10.2f", Pollut[i].ID, - QualUnitsWords[Pollut[i].units], Pollut[i].pptConcen, - Pollut[i].gwConcen, Pollut[i].kDecay*SECperDAY); - if ( Pollut[i].coPollut >= 0 ) - fprintf(Frpt.file, " %-s (%.2f)", - Pollut[Pollut[i].coPollut].ID, Pollut[i].coFraction); - } - } - - if ( Nobjects[LANDUSE] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("***************"); - WRITE("Landuse Summary"); - WRITE("***************"); - fprintf(Frpt.file, - "\n Sweeping Maximum Last"); - fprintf(Frpt.file, - "\n Name Interval Removal Swept"); - fprintf(Frpt.file, - "\n ---------------------------------------------------"); - for (i=0; i 0 ) - { - WRITE(""); - WRITE(""); - WRITE("****************"); - WRITE("Raingage Summary"); - WRITE("****************"); - fprintf(Frpt.file, -"\n Data Recording"); - fprintf(Frpt.file, -"\n Name Data Source Type Interval "); - fprintf(Frpt.file, -"\n ------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[GAGE]; i++) - { - if ( Gage[i].tSeries >= 0 ) - { - fprintf(Frpt.file, "\n %-20s %-30s ", - Gage[i].ID, Tseries[Gage[i].tSeries].ID); - fprintf(Frpt.file, "%-10s %3d min.", - RainTypeWords[Gage[i].rainType], - (Gage[i].rainInterval)/60); - } - else fprintf(Frpt.file, "\n %-20s %-30s", - Gage[i].ID, Gage[i].fname); - } - } - - if ( Nobjects[SUBCATCH] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("********************"); - WRITE("Subcatchment Summary"); - WRITE("********************"); - fprintf(Frpt.file, -"\n Name Area Width %%Imperv %%Slope Rain Gage Outlet "); - fprintf(Frpt.file, -"\n -----------------------------------------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[SUBCATCH]; i++) - { - fprintf(Frpt.file,"\n %-20s %10.2f%10.2f%10.2f%10.4f %-20s ", - Subcatch[i].ID, Subcatch[i].area*UCF(LANDAREA), - Subcatch[i].width*UCF(LENGTH), Subcatch[i].fracImperv*100.0, - Subcatch[i].slope*100.0, Gage[Subcatch[i].gage].ID); - if ( Subcatch[i].outNode >= 0 ) - { - fprintf(Frpt.file, "%-20s", Node[Subcatch[i].outNode].ID); - } - else if ( Subcatch[i].outSubcatch >= 0 ) - { - fprintf(Frpt.file, "%-20s", Subcatch[Subcatch[i].outSubcatch].ID); - } - if ( Subcatch[i].lidArea ) lidCount++; - } - } - if ( lidCount > 0 ) lid_writeSummary(); - - if ( Nobjects[NODE] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("************"); - WRITE("Node Summary"); - WRITE("************"); - fprintf(Frpt.file, -"\n Invert Max. Ponded External"); - fprintf(Frpt.file, -"\n Name Type Elev. Depth Area Inflow "); - fprintf(Frpt.file, -"\n -------------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[NODE]; i++) - { - fprintf(Frpt.file, "\n %-20s %-16s%10.2f%10.2f%10.1f", Node[i].ID, - NodeTypeWords[Node[i].type-JUNCTION], - Node[i].invertElev*UCF(LENGTH), - Node[i].fullDepth*UCF(LENGTH), - Node[i].pondedArea*UCF(LENGTH)*UCF(LENGTH)); - if ( Node[i].extInflow || Node[i].dwfInflow || Node[i].rdiiInflow ) - { - fprintf(Frpt.file, " Yes"); - } - } - } - - if ( Nobjects[LINK] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("************"); - WRITE("Link Summary"); - WRITE("************"); - fprintf(Frpt.file, -"\n Name From Node To Node Type Length %%Slope Roughness"); - fprintf(Frpt.file, -"\n ---------------------------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[LINK]; i++) - { - // --- list end nodes in their original orientation - if ( Link[i].direction == 1 ) - fprintf(Frpt.file, "\n %-16s %-16s %-16s ", - Link[i].ID, Node[Link[i].node1].ID, Node[Link[i].node2].ID); - else - fprintf(Frpt.file, "\n %-16s %-16s %-16s ", - Link[i].ID, Node[Link[i].node2].ID, Node[Link[i].node1].ID); - - // --- list link type - if ( Link[i].type == PUMP ) - { - k = Link[i].subIndex; - fprintf(Frpt.file, "%-5s PUMP ", - PumpTypeWords[Pump[k].type]); - } - else fprintf(Frpt.file, "%-12s", - LinkTypeWords[Link[i].type-CONDUIT]); - - // --- list length, slope and roughness for conduit links - if (Link[i].type == CONDUIT) - { - k = Link[i].subIndex; - fprintf(Frpt.file, "%10.1f%10.4f%10.4f", - Conduit[k].length*UCF(LENGTH), - Conduit[k].slope*100.0*Link[i].direction, - Conduit[k].roughness); - } - } - - WRITE(""); - WRITE(""); - WRITE("*********************"); - WRITE("Cross Section Summary"); - WRITE("*********************"); - fprintf(Frpt.file, -"\n Full Full Hyd. Max. No. of Full"); - fprintf(Frpt.file, -"\n Conduit Shape Depth Area Rad. Width Barrels Flow"); - fprintf(Frpt.file, -"\n ---------------------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[LINK]; i++) - { - if (Link[i].type == CONDUIT) - { - k = Link[i].subIndex; - fprintf(Frpt.file, "\n %-16s ", Link[i].ID); - if ( Link[i].xsect.type == CUSTOM ) - fprintf(Frpt.file, "%-16s ", Curve[Link[i].xsect.transect].ID); - else if ( Link[i].xsect.type == IRREGULAR ) - fprintf(Frpt.file, "%-16s ", - Transect[Link[i].xsect.transect].ID); - else if ( Link[i].xsect.type == STREET_XSECT ) - fprintf(Frpt.file, "%-16s ", - Street[Link[i].xsect.transect].ID); - else fprintf(Frpt.file, "%-16s ", - XsectTypeWords[Link[i].xsect.type]); - fprintf(Frpt.file, "%8.2f %8.2f %8.2f %8.2f %3d %8.2f", - Link[i].xsect.yFull*UCF(LENGTH), - Link[i].xsect.aFull*UCF(LENGTH)*UCF(LENGTH), - Link[i].xsect.rFull*UCF(LENGTH), - Link[i].xsect.wMax*UCF(LENGTH), - Conduit[k].barrels, - Link[i].qFull*UCF(FLOW)); - } - } - } - - if (Nobjects[SHAPE] > 0) - { - WRITE(""); - WRITE(""); - WRITE("*************"); - WRITE("Shape Summary"); - WRITE("*************"); - for (i = 0; i < Nobjects[SHAPE]; i++) - { - k = Shape[i].curve; - fprintf(Frpt.file, "\n\n Shape %s", Curve[k].ID); - fprintf(Frpt.file, "\n Area: "); - for ( m = 1; m < N_SHAPE_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Shape[i].areaTbl[m]); - } - fprintf(Frpt.file, "\n Hrad: "); - for ( m = 1; m < N_SHAPE_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Shape[i].hradTbl[m]); - } - fprintf(Frpt.file, "\n Width: "); - for ( m = 1; m < N_SHAPE_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Shape[i].widthTbl[m]); - } - } - } - - if (Nobjects[TRANSECT] > 0) - { - WRITE(""); - WRITE(""); - WRITE("****************"); - WRITE("Transect Summary"); - WRITE("****************"); - for (i = 0; i < Nobjects[TRANSECT]; i++) - { - fprintf(Frpt.file, "\n\n Transect %s", Transect[i].ID); - fprintf(Frpt.file, "\n Area: "); - for ( m = 1; m < N_TRANSECT_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Transect[i].areaTbl[m]); - } - fprintf(Frpt.file, "\n Hrad: "); - for ( m = 1; m < N_TRANSECT_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Transect[i].hradTbl[m]); - } - fprintf(Frpt.file, "\n Width: "); - for ( m = 1; m < N_TRANSECT_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Transect[i].widthTbl[m]); - } - } - } - - if (Nobjects[STREET] > 0) - { - WRITE(""); - WRITE(""); - WRITE("**************"); - WRITE("Street Summary"); - WRITE("**************"); - for (i = 0; i < Nobjects[STREET]; i++) - { - fprintf(Frpt.file, "\n\n Street %s", Street[i].ID); - fprintf(Frpt.file, "\n Area: "); - for (m = 1; m < Street[i].transect.nTbl; m++) - { - if (m % 5 == 1) fprintf(Frpt.file, "\n "); - fprintf(Frpt.file, "%10.4f ", Street[i].transect.areaTbl[m]); - } - fprintf(Frpt.file, "\n Hrad: "); - for (m = 1; m < Street[i].transect.nTbl; m++) - { - if (m % 5 == 1) fprintf(Frpt.file, "\n "); - fprintf(Frpt.file, "%10.4f ", Street[i].transect.hradTbl[m]); - } - fprintf(Frpt.file, "\n Width: "); - for (m = 1; m < Street[i].transect.nTbl; m++) - { - if (m % 5 == 1) fprintf(Frpt.file, "\n "); - fprintf(Frpt.file, "%10.4f ", Street[i].transect.widthTbl[m]); - } - } - } - WRITE(""); -} diff --git a/src/keywords.c b/src/keywords.c deleted file mode 100644 index 17973d182..000000000 --- a/src/keywords.c +++ /dev/null @@ -1,170 +0,0 @@ -//----------------------------------------------------------------------------- -// keywords.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Exportable keyword dictionary -// -// NOTE: the keywords in each list must appear in same order used -// by its complementary enumerated variable in enums.h and -// must be terminated by NULL. The actual text of each keyword -// is defined in text.h. -// -// Update History -// ============== -// Build 5.1.007: -// - Keywords for Ignore RDII option and groundwater flow equation -// and climate adjustment input sections added. -// Build 5.1.008: -// - Keyword arrays placed in alphabetical order for better readability. -// - Keywords added for Minimum Routing Step and Number of Threads options. -// Build 5.1.010: -// - New Modified Green Ampt keyword added to InfilModelWords. -// - New Roadway weir keyword added to WeirTypeWords. -// Build 5.1.011: -// - New section keyword for [EVENTS] added. -// Build 5.1.013: -// - New option keywords w_SURCHARGE_METHOD, w_RULE_STEP, w_AVERAGES -// and w_WEIR added. -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for variable speed pumps. -// - Support added for analytical storage shapes. -// - Support added for RptFlags.disabled option. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include // need this to define NULL -#include "text.h" - -char* BuildupTypeWords[] = { w_NONE, w_POW, w_EXP, w_SAT, w_EXT, NULL}; -char* CurveTypeWords[] = { w_STORAGE, w_DIVERSION, w_TIDAL, w_RATING, - w_CONTROLS, w_SHAPE, w_WEIR, - w_PUMP1, w_PUMP2, w_PUMP3, w_PUMP4, - w_PUMP5, NULL}; -char* DividerTypeWords[] = { w_CUTOFF, w_TABULAR, w_WEIR, w_OVERFLOW, NULL}; -char* EvapTypeWords[] = { w_CONSTANT, w_MONTHLY, w_TIMESERIES, - w_TEMPERATURE, w_FILE, w_RECOVERY, - w_DRYONLY, NULL}; -char* FileTypeWords[] = { w_RAINFALL, w_RUNOFF, w_HOTSTART, w_RDII, - w_INFLOWS, w_OUTFLOWS, NULL}; -char* FileModeWords[] = { w_NO, w_SCRATCH, w_USE, w_SAVE, NULL}; -char* FlowUnitWords[] = { w_CFS, w_GPM, w_MGD, w_CMS, w_LPS, w_MLD, NULL}; -char* ForceMainEqnWords[] = { w_H_W, w_D_W, NULL}; -char* GageDataWords[] = { w_TIMESERIES, w_FILE, NULL}; -char* InfilModelWords[] = { w_HORTON, w_MOD_HORTON, w_GREEN_AMPT, - w_MOD_GREEN_AMPT, w_CURVE_NUMEBR, NULL}; -char* InertDampingWords[] = { w_NONE, w_PARTIAL, w_FULL, NULL}; -char* LinkOffsetWords[] = { w_DEPTH, w_ELEVATION, NULL}; -char* LinkTypeWords[] = { w_CONDUIT, w_PUMP, w_ORIFICE, - w_WEIR, w_OUTLET }; -char* LoadUnitsWords[] = { w_LBS, w_KG, w_LOGN }; -char* NodeTypeWords[] = { w_JUNCTION, w_OUTFALL, - w_STORAGE, w_DIVIDER }; -char* NoneAllWords[] = { w_NONE, w_ALL, NULL}; -char* NormalFlowWords[] = { w_SLOPE, w_FROUDE, w_BOTH, NULL}; -char* NormalizerWords[] = { w_PER_AREA, w_PER_CURB, NULL}; -char* NoYesWords[] = { w_NO, w_YES, NULL}; -char* OffOnWords[] = { w_OFF, w_ON, NULL}; -char* OldRouteModelWords[] = { w_NONE, w_NF, w_KW, w_EKW, w_DW, NULL}; -char* OptionWords[] = { w_FLOW_UNITS, w_INFIL_MODEL, - w_ROUTE_MODEL, w_START_DATE, - w_START_TIME, w_END_DATE, - w_END_TIME, w_REPORT_START_DATE, - w_REPORT_START_TIME, w_SWEEP_START, - w_SWEEP_END, w_START_DRY_DAYS, - w_WET_STEP, w_DRY_STEP, - w_ROUTE_STEP, w_RULE_STEP, - w_REPORT_STEP, - w_ALLOW_PONDING, w_INERT_DAMPING, - w_SLOPE_WEIGHTING, w_VARIABLE_STEP, - w_NORMAL_FLOW_LTD, w_LENGTHENING_STEP, - w_MIN_SURFAREA, w_COMPATIBILITY, - w_SKIP_STEADY_STATE, w_TEMPDIR, - w_IGNORE_RAINFALL, w_FORCE_MAIN_EQN, - w_LINK_OFFSETS, w_MIN_SLOPE, - w_IGNORE_SNOWMELT, w_IGNORE_GWATER, - w_IGNORE_ROUTING, w_IGNORE_QUALITY, - w_MAX_TRIALS, w_HEAD_TOL, - w_SYS_FLOW_TOL, w_LAT_FLOW_TOL, - w_IGNORE_RDII, w_MIN_ROUTE_STEP, - w_NUM_THREADS, w_SURCHARGE_METHOD, - NULL }; -char* OrificeTypeWords[] = { w_SIDE, w_BOTTOM, NULL}; -char* OutfallTypeWords[] = { w_FREE, w_NORMAL, w_FIXED, w_TIDAL, - w_TIMESERIES, NULL}; -char* PatternTypeWords[] = { w_MONTHLY, w_DAILY, w_HOURLY, w_WEEKEND, NULL}; -char* PondingUnitsWords[] = { w_PONDED_FEET, w_PONDED_METERS }; -char* ProcessVarWords[] = { w_HRT, w_DT, w_FLOW, w_DEPTH, w_AREA, NULL}; -char* PumpTypeWords[] = { w_TYPE1, w_TYPE2, w_TYPE3, w_TYPE4, w_TYPE5, w_IDEAL }; -char* QualUnitsWords[] = { w_MGperL, w_UGperL, w_COUNTperL, NULL}; -char* RainTypeWords[] = { w_INTENSITY, w_VOLUME, w_CUMULATIVE, NULL}; -char* RainUnitsWords[] = { w_INCHES, w_MMETER, NULL}; -char* RelationWords[] = { w_TABULAR, w_FUNCTIONAL, - w_CYLINDRICAL, w_CONICAL, w_PARABOLIC, - w_PYRAMIDAL, NULL}; -char* ReportWords[] = { w_DISABLED, w_INPUT, w_SUBCATCH, w_NODE, w_LINK, - w_CONTINUITY, w_FLOWSTATS,w_CONTROLS, - w_AVERAGES, w_NODESTATS, NULL}; -char* RouteModelWords[] = { w_NONE, w_STEADY, w_KINWAVE, w_XKINWAVE, - w_DYNWAVE, NULL}; -char* RuleKeyWords[] = { w_RULE, w_IF, w_AND, w_OR, w_THEN, w_ELSE, - w_PRIORITY, NULL}; -char* SectWords[] = { ws_TITLE, ws_OPTION, - ws_FILE, ws_RAINGAGE, - ws_TEMP, ws_EVAP, - ws_SUBCATCH, ws_SUBAREA, - ws_INFIL, ws_AQUIFER, - ws_GROUNDWATER, ws_SNOWMELT, - ws_JUNCTION, ws_OUTFALL, - ws_STORAGE, ws_DIVIDER, - ws_CONDUIT, ws_PUMP, - ws_ORIFICE, ws_WEIR, - ws_OUTLET, ws_XSECTION, - ws_TRANSECT, ws_LOSS, - ws_CONTROL, ws_POLLUTANT, - ws_LANDUSE, ws_BUILDUP, - ws_WASHOFF, ws_COVERAGE, - ws_INFLOW, ws_DWF, - ws_PATTERN, ws_RDII, - ws_UNITHYD, ws_LOADING, - ws_TREATMENT, ws_CURVE, - ws_TIMESERIES, ws_REPORT, - ws_COORDINATE, ws_VERTICES, - ws_POLYGON, ws_LABEL, - ws_SYMBOL, ws_BACKDROP, - ws_TAG, ws_PROFILE, - ws_MAP, ws_LID_CONTROL, - ws_LID_USAGE, ws_GWF, - ws_ADJUST, ws_EVENT, - ws_STREET, ws_INLET_USAGE, - ws_INLET, NULL}; -char* SnowmeltWords[] = { w_PLOWABLE, w_IMPERV, w_PERV, w_REMOVAL, NULL}; -char* SurchargeWords[] = { w_EXTRAN, w_SLOT, NULL}; -char* TempKeyWords[] = { w_TIMESERIES, w_FILE, w_WINDSPEED, w_SNOWMELT, - w_ADC, NULL}; -char* TransectKeyWords[] = { w_NC, w_X1, w_GR, NULL}; -char* TreatTypeWords[] = { w_REMOVAL, w_CONCEN, NULL}; -char* UHTypeWords[] = { w_SHORT, w_MEDIUM, w_LONG, NULL}; -char* VolUnitsWords[] = { w_MGAL, w_MLTRS }; -char* VolUnitsWords2[] = { w_GAL, w_LTR }; -char* WashoffTypeWords[] = { w_NONE, w_EXP, w_RC, w_EMC, NULL}; -char* WeirTypeWords[] = { w_TRANSVERSE, w_SIDEFLOW, w_VNOTCH, - w_TRAPEZOIDAL, w_ROADWAY, NULL}; -char* XsectTypeWords[] = { w_DUMMY, w_CIRCULAR, - w_FILLED_CIRCULAR, w_RECT_CLOSED, - w_RECT_OPEN, w_TRAPEZOIDAL, - w_TRIANGULAR, w_PARABOLIC, - w_POWERFUNC, w_RECT_TRIANG, - w_RECT_ROUND, w_MOD_BASKET, - w_HORIZELLIPSE, w_VERTELLIPSE, - w_ARCH, w_EGGSHAPED, - w_HORSESHOE, w_GOTHIC, - w_CATENARY, w_SEMIELLIPTICAL, - w_BASKETHANDLE, w_SEMICIRCULAR, - w_IRREGULAR, w_CUSTOM, - w_FORCE_MAIN, w_STREET, - NULL}; diff --git a/src/keywords.h b/src/keywords.h deleted file mode 100644 index e330c59bc..000000000 --- a/src/keywords.h +++ /dev/null @@ -1,73 +0,0 @@ -//----------------------------------------------------------------------------- -// keywords.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Exportable keyword dictionary -// -// Update History -// ============== -// Build 5.1.008: -// - Keyword arrays listed in alphabetical order. -// Build 5.1.013: -// - New keyword array defined for surcharge method. -//----------------------------------------------------------------------------- - -#ifndef KEYWORDS_H -#define KEYWORDS_H - - -extern char* BuildupTypeWords[]; -extern char* CurveTypeWords[]; -extern char* DividerTypeWords[]; -extern char* DynWaveMethodWords[]; -extern char* EvapTypeWords[]; -extern char* FileModeWords[]; -extern char* FileTypeWords[]; -extern char* FlowUnitWords[]; -extern char* ForceMainEqnWords[]; -extern char* GageDataWords[]; -extern char* InertDampingWords[]; -extern char* InfilModelWords[]; -extern char* LinkOffsetWords[]; -extern char* LinkTypeWords[]; -extern char* LoadUnitsWords[]; -extern char* NodeTypeWords[]; -extern char* NoneAllWords[]; -extern char* NormalFlowWords[]; -extern char* NormalizerWords[]; -extern char* NoYesWords[]; -extern char* OldRouteModelWords[]; -extern char* OffOnWords[]; -extern char* OptionWords[]; -extern char* OrificeTypeWords[]; -extern char* OutfallTypeWords[]; -extern char* PatternTypeWords[]; -extern char* PondingUnitsWords[]; -extern char* ProcessVarWords[]; -extern char* PumpTypeWords[]; -extern char* QualUnitsWords[]; -extern char* RainTypeWords[]; -extern char* RainUnitsWords[]; -extern char* ReportWords[]; -extern char* RelationWords[]; -extern char* RouteModelWords[]; -extern char* RuleKeyWords[]; -extern char* SectWords[]; -extern char* SnowmeltWords[]; -extern char* SurchargeWords[]; -extern char* TempKeyWords[]; -extern char* TransectKeyWords[]; -extern char* TreatTypeWords[]; -extern char* UHTypeWords[]; -extern char* VolUnitsWords[]; -extern char* VolUnitsWords2[]; -extern char* WashoffTypeWords[]; -extern char* WeirTypeWords[]; -extern char* XsectTypeWords[]; - - -#endif //KEYWORDS_H diff --git a/src/kinwave.c b/src/kinwave.c deleted file mode 100644 index b137e4ce9..000000000 --- a/src/kinwave.c +++ /dev/null @@ -1,272 +0,0 @@ -//----------------------------------------------------------------------------- -// kinwave.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// M. Tryby (EPA) -// -// Kinematic wave flow routing functions. -// -// Update History -// ============== -// Build 5.1.008: -// - Conduit inflow passed to function that computes conduit losses. -// Build 5.1.014: -// - Arguments to function link_getLossRate changed. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "headers.h" -#include "findroot.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double WX = 0.6; // distance weighting -static const double WT = 0.6; // time weighting -static const double EPSIL = 0.001; // convergence criterion - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -static double Beta1; -static double C1; -static double C2; -static double Afull; -static double Qfull; -static TXsect* pXsect; - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// kinwave_execute (called by flowrout_execute) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int solveContinuity(double qin, double ain, double* aout); -static void evalContinuity(double a, double* f, double* df, void* p); - -//============================================================================= - -int kinwave_execute(int j, double* qinflow, double* qoutflow, double tStep) -// -// Input: j = link index -// qinflow = inflow at current time (cfs) -// tStep = time step (sec) -// Output: qoutflow = outflow at current time (cfs), -// returns number of iterations used -// Purpose: finds outflow over time step tStep given flow entering a -// conduit using Kinematic Wave flow routing. -// -// -// ^ q3 -// t | -// | qin, ain |-------------------| qout, aout -// | | Flow ---> | -// |----> x q1, a1 |-------------------| q2, a2 -// -// -{ - int k; - int result = 1; - double dxdt, dq; - double ain, aout; - double qin, qout; - double a1, a2, q1, q2, q3; - - // --- no routing for non-conduit link - (*qoutflow) = (*qinflow); - if ( Link[j].type != CONDUIT ) return result; - - // --- no routing for dummy xsection - if ( Link[j].xsect.type == DUMMY ) return result; - - // --- assign module-level variables - pXsect = &Link[j].xsect; - Qfull = Link[j].qFull; - Afull = Link[j].xsect.aFull; - k = Link[j].subIndex; - Beta1 = Conduit[k].beta / Qfull; - - // --- normalize previous flows - q1 = Conduit[k].q1 / Qfull; - q2 = Conduit[k].q2 / Qfull; - - // --- normalize inflow - qin = (*qinflow) / Conduit[k].barrels / Qfull; - - // --- compute evaporation and infiltration loss rate - q3 = link_getLossRate(j, qin*Qfull) / Qfull; - - // --- normalize previous areas - a1 = Conduit[k].a1 / Afull; - a2 = Conduit[k].a2 / Afull; - - // --- use full area when inlet flow >= full flow - if ( qin >= 1.0 ) ain = 1.0; - - // --- get normalized inlet area corresponding to inlet flow - else ain = xsect_getAofS(pXsect, qin/Beta1) / Afull; - - // --- check for no flow - if ( qin <= TINY && q2 <= TINY ) - { - qout = 0.0; - aout = 0.0; - } - - // --- otherwise solve finite difference form of continuity eqn. - else - { - // --- compute constant factors - dxdt = link_getLength(j) / tStep * Afull / Qfull; - dq = q2 - q1; - C1 = dxdt * WT / WX; - C2 = (1.0 - WT) * (ain - a1); - C2 = C2 - WT * a2; - C2 = C2 * dxdt / WX; - C2 = C2 + (1.0 - WX) / WX * dq - qin; - C2 = C2 + q3 / WX; - - // --- starting guess for aout is value from previous time step - aout = a2; - - // --- solve continuity equation for aout - result = solveContinuity(qin, ain, &aout); - - // --- report error if continuity eqn. not solved - if ( result == -1 ) - { - report_writeErrorMsg(ERR_KINWAVE, Link[j].ID); - return 1; - } - if ( result <= 0 ) result = 1; - - // --- compute normalized outlet flow from outlet area - qout = Beta1 * xsect_getSofA(pXsect, aout*Afull); - if ( qin > 1.0 ) qin = 1.0; - } - - // --- save new flows and areas - Conduit[k].q1 = qin * Qfull; - Conduit[k].a1 = ain * Afull; - Conduit[k].q2 = qout * Qfull; - Conduit[k].a2 = aout * Afull; - Conduit[k].fullState = - link_getFullState(Conduit[k].a1, Conduit[k].a2, Afull); - (*qinflow) = Conduit[k].q1 * Conduit[k].barrels; - (*qoutflow) = Conduit[k].q2 * Conduit[k].barrels; - return result; -} - -//============================================================================= - -int solveContinuity(double qin, double ain, double* aout) -// -// Input: qin = upstream normalized flow -// ain = upstream normalized area -// aout = downstream normalized area -// Output: new value for aout; returns an error code -// Purpose: solves continuity equation f(a) = Beta1*S(a) + C1*a + C2 = 0 -// for 'a' using the Newton-Raphson root finder function. -// Return code has the following meanings: -// >= 0 number of function evaluations used -// -1 Newton function failed -// -2 flow always above max. flow -// -3 flow always below zero -// -// Note: pXsect (pointer to conduit's cross-section), and constants Beta1, -// C1, and C2 are module-level shared variables assigned values -// in kinwave_execute(). -// -{ - int n; // # evaluations or error code - double aLo, aHi, aTmp; // lower/upper bounds on a - double fLo, fHi; // lower/upper bounds on f - double tol = EPSIL; // absolute convergence tol. - - // --- first determine bounds on 'a' so that f(a) passes through 0. - - // --- set upper bound to area at full flow - aHi = 1.0; - fHi = 1.0 + C1 + C2; - - // --- try setting lower bound to area where section factor is maximum - aLo = xsect_getAmax(pXsect) / Afull; - if ( aLo < aHi ) - { - fLo = ( Beta1 * pXsect->sMax ) + (C1 * aLo) + C2; - } - else fLo = fHi; - - // --- if fLo and fHi have same sign then set lower bound to 0 - if ( fHi*fLo > 0.0 ) - { - aHi = aLo; - fHi = fLo; - aLo = 0.0; - fLo = C2; - } - - // --- proceed with search for root if fLo and fHi have different signs - if ( fHi*fLo <= 0.0 ) - { - // --- start search at midpoint of lower/upper bounds - // if initial value outside of these bounds - if ( *aout < aLo || *aout > aHi ) *aout = 0.5*(aLo + aHi); - - // --- if fLo > fHi then switch aLo and aHi - if ( fLo > fHi ) - { - aTmp = aLo; - aLo = aHi; - aHi = aTmp; - } - - // --- call the Newton root finder method passing it the - // evalContinuity function to evaluate the function - // and its derivatives - n = findroot_Newton(aLo, aHi, aout, tol, evalContinuity, NULL); - - // --- check if root finder succeeded - if ( n <= 0 ) n = -1; - } - - // --- if lower/upper bound functions both negative then use full flow - else if ( fLo < 0.0 ) - { - if ( qin > 1.0 ) *aout = ain; - else *aout = 1.0; - n = -2; - } - - // --- if lower/upper bound functions both positive then use no flow - else if ( fLo > 0 ) - { - *aout = 0.0; - n = -3; - } - else n = -1; - return n; -} - -//============================================================================= - -void evalContinuity(double a, double* f, double* df, void* p) -// -// Input: a = outlet normalized area -// Output: f = value of continuity eqn. -// df = derivative of continuity eqn. -// Purpose: computes value of continuity equation (f) and its derivative (df) -// w.r.t. normalized area for link with normalized outlet area 'a'. -// -{ - *f = (Beta1 * xsect_getSofA(pXsect, a*Afull)) + (C1 * a) + C2; - *df = (Beta1 * Afull * xsect_getdSdA(pXsect, a*Afull)) + C1; -} - -//============================================================================= diff --git a/src/landuse.c b/src/landuse.c deleted file mode 100644 index 4c8402790..000000000 --- a/src/landuse.c +++ /dev/null @@ -1,723 +0,0 @@ -//----------------------------------------------------------------------------- -// landuse.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Pollutant buildup and washoff functions. -// -// Update History -// ============== -// Build 5.1.008: -// - landuse_getWashoffMass() re-named to landuse_getWashoffQual() and -// modified to return concentration instead of mass load. -// - landuse_getRunoffLoad() re-named to landuse_getWashoffLoad() and -// modified to work with landuse_getWashoffQual(). -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// landuse_readParams (called by parseLine in input.c) -// landuse_readPollutParams (called by parseLine in input.c) -// landuse_readBuildupParams (called by parseLine in input.c) -// landuse_readWashoffParams (called by parseLine in input.c) - -// landuse_getInitBuildup (called by subcatch_initState) -// landuse_getBuildup (called by surfqual_getBuildup) -// landuse_getWashoffLoad (called by surfqual_getWashoff) -// landuse_getCoPollutLoad (called by surfqual_getwashoff)); -// landuse_getAvgBMPEffic (called by updatePondedQual in surfqual.c) - -//----------------------------------------------------------------------------- -// Function declarations -//----------------------------------------------------------------------------- -static double landuse_getBuildupDays(int landuse, int pollut, double buildup); -static double landuse_getBuildupMass(int landuse, int pollut, double days); -static double landuse_getWashoffQual(int landuse, int pollut, double buildup, - double runoff, double area); -static double landuse_getExternalBuildup(int i, int p, double buildup, - double tStep); - -//============================================================================= - -int landuse_readParams(int j, char* tok[], int ntoks) -// -// Input: j = land use index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads landuse parameters from a tokenized line of input. -// -// Data format is: -// landuseID (sweepInterval sweepRemoval sweepDays0) -// -{ - char *id; - if ( ntoks < 1 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LANDUSE, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - Landuse[j].ID = id; - if ( ntoks > 1 ) - { - if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); - if ( ! getDouble(tok[1], &Landuse[j].sweepInterval) ) - return error_setInpError(ERR_NUMBER, tok[1]); - if ( ! getDouble(tok[2], &Landuse[j].sweepRemoval) ) - return error_setInpError(ERR_NUMBER, tok[2]); - if ( ! getDouble(tok[3], &Landuse[j].sweepDays0) ) - return error_setInpError(ERR_NUMBER, tok[3]); - } - else - { - Landuse[j].sweepInterval = 0.0; - Landuse[j].sweepRemoval = 0.0; - Landuse[j].sweepDays0 = 0.0; - } - if ( Landuse[j].sweepRemoval < 0.0 - || Landuse[j].sweepRemoval > 1.0 ) - return error_setInpError(ERR_NUMBER, tok[2]); - return 0; -} - -//============================================================================= - -int landuse_readPollutParams(int j, char* tok[], int ntoks) -// -// Input: j = pollutant index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads pollutant parameters from a tokenized line of input. -// -// Data format is: -// ID Units cRain cGW cRDII kDecay (snowOnly coPollut coFrac cDWF cInit) -// -{ - int i, k, coPollut, snowFlag; - double x[4], coFrac, cDWF, cInit; - char *id; - - // --- extract pollutant name & units - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(POLLUT, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - k = findmatch(tok[1], QualUnitsWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- extract concen. in rain, gwater, & I&I - for ( i = 2; i <= 4; i++ ) - { - if ( ! getDouble(tok[i], &x[i-2]) || x[i-2] < 0.0 ) - { - return error_setInpError(ERR_NUMBER, tok[i]); - } - } - - // --- extract decay coeff. (which can be negative for growth) - if ( ! getDouble(tok[5], &x[3]) ) - { - return error_setInpError(ERR_NUMBER, tok[5]); - } - - // --- set defaults for snow only flag & co-pollut. parameters - snowFlag = 0; - coPollut = -1; - coFrac = 0.0; - cDWF = 0.0; - cInit = 0.0; - - // --- check for snow only flag - if ( ntoks >= 7 ) - { - snowFlag = findmatch(tok[6], NoYesWords); - if ( snowFlag < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); - } - - // --- check for co-pollutant - if ( ntoks >= 9 ) - { - if ( !strcomp(tok[7], "*") ) - { - coPollut = project_findObject(POLLUT, tok[7]); - if ( coPollut < 0 ) return error_setInpError(ERR_NAME, tok[7]); - if ( ! getDouble(tok[8], &coFrac) || coFrac < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[8]); - } - } - - // --- check for DWF concen. - if ( ntoks >= 10 ) - { - if ( ! getDouble(tok[9], &cDWF) || cDWF < 0.0) - return error_setInpError(ERR_NUMBER, tok[9]); - } - - // --- check for initial concen. - if ( ntoks >= 11 ) - { - if ( ! getDouble(tok[10], &cInit) || cInit < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[9]); - } - - // --- save values for pollutant object - Pollut[j].ID = id; - Pollut[j].units = k; - if ( Pollut[j].units == MG ) Pollut[j].mcf = UCF(MASS); - else if ( Pollut[j].units == UG ) Pollut[j].mcf = UCF(MASS) / 1000.0; - else Pollut[j].mcf = 1.0; - Pollut[j].pptConcen = x[0]; - Pollut[j].gwConcen = x[1]; - Pollut[j].rdiiConcen = x[2]; - Pollut[j].kDecay = x[3]/SECperDAY; - Pollut[j].snowOnly = snowFlag; - Pollut[j].coPollut = coPollut; - Pollut[j].coFraction = coFrac; - Pollut[j].dwfConcen = cDWF; - Pollut[j].initConcen = cInit; - return 0; -} - -//============================================================================= - -int landuse_readBuildupParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads pollutant buildup parameters from a tokenized line of input. -// -// Data format is: -// landuseID pollutID buildupType c1 c2 c3 normalizerType -// -{ - int i, j, k, n, p; - double c[3] = {0, 0, 0}, tmax; - - if ( ntoks < 3 ) return 0; - j = project_findObject(LANDUSE, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - p = project_findObject(POLLUT, tok[1]); - if ( p < 0 ) return error_setInpError(ERR_NAME, tok[1]); - k = findmatch(tok[2], BuildupTypeWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[2]); - Landuse[j].buildupFunc[p].funcType = k; - if ( k > NO_BUILDUP ) - { - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - if ( k != EXTERNAL_BUILDUP ) for (i=0; i<3; i++) - { - if ( ! getDouble(tok[i+3], &c[i]) || c[i] < 0.0 ) - { - return error_setInpError(ERR_NUMBER, tok[i+3]); - } - } - n = findmatch(tok[6], NormalizerWords); - if (n < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); - Landuse[j].buildupFunc[p].normalizer = n; - } - - // Find time until max. buildup (or time series for external buildup) - switch (Landuse[j].buildupFunc[p].funcType) - { - case POWER_BUILDUP: - // --- check for too small or large an exponent - if ( c[2] > 0.0 && (c[2] < 0.01 || c[2] > 10.0) ) - return error_setInpError(ERR_KEYWORD, tok[5]); - - // --- find time to reach max. buildup - // --- use zero if coeffs. are 0 - if ( c[1]*c[2] == 0.0 ) tmax = 0.0; - - // --- use 10 years if inverse power function tends to blow up - else if ( log10(c[0]) / c[2] > 3.5 ) tmax = 3650.0; - - // --- otherwise use inverse power function - else tmax = pow(c[0]/c[1], 1.0/c[2]); - break; - - case EXPON_BUILDUP: - if ( c[1] == 0.0 ) tmax = 0.0; - else tmax = -log(0.001)/c[1]; - break; - - case SATUR_BUILDUP: - tmax = 1000.0*c[2]; - break; - - case EXTERNAL_BUILDUP: - if ( !getDouble(tok[3], &c[0]) || c[0] < 0.0 ) //max. buildup - return error_setInpError(ERR_NUMBER, tok[3]); - if ( !getDouble(tok[4], &c[1]) || c[1] < 0.0 ) //scaling factor - return error_setInpError(ERR_NUMBER, tok[3]); - n = project_findObject(TSERIES, tok[5]); //time series - if ( n < 0 ) return error_setInpError(ERR_NAME, tok[4]); - Tseries[n].refersTo = EXTERNAL_BUILDUP; - c[2] = n; - tmax = 0.0; - break; - - default: - tmax = 0.0; - } - - // Assign parameters to buildup object - Landuse[j].buildupFunc[p].coeff[0] = c[0]; - Landuse[j].buildupFunc[p].coeff[1] = c[1]; - Landuse[j].buildupFunc[p].coeff[2] = c[2]; - Landuse[j].buildupFunc[p].maxDays = tmax; - return 0; -} - -//============================================================================= - -int landuse_readWashoffParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads pollutant washoff parameters from a tokenized line of input. -// -// Data format is: -// landuseID pollutID washoffType c1 c2 sweepEffic bmpRemoval -{ - int i, j, p; - int func; - double x[4]; - - if ( ntoks < 3 ) return 0; - for (i=0; i<4; i++) x[i] = 0.0; - func = NO_WASHOFF; - j = project_findObject(LANDUSE, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - p = project_findObject(POLLUT, tok[1]); - if ( p < 0 ) return error_setInpError(ERR_NAME, tok[1]); - if ( ntoks > 2 ) - { - func = findmatch(tok[2], WashoffTypeWords); - if ( func < 0 ) return error_setInpError(ERR_KEYWORD, tok[2]); - if ( func != NO_WASHOFF ) - { - if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); - if ( ! getDouble(tok[3], &x[0]) ) - return error_setInpError(ERR_NUMBER, tok[3]); - if ( ! getDouble(tok[4], &x[1]) ) - return error_setInpError(ERR_NUMBER, tok[4]); - if ( ntoks >= 6 ) - { - if ( ! getDouble(tok[5], &x[2]) ) - return error_setInpError(ERR_NUMBER, tok[5]); - } - if ( ntoks >= 7 ) - { - if ( ! getDouble(tok[6], &x[3]) ) - return error_setInpError(ERR_NUMBER, tok[6]); - } - } - } - - // --- check for valid parameter values - // x[0] = washoff coeff. - // x[1] = washoff expon. - // x[2] = sweep effic. - // x[3] = BMP effic. - if ( x[0] < 0.0 ) return error_setInpError(ERR_NUMBER, tok[3]); - if ( x[1] < -10.0 || x[1] > 10.0 ) - return error_setInpError(ERR_NUMBER, tok[4]);; - if ( x[2] < 0.0 || x[2] > 100.0 ) - return error_setInpError(ERR_NUMBER, tok[5]); - if ( x[3] < 0.0 || x[3] > 100.0 ) - return error_setInpError(ERR_NUMBER, tok[6]); - - // --- convert units of washoff coeff. - if ( func == EXPON_WASHOFF ) x[0] /= 3600.0; - if ( func == RATING_WASHOFF ) x[0] *= pow(UCF(FLOW), x[1]); - if ( func == EMC_WASHOFF ) x[0] *= LperFT3; - - // --- assign washoff parameters to washoff object - Landuse[j].washoffFunc[p].funcType = func; - Landuse[j].washoffFunc[p].coeff = x[0]; - Landuse[j].washoffFunc[p].expon = x[1]; - Landuse[j].washoffFunc[p].sweepEffic = x[2] / 100.0; - Landuse[j].washoffFunc[p].bmpEffic = x[3] / 100.0; - return 0; -} - -//============================================================================= - -void landuse_getInitBuildup(TLandFactor* landFactor, double* initBuildup, - double area, double curb) -// -// Input: landFactor = array of land use factors -// initBuildup = total initial buildup of each pollutant -// area = subcatchment's area (ft2) -// curb = subcatchment's curb length (users units) -// Output: modifies each land use factor's initial pollutant buildup -// Purpose: determines the initial buildup of each pollutant on -// each land use for a given subcatchment. -// -// Notes: Contributions from co-pollutants to initial buildup are not -// included since the co-pollutant mechanism only applies to -// washoff. -// -{ - int i, p; - double startDrySeconds; // antecedent dry period (sec) - double f; // faction of total land area - double fArea; // area of land use (ft2) - double fCurb; // curb length of land use - double buildup; // pollutant mass buildup - - // --- convert antecedent dry days into seconds - startDrySeconds = StartDryDays*SECperDAY; - - // --- examine each land use - for (i = 0; i < Nobjects[LANDUSE]; i++) - { - // --- initialize date when last swept - landFactor[i].lastSwept = StartDateTime - Landuse[i].sweepDays0; - - // --- determine area and curb length covered by land use - f = landFactor[i].fraction; - fArea = f * area * UCF(LANDAREA); - fCurb = f * curb; - - // --- determine buildup of each pollutant - for (p = 0; p < Nobjects[POLLUT]; p++) - { - // --- if an initial loading was supplied, then use it to - // find the starting buildup over the land use - buildup = 0.0; - if ( initBuildup[p] > 0.0 ) buildup = initBuildup[p] * fArea; - - // --- otherwise use the land use's buildup function to - // compute a buildup over the antecedent dry period - else buildup = landuse_getBuildup(i, p, fArea, fCurb, buildup, - startDrySeconds); - landFactor[i].buildup[p] = buildup; - } - } -} - -//============================================================================= - -double landuse_getBuildup(int i, int p, double area, double curb, double buildup, - double tStep) -// -// Input: i = land use index -// p = pollutant index -// area = land use area (ac or ha) -// curb = land use curb length (users units) -// buildup = current pollutant buildup (lbs or kg) -// tStep = time increment for buildup (sec) -// Output: returns new buildup mass (lbs or kg) -// Purpose: computes new pollutant buildup on a landuse after a time increment. -// -{ - int n; // normalizer code - double days; // accumulated days of buildup - double perUnit; // normalizer value (area or curb length) - - // --- return current buildup if no buildup function or time increment - if ( Landuse[i].buildupFunc[p].funcType == NO_BUILDUP || tStep == 0.0 ) - { - return buildup; - } - - // --- see what buildup is normalized to - n = Landuse[i].buildupFunc[p].normalizer; - perUnit = 1.0; - if ( n == PER_AREA ) perUnit = area; - if ( n == PER_CURB ) perUnit = curb; - if ( perUnit == 0.0 ) return 0.0; - - // --- buildup determined by loading time series - if ( Landuse[i].buildupFunc[p].funcType == EXTERNAL_BUILDUP ) - { - return landuse_getExternalBuildup(i, p, buildup/perUnit, tStep) * - perUnit; - } - - // --- determine equivalent days of current buildup - days = landuse_getBuildupDays(i, p, buildup/perUnit); - - // --- compute buildup after adding on time increment - days += tStep / SECperDAY; - return landuse_getBuildupMass(i, p, days) * perUnit; -} - -//============================================================================= - -double landuse_getBuildupDays(int i, int p, double buildup) -// -// Input: i = land use index -// p = pollutant index -// buildup = amount of pollutant buildup -// Output: returns number of days it takes for buildup to reach a given level -// Purpose: finds the number of days corresponding to a pollutant buildup. -// -{ - double c0 = Landuse[i].buildupFunc[p].coeff[0]; - double c1 = Landuse[i].buildupFunc[p].coeff[1]; - double c2 = Landuse[i].buildupFunc[p].coeff[2]; - - if ( buildup == 0.0 ) return 0.0; - if ( buildup >= c0 ) return Landuse[i].buildupFunc[p].maxDays; - switch (Landuse[i].buildupFunc[p].funcType) - { - case POWER_BUILDUP: - if ( c1*c2 == 0.0 ) return 0.0; - else return pow( (buildup/c1), (1.0/c2) ); - - case EXPON_BUILDUP: - if ( c0*c1 == 0.0 ) return 0.0; - else return -log(1. - buildup/c0) / c1; - - case SATUR_BUILDUP: - if ( c0 == 0.0 ) return 0.0; - else return buildup*c2 / (c0 - buildup); - - default: - return 0.0; - } -} - -//============================================================================= - -double landuse_getBuildupMass(int i, int p, double days) -// -// Input: i = land use index -// p = pollutant index -// days = time over which buildup has occurred (days) -// Output: returns mass of pollutant buildup (lbs or kg per area or curblength) -// Purpose: finds amount of buildup of pollutant on a land use. -// -{ - double b; - double c0 = Landuse[i].buildupFunc[p].coeff[0]; - double c1 = Landuse[i].buildupFunc[p].coeff[1]; - double c2 = Landuse[i].buildupFunc[p].coeff[2]; - - if ( days == 0.0 ) return 0.0; - if ( days >= Landuse[i].buildupFunc[p].maxDays ) return c0; - switch (Landuse[i].buildupFunc[p].funcType) - { - case POWER_BUILDUP: - b = c1 * pow(days, c2); - if ( b > c0 ) b = c0; - break; - - case EXPON_BUILDUP: - b = c0*(1.0 - exp(-days*c1)); - break; - - case SATUR_BUILDUP: - b = days*c0/(c2 + days); - break; - - default: b = 0.0; - } - return b; -} - -//============================================================================= - -double landuse_getAvgBmpEffic(int j, int p) -// -// Input: j = subcatchment index -// p = pollutant index -// Output: returns a BMP removal fraction for pollutant p -// Purpose: finds the overall average BMP removal achieved for pollutant p -// treated in subcatchment j. -// -{ - int i; - double r = 0.0; - for (i = 0; i < Nobjects[LANDUSE]; i++) - { - r += Subcatch[j].landFactor[i].fraction * - Landuse[i].washoffFunc[p].bmpEffic; - } - return r; -} - -//============================================================================= - -double landuse_getWashoffLoad(int i, int p, double area, - TLandFactor landFactor[], double runoff, double vOutflow) -// -// Input: i = land use index -// p = pollut. index -// area = sucatchment area (ft2) -// landFactor[] = array of land use data for subcatchment -// runoff = runoff flow generated by subcatchment (ft/sec) -// vOutflow = runoff volume leaving the subcatchment (ft3) -// Output: returns pollutant runoff load (mass) -// Purpose: computes pollutant load generated by a land use over a time step. -// -{ - double landuseArea; // area of current land use (ft2) - double buildup; // current pollutant buildup (lb or kg) - double washoffQual; // pollutant concentration in washoff (mass/ft3) - double washoffLoad; // pollutant washoff load over time step (lb or kg) - double bmpRemoval; // pollutant load removed by BMP treatment (lb or kg) - - // --- compute concen. of pollutant in washoff (mass/ft3) - buildup = landFactor[i].buildup[p]; - landuseArea = landFactor[i].fraction * area; - washoffQual = landuse_getWashoffQual(i, p, buildup, runoff, landuseArea); - - // --- compute washoff load exported (lbs or kg) from landuse - // (Pollut[].mcf converts from mg (or ug) mass units to lbs (or kg) - washoffLoad = washoffQual * vOutflow * landuseArea / area * Pollut[p].mcf; - - // --- if buildup modelled, reduce it by amount of washoff - if ( Landuse[i].buildupFunc[p].funcType != NO_BUILDUP || - buildup > washoffLoad ) - { - washoffLoad = MIN(washoffLoad, buildup); - buildup -= washoffLoad; - landFactor[i].buildup[p] = buildup; - } - - // --- otherwise add washoff to buildup mass balance totals - // so that things will balance - else - { - massbal_updateLoadingTotals(BUILDUP_LOAD, p, washoffLoad); - landFactor[i].buildup[p] = 0.0; - } - - // --- apply any BMP removal to washoff - bmpRemoval = Landuse[i].washoffFunc[p].bmpEffic * washoffLoad; - if ( bmpRemoval > 0.0 ) - { - massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, bmpRemoval); - washoffLoad -= bmpRemoval; - } - - // --- return washoff load converted back to mass (mg or ug) - return washoffLoad / Pollut[p].mcf; -} - -//============================================================================= - -double landuse_getWashoffQual(int i, int p, double buildup, double runoff, - double area) -// -// Input: i = land use index -// p = pollutant index -// buildup = current buildup over land use (lbs or kg) -// runoff = current runoff on subcatchment (ft/sec) -// area = area devoted to land use (ft2) -// Output: returns pollutant concentration in washoff (mass/ft3) -// Purpose: finds concentration of pollutant washed off a land use. -// -// Notes: "coeff" for each washoff function was previously adjusted to -// result in units of mass/sec -// -{ - double cWashoff = 0.0; - double coeff = Landuse[i].washoffFunc[p].coeff; - double expon = Landuse[i].washoffFunc[p].expon; - int func = Landuse[i].washoffFunc[p].funcType; - - // --- if no washoff function or no runoff, return 0 - if ( func == NO_WASHOFF || runoff == 0.0 ) return 0.0; - - // --- if buildup function exists but no current buildup, return 0 - if ( Landuse[i].buildupFunc[p].funcType != NO_BUILDUP && buildup == 0.0 ) - return 0.0; - - // --- Exponential Washoff function - if ( func == EXPON_WASHOFF ) - { - // --- evaluate washoff eqn. with runoff in in/hr (or mm/hr) - // and buildup converted from lbs (or kg) to concen. mass units - cWashoff = coeff * pow(runoff * UCF(RAINFALL), expon) * - buildup / Pollut[p].mcf; - cWashoff /= runoff * area; - } - - // --- Rating Curve Washoff function - else if ( func == RATING_WASHOFF ) - { - cWashoff = coeff * pow(runoff * area, expon-1.0); - } - - // --- Event Mean Concentration Washoff - else if ( func == EMC_WASHOFF ) - { - cWashoff = coeff; // coeff includes LperFT3 factor - } - return cWashoff; -} - -//============================================================================= - -double landuse_getCoPollutLoad(int p, double washoff[]) -// -// Input: p = pollutant index -// washoff = pollut. washoff rate (mass/sec) -// Output: returns washoff mass added by co-pollutant relation (mass) -// Purpose: finds washoff mass added by a co-pollutant of a given pollutant. -// -{ - int k; - double w; - - // --- check if pollutant p has a co-pollutant k - k = Pollut[p].coPollut; - if ( k >= 0 ) - { - // --- compute addition to washoff from co-pollutant - w = Pollut[p].coFraction * washoff[k]; - - // --- add washoff to buildup mass balance totals - // so that things will balance - massbal_updateLoadingTotals(BUILDUP_LOAD, p, w * Pollut[p].mcf); - return w; - } - return 0.0; -} - -//============================================================================= - -double landuse_getExternalBuildup(int i, int p, double buildup, double tStep) -// -// Input: i = landuse index -// p = pollutant index -// buildup = buildup at start of time step (mass/unit) -// tStep = time step (sec) -// Output: returns pollutant buildup at end of time interval (mass/unit) -// Purpose: finds pollutant buildup contributed by external loading over a -// given time step. -// -{ - double maxBuildup = Landuse[i].buildupFunc[p].coeff[0]; - double sf = Landuse[i].buildupFunc[p].coeff[1]; // scaling factor - int ts = (int)floor(Landuse[i].buildupFunc[p].coeff[2]); // time series index - double rate = 0.0; - - // --- no buildup increment at start of simulation - if (NewRunoffTime == 0.0) return 0.0; - - // --- get buildup rate (mass/unit/day) over the interval - if ( ts >= 0 ) - { - rate = sf * table_tseriesLookup(&Tseries[ts], - getDateTime(NewRunoffTime), FALSE); - } - - // --- compute buildup at end of time interval - buildup = buildup + rate * tStep / SECperDAY; - buildup = MIN(buildup, maxBuildup); - return buildup; -} diff --git a/src/lid.c b/src/lid.c deleted file mode 100644 index 7bb69dcef..000000000 --- a/src/lid.c +++ /dev/null @@ -1,2031 +0,0 @@ -//----------------------------------------------------------------------------- -// lid.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// This module handles all data processing involving LID (Low Impact -// Development) practices used to treat runoff for individual subcatchments -// within a project. The actual computation of LID performance is made by -// functions within the lidproc.c module. See LidTypes below for the types -// of LIDs that can be modeled. -// -// An LID process is described by the TLidProc data structure and consists of -// size-independent design data for the different vertical layers that make -// up a specific type of LID. The collection of these LID process designs is -// stored in the LidProcs array. -// -// When a member of LidProcs is to be deployed in a particular subcatchment, -// its sizing and treatment data are stored in a TLidUnit data structure. -// The collection of all TLidUnits deployed in a subcatchment is held in a -// TLidGroup list data structure. The LidGroups array contains a TLidGroup -// list for each subcatchment in the project. -// -// During a runoff time step, each subcatchment calls the lid_getRunoff() -// function to compute flux rates and a water balance through each layer -// of each LID unit in the subcatchment. The resulting outflows (runoff, -// drain flow, evaporation and infiltration) are added to those computed -// for the non-LID portion of the subcatchment. -// -// An option exists for the detailed time series of flux rates and storage -// levels for a specific LID unit to be written to a text file named by the -// user for viewing outside of the SWMM program. -// -// Update History -// ============== -// Build 5.1.008: -// - More input error reporting added. -// - Rooftop Disconnection added to the types of LIDs. -// - LID drain flows are now tracked separately. -// - LID drain flows can now be routed to separate outlets. -// - Check added to insure LID flows not returned to nonexistent pervious area. -// Build 5.1.009: -// - Fixed bug where LID's could return outflow to non-LID area when LIDs -// make up entire subcatchment. -// Build 5.1.010: -// - Support for new Modified Green Ampt infiltration model added. -// - Imported variable HasWetLids now properly initialized. -// - Initial state of reporting (lidUnit->rptFile->wasDry) changed to -// prevent duplicate printing of first line of detailed report file. -// Build 5.1.011: -// - The top of the storage layer is no longer used as a limit for an -// underdrain offset thus allowing upturned drains to be modeled. -// - Column headings for the detailed LID report file were modified. -// Build 5.1.012: -// - Redefined initialization of wasDry for LID reporting. -// Build 5.1.013: -// - Support added for LID units treating pervious area runoff. -// - Support added for open/closed head levels and multiplier v. head -// control curve for underdrain flow. -// - Support added for unclogging permeable pavement at fixed intervals. -// - Support added for pollutant removal in underdrain flow. -// Build 5.1.014: -// - Fixed bug in creating LidProcs when there are no subcatchments. -// - Fixed bug in adding underdrain pollutant loads to mass balances. -// Build 5.1.015: -// - Support added for mutiple infiltration methods within a project. -// Build 5.2.0: -// - Covered property added to RAIN_BARREL parameters -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "headers.h" -#include "lid.h" - -#define ERR_PAVE_LAYER " - check pavement layer parameters" -#define ERR_SOIL_LAYER " - check soil layer parameters" -#define ERR_STOR_LAYER " - check storage layer parameters" -#define ERR_SWALE_SURF " - check swale surface parameters" -#define ERR_GREEN_AMPT " - check subcatchment Green-Ampt parameters" -#define ERR_DRAIN_OFFSET " - drain offset exceeds storage height" -#define ERR_DRAIN_HEADS " - invalid drain open/closed heads" -#define ERR_SWALE_WIDTH " - invalid swale width" - -//----------------------------------------------------------------------------- -// Enumerations -//----------------------------------------------------------------------------- -enum LidLayerTypes { - SURF, // surface layer - SOIL, // soil layer - STOR, // storage layer - PAVE, // pavement layer - DRAINMAT, // drainage mat layer - DRAIN, // underdrain system - REMOVALS}; // pollutant removals - -//// Note: DRAINMAT must be placed before DRAIN so the two keywords can -/// be distinguished from one another when parsing a line of input. - -char* LidLayerWords[] = - {"SURFACE", "SOIL", "STORAGE", "PAVEMENT", "DRAINMAT", "DRAIN", - "REMOVALS", NULL}; - -char* LidTypeWords[] = - {"BC", //bio-retention cell - "RG", //rain garden - "GR", //green roof - "IT", //infiltration trench - "PP", //porous pavement - "RB", //rain barrel - "VS", //vegetative swale - "RD", //rooftop disconnection - NULL}; - -//----------------------------------------------------------------------------- -// Data Structures -//----------------------------------------------------------------------------- - -// LID List - list of LID units contained in an LID group -struct LidList -{ - TLidUnit* lidUnit; // ptr. to a LID unit - struct LidList* nextLidUnit; -}; -typedef struct LidList TLidList; - -// LID Group - collection of LID units applied to a specific subcatchment -struct LidGroup -{ - double pervArea; // amount of pervious area in group (ft2) - double flowToPerv; // total flow sent to pervious area (cfs) - double oldDrainFlow; // total drain flow in previous period (cfs) - double newDrainFlow; // total drain flow in current period (cfs) - TLidList* lidList; // list of LID units in the group -}; -typedef struct LidGroup* TLidGroup; - - -//----------------------------------------------------------------------------- -// Shared Variables -//----------------------------------------------------------------------------- -static TLidProc* LidProcs; // array of LID processes -static int LidCount; // number of LID processes -static TLidGroup* LidGroups; // array of LID process groups -static int GroupCount; // number of LID groups (subcatchments) - -static double EvapRate; // evaporation rate (ft/s) -static double NativeInfil; // native soil infil. rate (ft/s) -static double MaxNativeInfil; // native soil infil. rate limit (ft/s) - -//----------------------------------------------------------------------------- -// Imported Variables (from SUBCATCH.C) -//----------------------------------------------------------------------------- -// Volumes (ft3) for a subcatchment over a time step -extern double Vevap; // evaporation -extern double Vpevap; // pervious area evaporation -extern double Vinfil; // non-LID infiltration -extern double VlidInfil; // infiltration from LID units -extern double VlidIn; // impervious area flow to LID units -extern double VlidOut; // surface outflow from LID units -extern double VlidDrain; // drain outflow from LID units -extern double VlidReturn; // LID outflow returned to pervious area -extern char HasWetLids; // TRUE if any LIDs are wet - // (from RUNOFF.C) - -//----------------------------------------------------------------------------- -// External Functions (prototyped in lid.h) -//----------------------------------------------------------------------------- -// lid_create called by createObjects in project.c -// lid_delete called by deleteObjects in project.c -// lid_validate called by project_validate -// lid_initState called by project_init - -// lid_readProcParams called by parseLine in input.c -// lid_readGroupParams called by parseLine in input.c - -// lid_setOldGroupState called by subcatch_setOldState -// lid_setReturnQual called by findLidLoads in surfqual.c -// lid_getReturnQual called by subcatch_getRunon - -// lid_getPervArea called by subcatch_getFracPerv -// lid_getFlowToPerv called by subcatch_getRunon -// lid_getSurfaceDepth called by subcatch_getDepth -// lid_getDepthOnPavement called by sweptSurfacesDry in subcatch.c -// lid_getStoredVolume called by subcatch_getStorage -// lid_getRunon called by subcatch_getRunon -// lid_getRunoff called by subcatch_getRunoff - -// lid_addDrainRunon called by subcatch_getRunon -// lid_addDrainLoads called by surfqual_getWashoff -// lid_addDrainInflow called by addLidDrainInflows in routing.c - -// lid_writeSummary called by inputrpt_writeInput -// lid_writeWaterBalance called by statsrpt_writeReport - - -//----------------------------------------------------------------------------- -// Local Functions -//----------------------------------------------------------------------------- -static void freeLidGroup(int j); -static int readSurfaceData(int j, char* tok[], int ntoks); -static int readPavementData(int j, char* tok[], int ntoks); -static int readSoilData(int j, char* tok[], int ntoks); -static int readStorageData(int j, char* tok[], int ntoks); -static int readDrainData(int j, char* tok[], int ntoks); -static int readDrainMatData(int j, char* toks[], int ntoks); -static int readRemovalsData(int j, char* toks[], int ntoks); - -static int addLidUnit(int j, int k, int n, double x[], char* fname, - int drainSubcatch, int drainNode); -static int createLidRptFile(TLidUnit* lidUnit, char* fname); -static void initLidRptFile(char* title, char* lidID, char* subcatchID, - TLidUnit* lidUnit); -static void validateLidProc(int j); -static void validateLidGroup(int j); - -static int isLidPervious(int k); -static double getImpervAreaRunoff(int j); -static double getPervAreaRunoff(int j); -static double getSurfaceDepth(int subcatch); -static double getRainInflow(int j, TLidUnit* lidUnit); -static void findNativeInfil(int j, double tStep); - - -static void evalLidUnit(int j, TLidUnit* lidUnit, double lidArea, - double lidInflow, double tStep, double *qRunoff, - double *qDrain, double *qReturn); - -//============================================================================= - -void lid_create(int lidCount, int subcatchCount) -// -// Purpose: creates an array of LID objects. -// Input: n = number of LID processes -// Output: none -// -{ - int j; - - //... assign NULL values to LID arrays - LidProcs = NULL; - LidGroups = NULL; - LidCount = lidCount; - - //... create LID groups - GroupCount = subcatchCount; - if ( GroupCount > 0 ) - { - LidGroups = (TLidGroup *) calloc(GroupCount, sizeof(TLidGroup)); - if ( LidGroups == NULL ) - { - ErrorCode = ERR_MEMORY; - return; - } - } - - //... initialize LID groups - for (j = 0; j < GroupCount; j++) LidGroups[j] = NULL; - - //... create LID objects - if ( LidCount == 0 ) return; - LidProcs = (TLidProc *) calloc(LidCount, sizeof(TLidProc)); - if ( LidProcs == NULL ) - { - ErrorCode = ERR_MEMORY; - return; - } - - //... initialize LID objects - for (j = 0; j < LidCount; j++) - { - LidProcs[j].lidType = -1; - LidProcs[j].surface.thickness = 0.0; - LidProcs[j].surface.voidFrac = 1.0; - LidProcs[j].surface.roughness = 0.0; - LidProcs[j].surface.surfSlope = 0.0; - LidProcs[j].pavement.thickness = 0.0; - LidProcs[j].soil.thickness = 0.0; - LidProcs[j].storage.thickness = 0.0; - LidProcs[j].storage.kSat = 0.0; - LidProcs[j].drain.coeff = 0.0; - LidProcs[j].drain.offset = 0.0; - LidProcs[j].drainMat.thickness = 0.0; - LidProcs[j].drainMat.roughness = 0.0; - LidProcs[j].drainRmvl = NULL; - LidProcs[j].drainRmvl = (double *) - calloc(Nobjects[POLLUT], sizeof(double)); - if (LidProcs[j].drainRmvl == NULL) - { - ErrorCode = ERR_MEMORY; - return; - } - } -} - -//============================================================================= - -void lid_delete() -// -// Purpose: deletes all LID objects -// Input: none -// Output: none -// -{ - int j; - for (j = 0; j < GroupCount; j++) freeLidGroup(j); - FREE(LidGroups); - for (j = 0; j < LidCount; j++) FREE(LidProcs[j].drainRmvl); - FREE(LidProcs); - GroupCount = 0; - LidCount = 0; -} - -//============================================================================= - -void freeLidGroup(int j) -// -// Purpose: frees all LID units associated with a subcatchment. -// Input: j = group (or subcatchment) index -// Output: none -// -{ - TLidGroup lidGroup = LidGroups[j]; - TLidList* lidList; - TLidUnit* lidUnit; - TLidList* nextLidUnit; - - if ( lidGroup == NULL ) return; - lidList = lidGroup->lidList; - while (lidList) - { - lidUnit = lidList->lidUnit; - if ( lidUnit->rptFile ) - { - if ( lidUnit->rptFile->file ) fclose(lidUnit->rptFile->file); - free(lidUnit->rptFile); - } - nextLidUnit = lidList->nextLidUnit; - free(lidUnit); - free(lidList); - lidList = nextLidUnit; - } - free(lidGroup); - LidGroups[j] = NULL; -} - -//============================================================================= - -int lid_readProcParams(char* toks[], int ntoks) -// -// Purpose: reads LID process information from line of input data file -// Input: toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format for first line that defines a LID process is: -// LID_ID LID_Type -// -// Followed by some combination of lines below depending on LID_Type: -// LID_ID SURFACE -// LID_ID PAVEMENT -// LID_ID SOIL -// LID_ID STORAGE -// LID_ID DRAIN -// LID_ID DRAINMAT -// LID_ID REMOVALS -// -{ - int j, m; - - // --- check for minimum number of tokens - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that LID exists in database - j = project_findObject(LID, toks[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, toks[0]); - - // --- assign ID if not done yet - if ( LidProcs[j].ID == NULL ) - LidProcs[j].ID = project_findID(LID, toks[0]); - - // --- check if second token is the type of LID - m = findmatch(toks[1], LidTypeWords); - if ( m >= 0 ) - { - LidProcs[j].lidType = m; - return 0; - } - - // --- check if second token is name of LID layer - else m = findmatch(toks[1], LidLayerWords); - - // --- read input parameters for the identified layer - switch (m) - { - case SURF: return readSurfaceData(j, toks, ntoks); - case SOIL: return readSoilData(j, toks, ntoks); - case STOR: return readStorageData(j, toks, ntoks); - case PAVE: return readPavementData(j, toks, ntoks); - case DRAIN: return readDrainData(j, toks, ntoks); - case DRAINMAT: return readDrainMatData(j, toks, ntoks); - case REMOVALS: return readRemovalsData(j, toks, ntoks); - } - return error_setInpError(ERR_KEYWORD, toks[1]); -} - -//============================================================================= - -int lid_readGroupParams(char* toks[], int ntoks) -// -// Purpose: reads input data for a LID unit placed in a subcatchment. -// Input: toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of input data line is: -// Subcatch_ID LID_ID Number Area Width InitSat FromImp ToPerv -// (RptFile DrainTo FromPerv) -// where: -// Subcatch_ID = name of subcatchment -// LID_ID = name of LID process -// Number (n) = number of replicate units -// Area (x[0]) = area of each unit -// Width (x[1]) = outflow width of each unit -// InitSat (x[2]) = % that LID is initially saturated -// FromImp (x[3]) = % of impervious runoff sent to LID -// ToPerv (x[4]) = 1 if outflow goes to pervious sub-area; 0 if not -// RptFile = name of detailed results file (optional) -// DrainTo = name of subcatch/node for drain flow (optional) -// FromPerv (x[5]) = % of pervious runoff sent to LID -// -{ - int i, j, k, n; - double x[6]; - char* fname = NULL; - int drainSubcatch = -1, drainNode = -1; - - //... check for valid number of input tokens - if ( ntoks < 8 ) return error_setInpError(ERR_ITEMS, ""); - - //... find subcatchment - j = project_findObject(SUBCATCH, toks[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, toks[0]); - - //... find LID process in list of LID processes - k = project_findObject(LID, toks[1]); - if ( k < 0 ) return error_setInpError(ERR_NAME, toks[1]); - - //... get number of replicates - n = atoi(toks[2]); - if ( n < 0 ) return error_setInpError(ERR_NUMBER, toks[2]); - if ( n == 0 ) return 0; - - //... convert next 4 tokens to doubles - for (i = 3; i <= 7; i++) - { - if ( ! getDouble(toks[i], &x[i-3]) || x[i-3] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - //... check for valid percentages on tokens 5 & 6 (x[2] & x[3]) - for (i = 2; i <= 3; i++) if ( x[i] > 100.0 ) - return error_setInpError(ERR_NUMBER, toks[i+3]); - - //... read optional report file name - if ( ntoks >= 9 && strcmp(toks[8], "*") != 0 ) fname = toks[8]; - - //... read optional underdrain outlet - if ( ntoks >= 10 && strcmp(toks[9], "*") != 0 ) - { - drainSubcatch = project_findObject(SUBCATCH, toks[9]); - if ( drainSubcatch < 0 ) - { - drainNode = project_findObject(NODE, toks[9]); - if ( drainNode < 0 ) return error_setInpError(ERR_NAME, toks[9]); - } - } - - //... read percent of pervious area treated by LID unit - x[5] = 0.0; - if (ntoks >= 11) - { - if (!getDouble(toks[10], &x[5]) || x[5] < 0.0 || x[5] > 100.0) - return error_setInpError(ERR_NUMBER, toks[10]); - } - - //... create a new LID unit and add it to the subcatchment's LID group - return addLidUnit(j, k, n, x, fname, drainSubcatch, drainNode); -} - -//============================================================================= - -int addLidUnit(int j, int k, int n, double x[], char* fname, - int drainSubcatch, int drainNode) -// -// Purpose: adds an LID unit to a subcatchment's LID group. -// Input: j = subcatchment index -// k = LID control index -// n = number of replicate units -// x = LID unit's parameters -// fname = name of detailed performance report file -// drainSubcatch = index of subcatchment receiving underdrain flow -// drainNode = index of node receiving underdrain flow -// Output: returns an error code -// -{ - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... create a LID group (pointer to an LidGroup struct) - // if one doesn't already exist - lidGroup = LidGroups[j]; - if ( !lidGroup ) - { - lidGroup = (struct LidGroup *) malloc(sizeof(struct LidGroup)); - if ( !lidGroup ) return error_setInpError(ERR_MEMORY, ""); - lidGroup->lidList = NULL; - LidGroups[j] = lidGroup; - } - - //... create a new LID unit to add to the group - lidUnit = (TLidUnit *) malloc(sizeof(TLidUnit)); - if ( !lidUnit ) return error_setInpError(ERR_MEMORY, ""); - lidUnit->rptFile = NULL; - - //... add the LID unit to the group - lidList = (TLidList *) malloc(sizeof(TLidList)); - if ( !lidList ) - { - free(lidUnit); - return error_setInpError(ERR_MEMORY, ""); - } - lidList->lidUnit = lidUnit; - lidList->nextLidUnit = lidGroup->lidList; - lidGroup->lidList = lidList; - - //... assign parameter values to LID unit - lidUnit->lidIndex = k; - lidUnit->number = n; - lidUnit->area = x[0] / SQR(UCF(LENGTH)); - lidUnit->fullWidth = x[1] / UCF(LENGTH); - lidUnit->initSat = x[2] / 100.0; - lidUnit->fromImperv = x[3] / 100.0; - lidUnit->toPerv = (x[4] > 0.0); - lidUnit->fromPerv = x[5] / 100.0; - lidUnit->drainSubcatch = drainSubcatch; - lidUnit->drainNode = drainNode; - - //... open report file if it was supplied - if ( fname != NULL ) - { - if ( !createLidRptFile(lidUnit, fname) ) - return error_setInpError(ERR_RPT_FILE, fname); - } - return 0; -} - -//============================================================================= - -int createLidRptFile(TLidUnit* lidUnit, char* fname) -{ - TLidRptFile* rptFile; - - rptFile = (TLidRptFile *) malloc(sizeof(TLidRptFile)); - if ( rptFile == NULL ) return 0; - lidUnit->rptFile = rptFile; - rptFile->file = fopen(fname, "wt"); - if ( rptFile->file == NULL ) return 0; - return 1; -} - -//============================================================================= - -int readSurfaceData(int j, char* toks[], int ntoks) -// -// Purpose: reads surface layer data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID SURFACE StorageHt VegVolFrac Roughness SurfSlope SideSlope -// -{ - int i; - double x[5]; - - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 2; i < 7; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - if ( x[1] >= 1.0 ) return error_setInpError(ERR_NUMBER, toks[3]); - if ( x[0] == 0.0 ) x[1] = 0.0; - - LidProcs[j].surface.thickness = x[0] / UCF(RAINDEPTH); - LidProcs[j].surface.voidFrac = 1.0 - x[1]; - LidProcs[j].surface.roughness = x[2]; - LidProcs[j].surface.surfSlope = x[3] / 100.0; - LidProcs[j].surface.sideSlope = x[4]; - return 0; -} - -//============================================================================= - -int readPavementData(int j, char* toks[], int ntoks) -// -// Purpose: reads pavement layer data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID PAVEMENT Thickness VoidRatio FracImperv Permeability ClogFactor -// (RegenDays RegenDegree) -// -{ - int i; - double x[7]; - - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 2; i < 7; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - // ... read optional clogging regeneration properties - x[5] = 0.0; - if (ntoks > 7) - { - if (!getDouble(toks[7], &x[5]) || x[5] < 0.0) - return error_setInpError(ERR_NUMBER, toks[7]); - } - x[6] = 0.0; - if (ntoks > 8) - { - if (!getDouble(toks[8], &x[6]) || x[6] < 0.0 || x[6] > 1.0) - return error_setInpError(ERR_NUMBER, toks[8]); - } - - //... convert void ratio to void fraction - x[1] = x[1]/(x[1] + 1.0); - - LidProcs[j].pavement.thickness = x[0] / UCF(RAINDEPTH); - LidProcs[j].pavement.voidFrac = x[1]; - LidProcs[j].pavement.impervFrac = x[2]; - LidProcs[j].pavement.kSat = x[3] / UCF(RAINFALL); - LidProcs[j].pavement.clogFactor = x[4]; - LidProcs[j].pavement.regenDays = x[5]; - LidProcs[j].pavement.regenDegree = x[6]; - return 0; -} - -//============================================================================= - -int readSoilData(int j, char* toks[], int ntoks) -// -// Purpose: reads soil layer data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID SOIL Thickness Porosity FieldCap WiltPt Ksat Kslope Suction -// -{ - int i; - double x[7]; - - if ( ntoks < 9 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 2; i < 9; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - LidProcs[j].soil.thickness = x[0] / UCF(RAINDEPTH); - LidProcs[j].soil.porosity = x[1]; - LidProcs[j].soil.fieldCap = x[2]; - LidProcs[j].soil.wiltPoint = x[3]; - LidProcs[j].soil.kSat = x[4] / UCF(RAINFALL); - LidProcs[j].soil.kSlope = x[5]; - LidProcs[j].soil.suction = x[6] / UCF(RAINDEPTH); - return 0; -} - -//============================================================================= - -int readStorageData(int j, char* toks[], int ntoks) -// -// Purpose: reads drainage layer data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID STORAGE Thickness VoidRatio Ksat ClogFactor (YES/NO) -// -{ - int i; - int covered = FALSE; - double x[6]; - - //... read numerical parameters - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 2; i < 6; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - //... check if rain barrel is covered - if (ntoks > 6) - { - if (match(toks[6], w_YES)) - covered = TRUE; - } - - //... convert void ratio to void fraction - x[1] = x[1]/(x[1] + 1.0); - - //... save parameters to LID storage layer structure - LidProcs[j].storage.thickness = x[0] / UCF(RAINDEPTH); - LidProcs[j].storage.voidFrac = x[1]; - LidProcs[j].storage.kSat = x[2] / UCF(RAINFALL); - LidProcs[j].storage.clogFactor = x[3]; - LidProcs[j].storage.covered = covered; - return 0; -} - -//============================================================================= - -int readDrainData(int j, char* toks[], int ntoks) -// -// Purpose: reads underdrain data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID DRAIN coeff expon offset delay hOpen hClose curve -// -{ - int i; - double x[6]; - - //... read numerical parameters - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 0; i < 6; i++) x[i] = 0.0; - for (i = 2; i < 8; i++) - { - if ( (ntoks > i) && (! getDouble(toks[i], &x[i-2]) || x[i-2]) < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - i = -1; - if ( ntoks >= 9 ) - { - i = project_findObject(CURVE, toks[8]); - if (i < 0) return error_setInpError(ERR_NAME, toks[8]); - } - - //... save parameters to LID drain layer structure - LidProcs[j].drain.coeff = x[0]; - LidProcs[j].drain.expon = x[1]; - LidProcs[j].drain.offset = x[2] / UCF(RAINDEPTH); - LidProcs[j].drain.delay = x[3] * 3600.0; - LidProcs[j].drain.hOpen = x[4] / UCF(RAINDEPTH); - LidProcs[j].drain.hClose = x[5] / UCF(RAINDEPTH); - LidProcs[j].drain.qCurve = i; - return 0; -} - -//============================================================================= - -int readDrainMatData(int j, char* toks[], int ntoks) -// -// Purpose: reads drainage mat data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID DRAINMAT thickness voidRatio roughness -// -{ - int i; - double x[3]; - - //... read numerical parameters - if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); - if ( LidProcs[j].lidType != GREEN_ROOF ) return 0; - for (i = 2; i < 5; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - //... save parameters to LID drain layer structure - LidProcs[j].drainMat.thickness = x[0] / UCF(RAINDEPTH);; - LidProcs[j].drainMat.voidFrac = x[1]; - LidProcs[j].drainMat.roughness = x[2]; - return 0; -} - -//============================================================================= - -int readRemovalsData(int j, char* toks[], int ntoks) -// -// Purpose: reads pollutant removal data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID REMOVALS pollut1 %removal1 pollut2 %removal2 ... -// -{ - int i = 2; - int p; - double rmvl; - - //... start with 3rd token - if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); - while (ntoks > i) - { - //... find pollutant index from its name - p = project_findObject(POLLUT, toks[i]); - if (p < 0) return error_setInpError(ERR_NAME, toks[i]); - - //... check that a next token exists - i++; - if (ntoks == i) return error_setInpError(ERR_ITEMS, ""); - - //... get the % removal value from the next token - if (!getDouble(toks[i], &rmvl) || rmvl < 0.0 || rmvl > 100.0) - return error_setInpError(ERR_NUMBER, toks[i]); - - //... save the pollutant removal for the LID process as a fraction - LidProcs[j].drainRmvl[p] = rmvl / 100.0; - i++; - } - return 0; -} -//============================================================================= - -void lid_writeSummary() -// -// Purpose: writes summary of LID processes used to report file. -// Input: none -// Output: none -// -{ - int j, k; - double pctArea; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - fprintf(Frpt.file, "\n"); - fprintf(Frpt.file, "\n"); - fprintf(Frpt.file, "\n *******************"); - fprintf(Frpt.file, "\n LID Control Summary"); - fprintf(Frpt.file, "\n *******************"); - - - fprintf(Frpt.file, -"\n No. of Unit Unit %% Area %% Imperv %% Perv"); //(5.1.013) - fprintf(Frpt.file, // -"\n Subcatchment LID Control Units Area Width Covered Treated Treated"); // - fprintf(Frpt.file, // -"\n ---------------------------------------------------------------------------------------------------"); // - - for (j = 0; j < GroupCount; j++) - { - lidGroup = LidGroups[j]; - if ( lidGroup == NULL ) continue; - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - pctArea = lidUnit->area * lidUnit->number / Subcatch[j].area * 100.0; - fprintf(Frpt.file, "\n %-16s %-16s", Subcatch[j].ID, LidProcs[k].ID); - fprintf(Frpt.file, "%6d %10.2f %10.2f %10.2f %10.2f %10.2f", - lidUnit->number, lidUnit->area * SQR(UCF(LENGTH)), - lidUnit->fullWidth * UCF(LENGTH), pctArea, - lidUnit->fromImperv*100.0, lidUnit->fromPerv*100.0); - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_validate() -// -// Purpose: validates LID process and group parameters. -// Input: none -// Output: none -// -{ - int j; - for (j = 0; j < LidCount; j++) validateLidProc(j); - for (j = 0; j < GroupCount; j++) validateLidGroup(j); -} - -//============================================================================= - -void validateLidProc(int j) -// -// Purpose: validates LID process parameters. -// Input: j = LID process index -// Output: none -// -{ - int layerMissing = FALSE; - - //... check that LID type was supplied - if ( LidProcs[j].lidType < 0 ) - { - report_writeErrorMsg(ERR_LID_TYPE, LidProcs[j].ID); - return; - } - - //... check that required layers were defined - switch (LidProcs[j].lidType) - { - case BIO_CELL: - case RAIN_GARDEN: - if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; - break; - case GREEN_ROOF: - if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; - if ( LidProcs[j].drainMat.thickness <= 0.0) layerMissing = TRUE; - break; - case POROUS_PAVEMENT: - if ( LidProcs[j].pavement.thickness <= 0.0 ) layerMissing = TRUE; - break; - case INFIL_TRENCH: - if ( LidProcs[j].storage.thickness <= 0.0 ) layerMissing = TRUE; - break; - } - if ( layerMissing ) - { - report_writeErrorMsg(ERR_LID_LAYER, LidProcs[j].ID); - return; - } - - //... check pavement layer parameters - if ( LidProcs[j].lidType == POROUS_PAVEMENT ) - { - if ( LidProcs[j].pavement.thickness <= 0.0 - || LidProcs[j].pavement.kSat <= 0.0 - || LidProcs[j].pavement.voidFrac <= 0.0 - || LidProcs[j].pavement.voidFrac > 1.0 - || LidProcs[j].pavement.impervFrac > 1.0 ) - - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_PAVE_LAYER, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... check soil layer parameters - if ( LidProcs[j].soil.thickness > 0.0 ) - { - if ( LidProcs[j].soil.porosity <= 0.0 - || LidProcs[j].soil.fieldCap >= LidProcs[j].soil.porosity - || LidProcs[j].soil.wiltPoint >= LidProcs[j].soil.fieldCap - || LidProcs[j].soil.kSat <= 0.0 - || LidProcs[j].soil.kSlope < 0.0 ) - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_SOIL_LAYER, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... check storage layer parameters - if ( LidProcs[j].storage.thickness > 0.0 ) - { - if ( LidProcs[j].storage.voidFrac <= 0.0 || - LidProcs[j].storage.voidFrac > 1.0 ) - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_STOR_LAYER, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... if no storage layer adjust void fraction and drain offset - else - { - LidProcs[j].storage.voidFrac = 1.0; - LidProcs[j].drain.offset = 0.0; - } - - //... check for invalid drain open/closed heads - if (LidProcs[j].drain.hOpen > 0.0 && - LidProcs[j].drain.hOpen <= LidProcs[j].drain.hClose) - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_DRAIN_HEADS, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - - //... compute the surface layer's overland flow constant (alpha) - if ( LidProcs[j].lidType == VEG_SWALE ) - { - if ( LidProcs[j].surface.roughness * - LidProcs[j].surface.surfSlope <= 0.0 || - LidProcs[j].surface.thickness == 0.0 - ) - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_SWALE_SURF, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - else LidProcs[j].surface.alpha = - 1.49 * sqrt(LidProcs[j].surface.surfSlope) / - LidProcs[j].surface.roughness; - } - else - { - //... compute surface overland flow coeff. - if ( LidProcs[j].surface.roughness > 0.0 ) - LidProcs[j].surface.alpha = 1.49 / LidProcs[j].surface.roughness * - sqrt(LidProcs[j].surface.surfSlope); - else LidProcs[j].surface.alpha = 0.0; - } - - //... compute drainage mat layer's flow coeff. - if ( LidProcs[j].drainMat.roughness > 0.0 ) - { - LidProcs[j].drainMat.alpha = 1.49 / LidProcs[j].drainMat.roughness * - sqrt(LidProcs[j].surface.surfSlope); - } - else LidProcs[j].drainMat.alpha = 0.0; - - - //... convert clogging factors to void volume basis - if ( LidProcs[j].pavement.thickness > 0.0 ) - { - LidProcs[j].pavement.clogFactor *= - LidProcs[j].pavement.thickness * LidProcs[j].pavement.voidFrac * - (1.0 - LidProcs[j].pavement.impervFrac); - } - if ( LidProcs[j].storage.thickness > 0.0 ) - { - LidProcs[j].storage.clogFactor *= - LidProcs[j].storage.thickness * LidProcs[j].storage.voidFrac; - } - else LidProcs[j].storage.clogFactor = 0.0; - - //... for certain LID types, immediate overflow of excess surface water - // occurs if either the surface roughness or slope is zero - LidProcs[j].surface.canOverflow = TRUE; - switch (LidProcs[j].lidType) - { - case ROOF_DISCON: LidProcs[j].surface.canOverflow = FALSE; break; - case INFIL_TRENCH: - case POROUS_PAVEMENT: - case BIO_CELL: - case RAIN_GARDEN: - case GREEN_ROOF: - if ( LidProcs[j].surface.alpha > 0.0 ) - LidProcs[j].surface.canOverflow = FALSE; - } - - //... rain barrels have 100% void space and impermeable bottom - if ( LidProcs[j].lidType == RAIN_BARREL ) - { - LidProcs[j].storage.voidFrac = 1.0; - LidProcs[j].storage.kSat = 0.0; - } - - //... set storage layer parameters of a green roof - if ( LidProcs[j].lidType == GREEN_ROOF ) - { - LidProcs[j].storage.thickness = LidProcs[j].drainMat.thickness; - LidProcs[j].storage.voidFrac = LidProcs[j].drainMat.voidFrac; - LidProcs[j].storage.clogFactor = 0.0; - LidProcs[j].storage.kSat = 0.0; - } -} - -//============================================================================= - -void validateLidGroup(int j) -// -// Purpose: validates properties of LID units grouped in a subcatchment. -// Input: j = subcatchment index -// Output: returns 1 if data are valid, 0 if not -// -{ - int k; - double p[3]; - double totalArea = Subcatch[j].area; - double totalLidArea = 0.0; - double fromImperv = 0.0; - double fromPerv = 0.0; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - lidGroup = LidGroups[j]; - if ( lidGroup == NULL ) return; - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - - //... update contributing fractions - totalLidArea += (lidUnit->area * lidUnit->number); - fromImperv += lidUnit->fromImperv; - fromPerv += lidUnit->fromPerv; - - //... assign biocell soil layer infiltration parameters - lidUnit->soilInfil.Ks = 0.0; - if ( LidProcs[k].soil.thickness > 0.0 ) - { - p[0] = LidProcs[k].soil.suction * UCF(RAINDEPTH); - p[1] = LidProcs[k].soil.kSat * UCF(RAINFALL); - p[2] = (LidProcs[k].soil.porosity - LidProcs[k].soil.wiltPoint) * - (1.0 - lidUnit->initSat); - if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) - { - sstrncpy(Msg, LidProcs[k].ID, MAXMSG); - sstrcat(Msg, ERR_SOIL_LAYER, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... assign vegetative swale infiltration parameters - if ( LidProcs[k].lidType == VEG_SWALE ) - { - if ( Subcatch[j].infilModel == GREEN_AMPT || - Subcatch[j].infilModel == MOD_GREEN_AMPT ) - { - grnampt_getParams(j, p); - if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) - { - sstrncpy(Msg, LidProcs[k].ID, MAXMSG); - sstrcat(Msg, ERR_GREEN_AMPT, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - if ( lidUnit->fullWidth <= 0.0 ) - { - sstrncpy(Msg, LidProcs[k].ID, MAXMSG); - sstrcat(Msg, ERR_SWALE_WIDTH, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... LID unit cannot send outflow back to subcatchment's - // pervious area if none exists - if ( Subcatch[j].fracImperv >= 0.999 ) lidUnit->toPerv = 0; - - //... assign drain outlet if not set by user - if ( lidUnit->drainNode == -1 && lidUnit->drainSubcatch == -1 ) - { - lidUnit->drainNode = Subcatch[j].outNode; - lidUnit->drainSubcatch = Subcatch[j].outSubcatch; - } - lidList = lidList->nextLidUnit; - } - - //... check contributing area fractions - if ( totalLidArea > 1.001 * totalArea ) - { - report_writeErrorMsg(ERR_LID_AREAS, Subcatch[j].ID); - } - if ( fromImperv > 1.001 || fromPerv > 1.001 ) - { - report_writeErrorMsg(ERR_LID_CAPTURE_AREA, Subcatch[j].ID); - } - - //... Make subcatchment LID area equal total area if the two are close - if ( totalLidArea > 0.999 * totalArea ) totalLidArea = totalArea; - Subcatch[j].lidArea = totalLidArea; -} - -//============================================================================= - -void lid_initState() -// -// Purpose: initializes the internal state of each LID in a subcatchment. -// Input: none -// Output: none -// -{ - int i, j, k; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - double initVol; - double initDryTime = StartDryDays * SECperDAY; - - HasWetLids = FALSE; - for (j = 0; j < GroupCount; j++) - { - //... check if group exists - lidGroup = LidGroups[j]; - if ( lidGroup == NULL ) continue; - - //... initialize group variables - lidGroup->pervArea = 0.0; - lidGroup->flowToPerv = 0.0; - lidGroup->oldDrainFlow = 0.0; - lidGroup->newDrainFlow = 0.0; - - //... examine each LID in the group - lidList = lidGroup->lidList; - while ( lidList ) - { - //... initialize depth & moisture content - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - lidUnit->surfaceDepth = 0.0; - lidUnit->storageDepth = 0.0; - lidUnit->soilMoisture = 0.0; - lidUnit->paveDepth = 0.0; - lidUnit->dryTime = initDryTime; - lidUnit->volTreated = 0.0; - lidUnit->nextRegenDay = LidProcs[k].pavement.regenDays; - initVol = 0.0; - if ( LidProcs[k].soil.thickness > 0.0 ) - { - lidUnit->soilMoisture = LidProcs[k].soil.wiltPoint + - lidUnit->initSat * (LidProcs[k].soil.porosity - - LidProcs[k].soil.wiltPoint); - initVol += lidUnit->soilMoisture * LidProcs[k].soil.thickness; - } - if ( LidProcs[k].storage.thickness > 0.0 ) - { - lidUnit->storageDepth = lidUnit->initSat * - LidProcs[k].storage.thickness; - initVol += lidUnit->storageDepth * LidProcs[k].storage.voidFrac; - } - if ( LidProcs[k].drainMat.thickness > 0.0 ) - { - lidUnit->storageDepth = lidUnit->initSat * - LidProcs[k].drainMat.thickness; - initVol += lidUnit->storageDepth * LidProcs[k].drainMat.voidFrac; - } - if ( lidUnit->initSat > 0.0 ) HasWetLids = TRUE; - - //... initialize water balance totals - lidproc_initWaterBalance(lidUnit, initVol); - lidUnit->volTreated = 0.0; - - //... initialize report file for the LID - if ( lidUnit->rptFile ) - { - initLidRptFile(Title[0], LidProcs[k].ID, Subcatch[j].ID, lidUnit); - } - - //... initialize drain flows - lidUnit->oldDrainFlow = 0.0; - lidUnit->newDrainFlow = 0.0; - - //... set previous flux rates to 0 - for (i = 0; i < MAX_LAYERS; i++) - { - lidUnit->oldFluxRates[i] = 0.0; - } - - //... initialize infiltration state variables - if ( lidUnit->soilInfil.Ks > 0.0 ) - grnampt_initState(&(lidUnit->soilInfil)); - - //... add contribution to pervious LID area - if ( isLidPervious(lidUnit->lidIndex) ) - lidGroup->pervArea += (lidUnit->area * lidUnit->number); - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_setOldGroupState(int j) -// -// Purpose: saves the current drain flow rate for the LIDs in a subcatchment. -// Input: j = subcatchment index -// Output: none -// -{ - TLidList* lidList; - if ( LidGroups[j] != NULL ) - { - LidGroups[j]->oldDrainFlow = LidGroups[j]->newDrainFlow; - LidGroups[j]->newDrainFlow = 0.0; - lidList = LidGroups[j]->lidList; - while (lidList) - { - lidList->lidUnit->oldDrainFlow = lidList->lidUnit->newDrainFlow; - lidList->lidUnit->newDrainFlow = 0.0; - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -int isLidPervious(int k) -// -// Purpose: determines if a LID process allows infiltration or not. -// Input: k = LID process index -// Output: returns 1 if process is pervious or 0 if not -// -{ - return ( LidProcs[k].storage.thickness == 0.0 || - LidProcs[k].storage.kSat > 0.0 ); -} - -//============================================================================= - -double getSurfaceDepth(int j) -// -// Purpose: computes the depth (volume per unit area) of ponded water on the -// surface of all LIDs within a subcatchment. -// Input: j = subcatchment index -// Output: returns volumetric depth of ponded water (ft) -// -{ - int k; - double depth = 0.0; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - lidGroup = LidGroups[j]; - if ( lidGroup == NULL ) return 0.0; - if ( Subcatch[j].lidArea == 0.0 ) return 0.0; - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - depth += lidUnit->surfaceDepth * LidProcs[k].surface.voidFrac * - lidUnit->area * lidUnit->number; - lidList = lidList->nextLidUnit; - } - return depth / Subcatch[j].lidArea; -} - -//============================================================================= - -double lid_getPervArea(int j) -// -// Purpose: retrieves amount of pervious LID area in a subcatchment. -// Input: j = subcatchment index -// Output: returns amount of pervious LID area (ft2) -// -{ - if ( LidGroups[j] ) return LidGroups[j]->pervArea; - else return 0.0; -} - -//============================================================================= - -double lid_getFlowToPerv(int j) -// -// Purpose: retrieves flow returned from LID treatment to pervious area of -// a subcatchment. -// Input: j = subcatchment index -// Output: returns flow returned to pervious area (cfs) -// -{ - if ( LidGroups[j] != NULL ) return LidGroups[j]->flowToPerv; - return 0.0; -} - -//============================================================================= - -double lid_getStoredVolume(int j) -// -// Purpose: computes stored volume of water for all LIDs -// grouped within a subcatchment. -// Input: j = subcatchment index -// Output: returns stored volume of water (ft3) -// -{ - double total = 0.0; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - lidGroup = LidGroups[j]; - if ( lidGroup == NULL || Subcatch[j].lidArea == 0.0 ) return 0.0; - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - total += lidUnit->waterBalance.finalVol * lidUnit->area * lidUnit->number; - lidList = lidList->nextLidUnit; - } - return total; -} - -//============================================================================= - -double lid_getDrainFlow(int j, int timePeriod) -// -// Purpose: returns flow from all of a subcatchment's LID drains for -// a designated time period -// Input: j = subcatchment index -// timePeriod = either PREVIOUS or CURRENT -// Output: total drain flow (cfs) from the subcatchment. -{ - if ( LidGroups[j] != NULL ) - { - if ( timePeriod == PREVIOUS ) return LidGroups[j]->oldDrainFlow; - else return LidGroups[j]->newDrainFlow; - } - return 0.0; -} - -//============================================================================= - -void lid_addDrainLoads(int j, double c[], double tStep) -// -// Purpose: adds pollutant loads routed from drains to system -// mass balance totals. -// Input: j = subcatchment index -// c = array of pollutant washoff concentrations (mass/L) -// tStep = time step (sec) -// Output: none. -// -{ - int isRunoffLoad; // true if drain becomes external runoff load - int p; // pollutant index - double r; // pollutant fractional removal - double w; // pollutant mass load (lb or kg) - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... check if LID group exists - lidGroup = LidGroups[j]; - if ( lidGroup != NULL ) - { - //... examine each LID unit in the group - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - - //... see if unit's drain flow becomes external runoff - isRunoffLoad = (lidUnit->drainNode >= 0 || - lidUnit->drainSubcatch == j); - - //... for each pollutant not routed back on to subcatchment surface - if (!lidUnit->toPerv) for (p = 0; p < Nobjects[POLLUT]; p++) - { - //... get mass load flowing through the drain - w = lidUnit->newDrainFlow * c[p] * tStep * LperFT3 * Pollut[p].mcf; - - //... get fractional removal for this load - r = LidProcs[lidUnit->lidIndex].drainRmvl[p]; - - //... update system mass balance totals - massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, r*w); - if (isRunoffLoad) - massbal_updateLoadingTotals(RUNOFF_LOAD, p, w*(1.0 - r)); - } - - // process next LID unit in the group - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_addDrainRunon(int j) -// -// Purpose: adds drain flows from LIDs in a given subcatchment to the -// subcatchments that were designated to receive them -// Input: j = index of subcatchment contributing underdrain flows -// Output: none. -// -{ - int i; // index of an LID unit's LID process - int k; // index of subcatchment receiving LID drain flow - int p; // pollutant index - double q; // drain flow rate (cfs) - double w; // mass of polllutant from drain flow - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... check if LID group exists - lidGroup = LidGroups[j]; - if ( lidGroup != NULL ) - { - //... examine each LID in the group - lidList = lidGroup->lidList; - while ( lidList ) - { - //... see if LID's drain discharges to another subcatchment - lidUnit = lidList->lidUnit; - i = lidUnit->lidIndex; - k = lidUnit->drainSubcatch; - if ( k >= 0 && k != j ) - { - //... distribute drain flow across subcatchment's areas - q = lidUnit->oldDrainFlow; - subcatch_addRunonFlow(k, q); - - //... add pollutant loads from drain to subcatchment - // (newQual[] contains loading rate (mass/sec) at this - // point which is converted later on to a concentration) - for (p = 0; p < Nobjects[POLLUT]; p++) - { - w = q * Subcatch[j].oldQual[p] * LperFT3; - w = w * (1.0 - LidProcs[i].drainRmvl[p]); - Subcatch[k].newQual[p] += w; - } - } - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_addDrainInflow(int j, double f) -// -// Purpose: adds LID drain flow to conveyance system nodes -// Input: j = subcatchment index -// f = time interval weighting factor -// Output: none. -// -// Note: this function updates the total lateral flow (Node[].newLatFlow) -// and pollutant mass (Node[].newQual[]) inflow seen by nodes that -// receive drain flow from the LID units in subcatchment j. -{ - int i, // LID process index - k, // node index - p; // pollutant index - double q, // drain flow (cfs) - w, w1, w2; // pollutant mass loads (mass/sec) - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... check if LID group exists - lidGroup = LidGroups[j]; - if ( lidGroup != NULL ) - { - //... examine each LID in the group - lidList = lidGroup->lidList; - while ( lidList ) - { - //... see if LID's drain discharges to conveyance system node - lidUnit = lidList->lidUnit; - i = lidUnit->lidIndex; - k = lidUnit->drainNode; - if ( k >= 0 ) - { - //... add drain flow to node's wet weather inflow - q = (1.0 - f) * lidUnit->oldDrainFlow + f * lidUnit->newDrainFlow; - Node[k].newLatFlow += q; - massbal_addInflowFlow(WET_WEATHER_INFLOW, q); - - //... add pollutant load, based on parent subcatchment quality - for (p = 0; p < Nobjects[POLLUT]; p++) - { - //... get previous & current drain loads - w1 = lidUnit->oldDrainFlow * Subcatch[j].oldQual[p]; - w2 = lidUnit->newDrainFlow * Subcatch[j].newQual[p]; - - //... add interpolated load to node's wet weather loading - w = (1.0 - f) * w1 + f * w2; - w = w * (1.0 - LidProcs[i].drainRmvl[p]); - Node[k].newQual[p] += w; - massbal_addInflowQual(WET_WEATHER_INFLOW, p, w); - } - } - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_getRunoff(int j, double tStep) -// -// Purpose: computes runoff and drain flows from the LIDs in a subcatchment. -// Input: j = subcatchment index -// tStep = time step (sec) -// Output: updates following global quantities after LID treatment applied: -// Vevap, Vpevap, VlidInfil, VlidIn, VlidOut, VlidDrain. -// -{ - TLidGroup theLidGroup; // group of LIDs placed in the subcatchment - TLidList* lidList; // list of LID units in the group - TLidUnit* lidUnit; // a member of the list of LID units - double lidArea; // area of an LID unit - double qImperv = 0.0; // runoff from impervious areas (cfs) - double qPerv = 0.0; // runoff from pervious areas (cfs) - double lidInflow = 0.0; // inflow to an LID unit (ft/s) - double qRunoff = 0.0; // surface runoff from all LID units (cfs) - double qDrain = 0.0; // drain flow from all LID units (cfs) - double qReturn = 0.0; // LID outflow returned to pervious area (cfs) - - //... return if there are no LID's - theLidGroup = LidGroups[j]; - if ( !theLidGroup ) return; - lidList = theLidGroup->lidList; - if ( !lidList ) return; - - //... determine if evaporation can occur - EvapRate = Evap.rate; - if ( Evap.dryOnly && Subcatch[j].rainfall > 0.0 ) EvapRate = 0.0; - - //... find subcatchment's infiltration rate into native soil - findNativeInfil(j, tStep); - - //... get impervious and pervious area runoff from non-LID - // portion of subcatchment (cfs) - if ( Subcatch[j].area > Subcatch[j].lidArea ) - { - qImperv = getImpervAreaRunoff(j); - qPerv = getPervAreaRunoff(j); - } - - //... evaluate performance of each LID unit placed in the subcatchment - while ( lidList ) - { - //... find area of the LID unit - lidUnit = lidList->lidUnit; - lidArea = lidUnit->area * lidUnit->number; - - //... if LID unit has area, evaluate its performance - if ( lidArea > 0.0 ) - { - //... find runoff from non-LID area treated by LID area (ft/sec) - lidInflow = (qImperv * lidUnit->fromImperv + - qPerv * lidUnit->fromPerv) / lidArea; - - //... update total runoff volume treated - VlidIn += lidInflow * lidArea * tStep; - - //... add rainfall onto LID inflow (ft/s) - lidInflow = lidInflow + getRainInflow(j, lidUnit); - - // ... add upstream runon only if LID occupies full subcatchment - if ( Subcatch[j].area == Subcatch[j].lidArea ) - { - lidInflow += Subcatch[j].runon; - } - - //... evaluate the LID unit's performance, updating the LID group's - // total surface runoff, drain flow, and flow returned to - // pervious area - evalLidUnit(j, lidUnit, lidArea, lidInflow, tStep, - &qRunoff, &qDrain, &qReturn); - } - lidList = lidList->nextLidUnit; - } - - //... save the LID group's total drain & return flows - theLidGroup->newDrainFlow = qDrain; - theLidGroup->flowToPerv = qReturn; - - //... save the LID group's total surface, drain and return flow volumes - VlidOut = qRunoff * tStep; - VlidDrain = qDrain * tStep; - VlidReturn = qReturn * tStep; -} - -//============================================================================= - -void findNativeInfil(int j, double tStep) -// -// Purpose: determines a subcatchment's current infiltration rate into -// its native soil. -// Input: j = subcatchment index -// tStep = time step (sec) -// Output: sets values for module-level variables NativeInfil -// -{ - double nonLidArea; - - //... subcatchment has non-LID pervious area - nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; - if ( nonLidArea > 0.0 && Subcatch[j].fracImperv < 1.0 ) - { - NativeInfil = Vinfil / nonLidArea / tStep; - } - - //... otherwise find infil. rate for the subcatchment's rainfall + runon - else - { - NativeInfil = infil_getInfil(j, tStep, - Subcatch[j].rainfall, - Subcatch[j].runon, - getSurfaceDepth(j)); - } - - //... see if there is any groundwater-imposed limit on infil. - if ( !IgnoreGwater && Subcatch[j].groundwater ) - { - MaxNativeInfil = Subcatch[j].groundwater->maxInfilVol / tStep; - } - else MaxNativeInfil = BIG; -} - -//============================================================================= - -double getRainInflow(int j, TLidUnit* lidUnit) -// -// Purpose: gets rainfall inflow to an LID unit. -// Input: j = subcatchment index -// lidUnit = ptr. to an LID unit -// Output: returns rainfall rate over the LID unit (ft/sec) -// -{ - TLidProc* lidProc = &LidProcs[lidUnit->lidIndex]; - - if (lidProc->lidType == RAIN_BARREL && - lidProc->storage.covered == TRUE) return 0.0; - return Subcatch[j].rainfall; -} - -//============================================================================= - -double getImpervAreaRunoff(int j) -// -// Purpose: computes runoff from impervious area of a subcatchment that -// is available for LID treatment. -// Input: j = subcatchment index -// Output: returns runoff flow rate (cfs) -// -{ - int i; - double q = 0.0, // runoff rate (ft/sec) - nonLidArea; // non-LID area (ft2) - - // --- runoff from impervious area w/ & w/o depression storage - for (i = IMPERV0; i <= IMPERV1; i++) - { - q += Subcatch[j].subArea[i].runoff * Subcatch[j].subArea[i].fArea; - } - - // --- adjust for any fraction of runoff sent to pervious area - if ( Subcatch[j].subArea[IMPERV0].routeTo == TO_PERV && - Subcatch[j].fracImperv < 1.0 ) - { - q *= Subcatch[j].subArea[IMPERV0].fOutlet; - } - nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; - return q * nonLidArea; -} - -//============================================================================= - -double getPervAreaRunoff(int j) -// -// Purpose: computes runoff from pervious area of a subcatchment that -// is available for LID treatment. -// Input: j = subcatchment index -// Output: returns runoff flow rate (cfs) -// -{ - double q = 0.0, // runoff rate (ft/sec) - nonLidArea; // non-LID area (ft2) - - // --- runoff from pervious area - q = Subcatch[j].subArea[PERV].runoff * Subcatch[j].subArea[PERV].fArea; - - // --- adjust for any fraction of runoff sent to impervious area - if (Subcatch[j].subArea[PERV].routeTo == TO_IMPERV && - Subcatch[j].fracImperv > 0.0) - { - q *= Subcatch[j].subArea[PERV].fOutlet; - } - nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; - return q * nonLidArea; -} - -//============================================================================= - -void evalLidUnit(int j, TLidUnit* lidUnit, double lidArea, double lidInflow, - double tStep, double *qRunoff, double *qDrain, double *qReturn) -// -// Purpose: evaluates performance of a specific LID unit over current time step. -// Input: j = subcatchment index -// lidUnit = ptr. to LID unit being evaluated -// lidArea = area of LID unit -// lidInflow = inflow to LID unit (ft/s) -// tStep = time step (sec) -// Output: qRunoff = sum of surface runoff from all LIDs (cfs) -// qDrain = sum of drain flows from all LIDs (cfs) -// qReturn = sum of LID flows returned to pervious area (cfs) -// -{ - TLidProc* lidProc; // LID process associated with lidUnit - double lidRunoff, // surface runoff from LID unit (cfs) - lidEvap, // evaporation rate from LID unit (ft/s) - lidInfil, // infiltration rate from LID unit (ft/s) - lidDrain; // drain flow rate from LID unit (ft/s & cfs) - - //... identify the LID process of the LID unit being analyzed - lidProc = &LidProcs[lidUnit->lidIndex]; - - //... initialize evap and infil losses - lidEvap = 0.0; - lidInfil = 0.0; - - //... find surface runoff from the LID unit (in cfs) - lidRunoff = lidproc_getOutflow(lidUnit, lidProc, lidInflow, EvapRate, - NativeInfil, MaxNativeInfil, tStep, - &lidEvap, &lidInfil, &lidDrain) * lidArea; - - //... convert drain flow to CFS - lidDrain *= lidArea; - - //... revise flows if LID outflow returned to pervious area - if ( lidUnit->toPerv && Subcatch[j].area > Subcatch[j].lidArea ) - { - //... surface runoff is always returned - *qReturn += lidRunoff; - lidRunoff = 0.0; - - //... drain flow returned if it has same outlet as subcatchment - if ( lidUnit->drainNode == Subcatch[j].outNode && - lidUnit->drainSubcatch == Subcatch[j].outSubcatch ) - { - *qReturn += lidDrain; - lidDrain = 0.0; - } - } - - //... update system flow balance if drain flow goes to a - // conveyance system node - if ( lidUnit->drainNode >= 0 ) - { - massbal_updateRunoffTotals(RUNOFF_DRAINS, lidDrain * tStep); - } - - //... save new drain outflow - lidUnit->newDrainFlow = lidDrain; - - //... update moisture losses (ft3) - Vevap += lidEvap * tStep * lidArea; - VlidInfil += lidInfil * tStep * lidArea; - if ( isLidPervious(lidUnit->lidIndex) ) - { - Vpevap += lidEvap * tStep * lidArea; - } - - //... update time since last rainfall (for Rain Barrel emptying) - if ( Subcatch[j].rainfall > MIN_RUNOFF ) lidUnit->dryTime = 0.0; - else lidUnit->dryTime += tStep; - - //... update LID water balance and save results - lidproc_saveResults(lidUnit, UCF(RAINFALL), UCF(RAINDEPTH)); - - //... update LID group totals - *qRunoff += lidRunoff; - *qDrain += lidDrain; -} - -//============================================================================= - -void lid_writeWaterBalance() -// -// Purpose: writes a LID performance summary table to the project's report file. -// Input: none -// Output: none -// -{ - int j; - int k = 0; - double ucf = UCF(RAINDEPTH); - double inflow; - double outflow; - double err; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... check that project has LIDs - for ( j = 0; j < GroupCount; j++ ) - { - if ( LidGroups[j] ) k++; - } - if ( k == 0 ) return; - - //... write table header - fprintf(Frpt.file, - "\n" - "\n ***********************" - "\n LID Performance Summary" - "\n ***********************\n"); - - fprintf(Frpt.file, -"\n --------------------------------------------------------------------------------------------------------------------" -"\n Total Evap Infil Surface Drain Initial Final Continuity" -"\n Inflow Loss Loss Outflow Outflow Storage Storage Error"); - if ( UnitSystem == US ) fprintf(Frpt.file, -"\n Subcatchment LID Control in in in in in in in %%"); - else fprintf(Frpt.file, -"\n Subcatchment LID Control mm mm mm mm mm mm mm %%"); - fprintf(Frpt.file, -"\n --------------------------------------------------------------------------------------------------------------------"); - - //... examine each LID unit in each subcatchment - for ( j = 0; j < GroupCount; j++ ) - { - lidGroup = LidGroups[j]; - if ( !lidGroup || Subcatch[j].lidArea == 0.0 ) continue; - lidList = lidGroup->lidList; - while ( lidList ) - { - //... write water balance components to report file - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - fprintf(Frpt.file, "\n %-16s %-16s", Subcatch[j].ID, - LidProcs[k].ID); - fprintf(Frpt.file, "%10.2f%10.2f%10.2f%10.2f%10.2f%10.2f%10.2f", - lidUnit->waterBalance.inflow*ucf, - lidUnit->waterBalance.evap*ucf, - lidUnit->waterBalance.infil*ucf, - lidUnit->waterBalance.surfFlow*ucf, - lidUnit->waterBalance.drainFlow*ucf, - lidUnit->waterBalance.initVol*ucf, - lidUnit->waterBalance.finalVol*ucf); - - //... compute flow balance error - inflow = lidUnit->waterBalance.initVol + - lidUnit->waterBalance.inflow; - outflow = lidUnit->waterBalance.finalVol + - lidUnit->waterBalance.evap + - lidUnit->waterBalance.infil + - lidUnit->waterBalance.surfFlow + - lidUnit->waterBalance.drainFlow; - if ( inflow > 0.0 ) err = (inflow - outflow) / inflow; - else err = 1.0; - fprintf(Frpt.file, " %10.2f", err*100.0); - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void initLidRptFile(char* title, char* lidID, char* subcatchID, TLidUnit* lidUnit) -// -// Purpose: initializes the report file used for a specific LID unit -// Input: title = project's title -// lidID = LID process name -// subcatchID = subcatchment ID name -// lidUnit = ptr. to LID unit -// Output: none -// -{ - static int colCount = 14; - static char* head1[] = { - "\n \t", " Elapsed\t", - " Total\t", " Total\t", " Surface\t", " Pavement\t", " Soil\t", - " Storage\t", " Surface\t", " Drain\t", " Surface\t", " Pavement\t", - " Soil\t", " Storage"}; - static char* head2[] = { - "\n \t", " Time\t", - " Inflow\t", " Evap\t", " Infil\t", " Perc\t", " Perc\t", - " Exfil\t", " Runoff\t", " OutFlow\t", " Level\t", " Level\t", - " Moisture\t", " Level"}; - static char* units1[] = { - "\nDate Time \t", " Hours\t", - " in/hr\t", " in/hr\t", " in/hr\t", " in/hr\t", " in/hr\t", - " in/hr\t", " in/hr\t", " in/hr\t", " inches\t", " inches\t", - " Content\t", " inches"}; - static char* units2[] = { - "\nDate Time \t", " Hours\t", - " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm/hr\t", - " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm\t", " mm\t", - " Content\t", " mm"}; - static char line9[] = " ---------"; - int i; - FILE* f = lidUnit->rptFile->file; - - //... check that file was opened - if ( f == NULL ) return; - - //... write title lines - fprintf(f, "SWMM5 LID Report File\n"); - fprintf(f, "\nProject: %s", title); - fprintf(f, "\nLID Unit: %s in Subcatchment %s\n", lidID, subcatchID); - - //... write column headings - for ( i = 0; i < colCount; i++) fprintf(f, "%s", head1[i]); - for ( i = 0; i < colCount; i++) fprintf(f, "%s", head2[i]); - if ( UnitSystem == US ) - { - for ( i = 0; i < colCount; i++) fprintf(f, "%s", units1[i]); - } - else for ( i = 0; i < colCount; i++) fprintf(f, "%s", units2[i]); - fprintf(f, "\n----------- --------"); - for ( i = 1; i < colCount; i++) fprintf(f, "\t%s", line9); - - //... initialize LID dryness state - lidUnit->rptFile->wasDry = 1; - sstrncpy(lidUnit->rptFile->results, "", 0); -} diff --git a/src/lid.h b/src/lid.h deleted file mode 100644 index b250fcf3a..000000000 --- a/src/lid.h +++ /dev/null @@ -1,236 +0,0 @@ -//----------------------------------------------------------------------------- -// lid.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Public interface for LID functions. -// -// Update History -// ============== -// Build 5.1.008: -// - Support added for Roof Disconnection LID. -// - Support added for separate routing of LID drain flows. -// - Detailed LID reporting modified. -// Build 5.1.011: -// - Water depth replaces moisture content for LID's pavement layer. -// - Arguments for lidproc_saveResults() modified. -// Build 5.1.012: -// - Redefined meaning of wasDry in TLidRptFile structure. -// Build 5.1.013: -// - New member fromPerv added to TLidUnit structure to allow LID -// units to also treat pervious area runoff. -// - New members hOpen and hClose addded to TDrainLayer to open/close -// drain when certain heads are reached. -// - New member qCurve added to TDrainLayer to allow underdrain flow to -// be adjusted by a curve of multiplier v. head. -// - New array drainRmvl added to TLidProc to allow for underdrain -// pollutant removal values. -// - New members added to TPavementLayer and TLidUnit to support -// unclogging permeable pavement at fixed intervals. -// Build 5.2.0: -// - Covered property added to RAIN_BARREL parameters -//----------------------------------------------------------------------------- - -#ifndef LID_H -#define LID_H - -#include -#include -#include -#include "infil.h" - -//----------------------------------------------------------------------------- -// Enumerations -//----------------------------------------------------------------------------- -enum LidTypes { - BIO_CELL, // bio-retention cell - RAIN_GARDEN, // rain garden - GREEN_ROOF, // green roof - INFIL_TRENCH, // infiltration trench - POROUS_PAVEMENT, // porous pavement - RAIN_BARREL, // rain barrel - VEG_SWALE, // vegetative swale - ROOF_DISCON}; // roof disconnection - -enum TimePeriod { - PREVIOUS, // previous time period - CURRENT}; // current time period - -//----------------------------------------------------------------------------- -// Data Structures -//----------------------------------------------------------------------------- -#define MAX_LAYERS 4 - -// LID Surface Layer -typedef struct -{ - double thickness; // depression storage or berm ht. (ft) - double voidFrac; // available fraction of storage volume - double roughness; // surface Mannings n - double surfSlope; // land surface slope (fraction) - double sideSlope; // swale side slope (run/rise) - double alpha; // slope/roughness term in Manning eqn. - char canOverflow; // 1 if immediate outflow of excess water -} TSurfaceLayer; - -// LID Pavement Layer -typedef struct -{ - double thickness; // layer thickness (ft) - double voidFrac; // void volume / total volume - double impervFrac; // impervious area fraction - double kSat; // permeability (ft/sec) - double clogFactor; // clogging factor - double regenDays; // clogging regeneration interval (days) - double regenDegree; // degree of clogging regeneration -} TPavementLayer; - -// LID Soil Layer -typedef struct -{ - double thickness; // layer thickness (ft) - double porosity; // void volume / total volume - double fieldCap; // field capacity - double wiltPoint; // wilting point - double suction; // suction head at wetting front (ft) - double kSat; // saturated hydraulic conductivity (ft/sec) - double kSlope; // slope of log(K) v. moisture content curve -} TSoilLayer; - -// LID Storage Layer -typedef struct -{ - double thickness; // layer thickness (ft) - double voidFrac; // void volume / total volume - double kSat; // saturated hydraulic conductivity (ft/sec) - double clogFactor; // clogging factor - int covered; // TRUE if rain barrel is covered -} TStorageLayer; - -// Underdrain System (part of Storage Layer) -typedef struct -{ - double coeff; // underdrain flow coeff. (in/hr or mm/hr) - double expon; // underdrain head exponent (for in or mm) - double offset; // offset height of underdrain (ft) - double delay; // rain barrel drain delay time (sec) - double hOpen; // head when drain opens (ft) - double hClose; // head when drain closes (ft) - int qCurve; // curve controlling flow rate (optional) -} TDrainLayer; - -// Drainage Mat Layer (for green roofs) -typedef struct -{ - double thickness; // layer thickness (ft) - double voidFrac; // void volume / total volume - double roughness; // Mannings n for green roof drainage mats - double alpha; // slope/roughness term in Manning equation -} TDrainMatLayer; - -// LID Process - generic LID design per unit of area -typedef struct -{ - char* ID; // identifying name - int lidType; // type of LID - TSurfaceLayer surface; // surface layer parameters - TPavementLayer pavement; // pavement layer parameters - TSoilLayer soil; // soil layer parameters - TStorageLayer storage; // storage layer parameters - TDrainLayer drain; // underdrain system parameters - TDrainMatLayer drainMat; // drainage mat layer - double* drainRmvl; // underdrain pollutant removals -} TLidProc; - -// Water Balance Statistics -typedef struct -{ - double inflow; // total inflow (ft) - double evap; // total evaporation (ft) - double infil; // total infiltration (ft) - double surfFlow; // total surface runoff (ft) - double drainFlow; // total underdrain flow (ft) - double initVol; // initial stored volume (ft) - double finalVol; // final stored volume (ft) -} TWaterBalance; - -// LID Report File -typedef struct -{ - FILE* file; // file pointer - int wasDry; // number of successive dry periods - char results[256]; // results for current time period -} TLidRptFile; - -// LID Unit - specific LID process applied over a given area -typedef struct -{ - int lidIndex; // index of LID process - int number; // number of replicate units - double area; // area of single replicate unit (ft2) - double fullWidth; // full top width of single unit (ft) - double botWidth; // bottom width of single unit (ft) - double initSat; // initial saturation of soil & storage layers - double fromImperv; // fraction of impervious area runoff treated - double fromPerv; // fraction of pervious area runoff treated - int toPerv; // 1 if outflow sent to pervious area; 0 if not - int drainSubcatch; // subcatchment receiving drain flow - int drainNode; // node receiving drain flow - TLidRptFile* rptFile; // pointer to detailed report file - - TGrnAmpt soilInfil; // infil. object for biocell soil layer - double surfaceDepth; // depth of ponded water on surface layer (ft) - double paveDepth; // depth of water in porous pavement layer - double soilMoisture; // moisture content of biocell soil layer - double storageDepth; // depth of water in storage layer (ft) - - // net inflow - outflow from previous time step for each LID layer (ft/s) - double oldFluxRates[MAX_LAYERS]; - - double dryTime; // time since last rainfall (sec) - double oldDrainFlow; // previous drain flow (cfs) - double newDrainFlow; // current drain flow (cfs) - double volTreated; // total volume treated (ft) - double nextRegenDay; // next day when unit regenerated - TWaterBalance waterBalance; // water balance quantites -} TLidUnit; - -//----------------------------------------------------------------------------- -// LID Methods -//----------------------------------------------------------------------------- -void lid_create(int lidCount, int subcatchCount); -void lid_delete(void); - -int lid_readProcParams(char* tok[], int ntoks); -int lid_readGroupParams(char* tok[], int ntoks); - -void lid_validate(void); -void lid_initState(void); -void lid_setOldGroupState(int subcatch); - -double lid_getPervArea(int subcatch); -double lid_getFlowToPerv(int subcatch); -double lid_getDrainFlow(int subcatch, int timePeriod); -double lid_getStoredVolume(int subcatch); -void lid_addDrainLoads(int subcatch, double c[], double tStep); -void lid_addDrainRunon(int subcatch); -void lid_addDrainInflow(int subcatch, double f); -void lid_getRunoff(int subcatch, double tStep); -void lid_writeSummary(void); -void lid_writeWaterBalance(void); - -//----------------------------------------------------------------------------- - -void lidproc_initWaterBalance(TLidUnit *lidUnit, double initVol); - -double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, - double inflow, double evap, double infil, double maxInfil, - double tStep, double* lidEvap, double* lidInfil, double* lidDrain); - -void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, - double ucfRainDepth); - -#endif diff --git a/src/lidproc.c b/src/lidproc.c deleted file mode 100644 index 4dbd5b977..000000000 --- a/src/lidproc.c +++ /dev/null @@ -1,1593 +0,0 @@ -//----------------------------------------------------------------------------- -// lidproc.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// This module computes the hydrologic performance of an LID (Low Impact -// Development) unit at a given point in time. -// -// Update History -// ============== -// Build 5.1.007: -// - Euler integration now applied to all LID types except Vegetative -// Swale which continues to use successive approximation. -// - LID layer flux routines were re-written to more accurately model -// flooded conditions. -// Build 5.1.008: -// - MAX_STATE_VARS replaced with MAX_LAYERS. -// - Optional soil layer added to Porous Pavement LID. -// - Rooftop Disconnection added to types of LIDs. -// - Separate accounting of drain flows added. -// - Indicator for currently wet LIDs added. -// - Detailed reporting procedure fixed. -// - Possibile negative head on Bioretention Cell drain avoided. -// - Bug in computing flow through Green Roof drainage mat fixed. -// Build 5.1.009: -// - Fixed typo in net flux rate for vegetative swale LID. -// Build 5.1.010: -// - New modified version of Green-Ampt used for surface layer infiltration. -// Build 5.1.011: -// - Re-named STOR_INFIL to STOR_EXFIL and StorageInfil to StorageExfil to -// better reflect their meaning. -// - Evaporation rates from sub-surface layers reduced by fraction of -// surface that is pervious (applies to block paver systems) -// - Flux rate routines for LIDs with underdrains modified to produce more -// physically meaningful results. -// - Reporting of detailed results re-written. -// Build 5.1.012: -// - Modified upper limit for soil layer percolation. -// - Modified upper limit on surface infiltration into rain gardens. -// - Modified upper limit on drain flow for LIDs with storage layers. -// - Used re-defined wasDry variable for LID reports to fix duplicate lines. -// Build 5.1.013: -// - Support added for open/closed head levels and multiplier v. head curve -// to control underdrain flow. -// - Support added for regenerating pavement permeability at fixed intervals. -// Build 5.1.014: -// - Fixed failure to initialize all LID layer moisture volumes to 0 before -// computing LID unit performance in lidproc_getOutflow. -// Build 5.2.0: -// - Fixed failure to account for effect of Impervious Surface Fraction on -// pavement permeability for Permeable Pavement LID -// - Fixed units conversion for pavement depth in detailed report file. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "lid.h" -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -#define STOPTOL 0.00328 // integration error tolerance in ft (= 1 mm) -#define MINFLOW 2.3e-8 // flow cutoff for dry conditions (= 0.001 in/hr) - -//----------------------------------------------------------------------------- -// Enumerations -//----------------------------------------------------------------------------- -enum LidLayerTypes { - SURF, // surface layer - SOIL, // soil layer - STOR, // storage layer - PAVE, // pavement layer - DRAIN}; // underdrain system - -enum LidRptVars { - SURF_INFLOW, // inflow to surface layer - TOTAL_EVAP, // evaporation rate from all layers - SURF_INFIL, // infiltration into surface layer - PAVE_PERC, // percolation through pavement layer - SOIL_PERC, // percolation through soil layer - STOR_EXFIL, // exfiltration out of storage layer - SURF_OUTFLOW, // outflow from surface layer - STOR_DRAIN, // outflow from storage layer - SURF_DEPTH, // ponded depth on surface layer - PAVE_DEPTH, // water level in pavement layer - SOIL_MOIST, // moisture content of soil layer - STOR_DEPTH, // water level in storage layer - MAX_RPT_VARS}; - -//----------------------------------------------------------------------------- -// Imported variables -//----------------------------------------------------------------------------- -extern char HasWetLids; // TRUE if any LIDs are wet (declared in runoff.c) - -//----------------------------------------------------------------------------- -// Local Variables -//----------------------------------------------------------------------------- -static TLidUnit* theLidUnit; // ptr. to a subcatchment's LID unit -static TLidProc* theLidProc; // ptr. to a LID process - -static double Tstep; // current time step (sec) -static double EvapRate; // evaporation rate (ft/s) -static double MaxNativeInfil; // native soil infil. rate limit (ft/s) - -static double SurfaceInflow; // precip. + runon to LID unit (ft/s) -static double SurfaceInfil; // infil. rate from surface layer (ft/s) -static double SurfaceEvap; // evap. rate from surface layer (ft/s) -static double SurfaceOutflow; // outflow from surface layer (ft/s) -static double SurfaceVolume; // volume in surface storage (ft) - -static double PaveEvap; // evap. from pavement layer (ft/s) -static double PavePerc; // percolation from pavement layer (ft/s) -static double PaveVolume; // volume stored in pavement layer (ft) - -static double SoilEvap; // evap. from soil layer (ft/s) -static double SoilPerc; // percolation from soil layer (ft/s) -static double SoilVolume; // volume in soil/pavement storage (ft) - -static double StorageInflow; // inflow rate to storage layer (ft/s) -static double StorageExfil; // exfil. rate from storage layer (ft/s) -static double StorageEvap; // evap.rate from storage layer (ft/s) -static double StorageDrain; // underdrain flow rate layer (ft/s) -static double StorageVolume; // volume in storage layer (ft) - -static double Xold[MAX_LAYERS]; // previous moisture level in LID layers - -//----------------------------------------------------------------------------- -// External Functions (declared in lid.h) -//----------------------------------------------------------------------------- -// lidproc_initWaterBalance (called by lid_initState) -// lidproc_getOutflow (called by evalLidUnit in lid.c) -// lidproc_saveResults (called by evalLidUnit in lid.c) - -//----------------------------------------------------------------------------- -// Local Functions -//----------------------------------------------------------------------------- -static void barrelFluxRates(double x[], double f[]); -static void biocellFluxRates(double x[], double f[]); -static void greenRoofFluxRates(double x[], double f[]); -static void pavementFluxRates(double x[], double f[]); -static void trenchFluxRates(double x[], double f[]); -static void swaleFluxRates(double x[], double f[]); -static void roofFluxRates(double x[], double f[]); - -static double getSurfaceOutflowRate(double depth); -static double getSurfaceOverflowRate(double* surfaceDepth); -static double getPavementPermRate(void); -static double getSoilPercRate(double theta); -static double getStorageExfilRate(void); -static double getStorageDrainRate(double storageDepth, double soilTheta, - double paveDepth, double surfaceDepth); -static double getDrainMatOutflow(double depth); -static void getEvapRates(double surfaceVol, double paveVol, - double soilVol, double storageVol, double pervFrac); - -static void updateWaterBalance(TLidUnit *lidUnit, double inflow, - double evap, double infil, double surfFlow, - double drainFlow, double storage); - -static int modpuls_solve(int n, double* x, double* xOld, double* xPrev, - double* xMin, double* xMax, double* xTol, - double* qOld, double* q, double dt, double omega, - void (*derivs)(double*, double*)); - -//============================================================================= - -void lidproc_initWaterBalance(TLidUnit *lidUnit, double initVol) -// -// Purpose: initializes the water balance components of a LID unit. -// Input: lidUnit = a particular LID unit -// initVol = initial water volume stored in the unit (ft) -// Output: none -// -{ - lidUnit->waterBalance.inflow = 0.0; - lidUnit->waterBalance.evap = 0.0; - lidUnit->waterBalance.infil = 0.0; - lidUnit->waterBalance.surfFlow = 0.0; - lidUnit->waterBalance.drainFlow = 0.0; - lidUnit->waterBalance.initVol = initVol; - lidUnit->waterBalance.finalVol = initVol; -} - -//============================================================================= - -double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, double inflow, - double evap, double infil, double maxInfil, - double tStep, double* lidEvap, - double* lidInfil, double* lidDrain) -// -// Purpose: computes runoff outflow from a single LID unit. -// Input: lidUnit = ptr. to specific LID unit being analyzed -// lidProc = ptr. to generic LID process of the LID unit -// inflow = runoff rate captured by LID unit (ft/s) -// evap = potential evaporation rate (ft/s) -// infil = infiltration rate to native soil (ft/s) -// maxInfil = max. infiltration rate to native soil (ft/s) -// tStep = time step (sec) -// Output: lidEvap = evaporation rate for LID unit (ft/s) -// lidInfil = infiltration rate for LID unit (ft/s) -// lidDrain = drain flow for LID unit (ft/s) -// returns surface runoff rate from the LID unit (ft/s) -// -{ - int i; - double x[MAX_LAYERS]; // layer moisture levels - double xOld[MAX_LAYERS]; // work vector - double xPrev[MAX_LAYERS]; // work vector - double xMin[MAX_LAYERS]; // lower limit on moisture levels - double xMax[MAX_LAYERS]; // upper limit on moisture levels - double fOld[MAX_LAYERS]; // previously computed flux rates - double f[MAX_LAYERS]; // newly computed flux rates - - // convergence tolerance on moisture levels (ft, moisture fraction , ft) - double xTol[MAX_LAYERS] = {STOPTOL, STOPTOL, STOPTOL, STOPTOL}; - - double omega = 0.0; // integration time weighting - - //... define a pointer to function that computes flux rates through the LID - void (*fluxRates) (double *, double *) = NULL; - - //... save references to the LID process and LID unit - theLidProc = lidProc; - theLidUnit = lidUnit; - - //... save evap, max. infil. & time step to shared variables - EvapRate = evap; - MaxNativeInfil = maxInfil; - Tstep = tStep; - - //... store current moisture levels in vector x - x[SURF] = theLidUnit->surfaceDepth; - x[SOIL] = theLidUnit->soilMoisture; - x[STOR] = theLidUnit->storageDepth; - x[PAVE] = theLidUnit->paveDepth; - - //... initialize layer moisture volumes, flux rates and moisture limits - SurfaceVolume = 0.0; - PaveVolume = 0.0; - SoilVolume = 0.0; - StorageVolume = 0.0; - SurfaceInflow = inflow; - SurfaceInfil = 0.0; - SurfaceEvap = 0.0; - SurfaceOutflow = 0.0; - PaveEvap = 0.0; - PavePerc = 0.0; - SoilEvap = 0.0; - SoilPerc = 0.0; - StorageInflow = 0.0; - StorageExfil = 0.0; - StorageEvap = 0.0; - StorageDrain = 0.0; - for (i = 0; i < MAX_LAYERS; i++) - { - f[i] = 0.0; - fOld[i] = theLidUnit->oldFluxRates[i]; - xMin[i] = 0.0; - xMax[i] = BIG; - Xold[i] = x[i]; - } - - //... find Green-Ampt infiltration from surface layer - if ( theLidProc->lidType == POROUS_PAVEMENT ) SurfaceInfil = 0.0; - else if ( theLidUnit->soilInfil.Ks > 0.0 ) - { - SurfaceInfil = - grnampt_getInfil(&theLidUnit->soilInfil, Tstep, - SurfaceInflow, theLidUnit->surfaceDepth, - MOD_GREEN_AMPT); - } - else SurfaceInfil = infil; - - //... set moisture limits for soil & storage layers - if ( theLidProc->soil.thickness > 0.0 ) - { - xMin[SOIL] = theLidProc->soil.wiltPoint; - xMax[SOIL] = theLidProc->soil.porosity; - } - if ( theLidProc->pavement.thickness > 0.0 ) - { - xMax[PAVE] = theLidProc->pavement.thickness; - } - if ( theLidProc->storage.thickness > 0.0 ) - { - xMax[STOR] = theLidProc->storage.thickness; - } - if ( theLidProc->lidType == GREEN_ROOF ) - { - xMax[STOR] = theLidProc->drainMat.thickness; - } - - //... determine which flux rate function to use - switch (theLidProc->lidType) - { - case BIO_CELL: - case RAIN_GARDEN: fluxRates = &biocellFluxRates; break; - case GREEN_ROOF: fluxRates = &greenRoofFluxRates; break; - case INFIL_TRENCH: fluxRates = &trenchFluxRates; break; - case POROUS_PAVEMENT: fluxRates = &pavementFluxRates; break; - case RAIN_BARREL: fluxRates = &barrelFluxRates; break; - case ROOF_DISCON: fluxRates = &roofFluxRates; break; - case VEG_SWALE: fluxRates = &swaleFluxRates; - omega = 0.5; - break; - default: return 0.0; - } - - //... update moisture levels and flux rates over the time step - i = modpuls_solve(MAX_LAYERS, x, xOld, xPrev, xMin, xMax, xTol, - fOld, f, tStep, omega, fluxRates); - -/** For debugging only ******************************************** - if (i == 0) - { - fprintf(Frpt.file, - "\n WARNING 09: integration failed to converge at %s %s", - theDate, theTime); - fprintf(Frpt.file, - "\n for LID %s placed in subcatchment %s.", - theLidProc->ID, theSubcatch->ID); - } -*******************************************************************/ - - //... add any surface overflow to surface outflow - if ( theLidProc->surface.canOverflow || theLidUnit->fullWidth == 0.0 ) - { - SurfaceOutflow += getSurfaceOverflowRate(&x[SURF]); - } - - //... save updated results - theLidUnit->surfaceDepth = x[SURF]; - theLidUnit->paveDepth = x[PAVE]; - theLidUnit->soilMoisture = x[SOIL]; - theLidUnit->storageDepth = x[STOR]; - for (i = 0; i < MAX_LAYERS; i++) theLidUnit->oldFluxRates[i] = f[i]; - - //... assign values to LID unit evaporation, infiltration & drain flow - *lidEvap = SurfaceEvap + PaveEvap + SoilEvap + StorageEvap; - *lidInfil = StorageExfil; - *lidDrain = StorageDrain; - - //... return surface outflow (per unit area) from unit - return SurfaceOutflow; -} - -//============================================================================= - -void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, double ucfRainDepth) -// -// Purpose: updates the mass balance for an LID unit and saves -// current flux rates to the LID report file. -// Input: lidUnit = ptr. to LID unit -// ucfRainfall = units conversion factor for rainfall rate -// ucfDepth = units conversion factor for rainfall depth -// Output: none -// -{ - double ucf; // units conversion factor - double totalEvap; // total evaporation rate (ft/s) - double totalVolume; // total volume stored in LID (ft) - double rptVars[MAX_RPT_VARS]; // array of reporting variables - int isDry = FALSE; // true if current state of LID is dry - char timeStamp[TIME_STAMP_SIZE + 1]; // date/time stamp - double elapsedHrs; // elapsed hours - - //... find total evap. rate and stored volume - totalEvap = SurfaceEvap + PaveEvap + SoilEvap + StorageEvap; - totalVolume = SurfaceVolume + PaveVolume + SoilVolume + StorageVolume; - - //... update mass balance totals - updateWaterBalance(theLidUnit, SurfaceInflow, totalEvap, StorageExfil, - SurfaceOutflow, StorageDrain, totalVolume); - - //... check if dry-weather conditions hold - if ( SurfaceInflow < MINFLOW && - SurfaceOutflow < MINFLOW && - StorageDrain < MINFLOW && - StorageExfil < MINFLOW && - totalEvap < MINFLOW - ) isDry = TRUE; - - //... update status of HasWetLids - if ( !isDry ) HasWetLids = TRUE; - - //... write results to LID report file - if ( lidUnit->rptFile ) - { - //... convert rate results to original units (in/hr or mm/hr) - ucf = ucfRainfall; - rptVars[SURF_INFLOW] = SurfaceInflow*ucf; - rptVars[TOTAL_EVAP] = totalEvap*ucf; - rptVars[SURF_INFIL] = SurfaceInfil*ucf; - rptVars[PAVE_PERC] = PavePerc*ucf; - rptVars[SOIL_PERC] = SoilPerc*ucf; - rptVars[STOR_EXFIL] = StorageExfil*ucf; - rptVars[SURF_OUTFLOW] = SurfaceOutflow*ucf; - rptVars[STOR_DRAIN] = StorageDrain*ucf; - - //... convert storage results to original units (in or mm) - ucf = ucfRainDepth; - rptVars[SURF_DEPTH] = theLidUnit->surfaceDepth*ucf; - rptVars[PAVE_DEPTH] = theLidUnit->paveDepth*ucf; - rptVars[SOIL_MOIST] = theLidUnit->soilMoisture; - rptVars[STOR_DEPTH] = theLidUnit->storageDepth*ucf; - - //... if the current LID state is wet but the previous state was dry - // for more than one period then write the saved previous results - // to the report file thus marking the end of a dry period - if ( !isDry && theLidUnit->rptFile->wasDry > 1) - { - fprintf(theLidUnit->rptFile->file, "%s", - theLidUnit->rptFile->results); - } - - //... write the current results to a string which is saved between - // reporting periods - elapsedHrs = NewRunoffTime / 1000.0 / 3600.0; - datetime_getTimeStamp( - M_D_Y, getDateTime(NewRunoffTime), TIME_STAMP_SIZE, timeStamp); - snprintf(theLidUnit->rptFile->results, sizeof(theLidUnit->rptFile->results), - "\n%20s\t %8.3f\t %8.3f\t %8.4f\t %8.3f\t %8.3f\t %8.3f\t %8.3f\t" - "%8.3f\t %8.3f\t %8.3f\t %8.3f\t %8.3f\t %8.3f", - timeStamp, elapsedHrs, rptVars[0], rptVars[1], rptVars[2], - rptVars[3], rptVars[4], rptVars[5], rptVars[6], rptVars[7], - rptVars[8], rptVars[9], rptVars[10], rptVars[11]); - - //... if the current LID state is dry - if ( isDry ) - { - //... if the previous state was wet then write the current - // results to file marking the start of a dry period - if ( theLidUnit->rptFile->wasDry == 0 ) - { - fprintf(theLidUnit->rptFile->file, "%s", - theLidUnit->rptFile->results); - } - - //... increment the number of successive dry periods - theLidUnit->rptFile->wasDry++; - } - - //... if the current LID state is wet - else - { - //... write the current results to the report file - fprintf(theLidUnit->rptFile->file, "%s", - theLidUnit->rptFile->results); - - //... re-set the number of successive dry periods to 0 - theLidUnit->rptFile->wasDry = 0; - } - } -} - -//============================================================================= - -void roofFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates for roof disconnection. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - double surfaceDepth = x[SURF]; - - getEvapRates(surfaceDepth, 0.0, 0.0, 0.0, 1.0); - SurfaceVolume = surfaceDepth; - SurfaceInfil = 0.0; - if ( theLidProc->surface.alpha > 0.0 ) - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - else getSurfaceOverflowRate(&surfaceDepth); - StorageDrain = MIN(theLidProc->drain.coeff/UCF(RAINFALL), SurfaceOutflow); - SurfaceOutflow -= StorageDrain; - f[SURF] = (SurfaceInflow - SurfaceEvap - StorageDrain - SurfaceOutflow); -} - -//============================================================================= - -void greenRoofFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates from the layers of a green roof. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - // Moisture level variables - double surfaceDepth; - double soilTheta; - double storageDepth; - - // Intermediate variables - double availVolume; - double maxRate; - - // Green roof properties - double soilThickness = theLidProc->soil.thickness; - double storageThickness = theLidProc->storage.thickness; - double soilPorosity = theLidProc->soil.porosity; - double storageVoidFrac = theLidProc->storage.voidFrac; - double soilFieldCap = theLidProc->soil.fieldCap; - double soilWiltPoint = theLidProc->soil.wiltPoint; - - //... retrieve moisture levels from input vector - surfaceDepth = x[SURF]; - soilTheta = x[SOIL]; - storageDepth = x[STOR]; - - //... convert moisture levels to volumes - SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; - SoilVolume = soilTheta * soilThickness; - StorageVolume = storageDepth * storageVoidFrac; - - //... get ET rates - availVolume = SoilVolume - soilWiltPoint * soilThickness; - getEvapRates(SurfaceVolume, 0.0, availVolume, StorageVolume, 1.0); - if ( soilTheta >= soilPorosity ) StorageEvap = 0.0; - - //... soil layer perc rate - SoilPerc = getSoilPercRate(soilTheta); - - //... limit perc rate by available water - availVolume = (soilTheta - soilFieldCap) * soilThickness; - maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; - SoilPerc = MIN(SoilPerc, maxRate); - SoilPerc = MAX(SoilPerc, 0.0); - - //... storage (drain mat) outflow rate - StorageExfil = 0.0; - StorageDrain = getDrainMatOutflow(storageDepth); - - //... unit is full - if ( soilTheta >= soilPorosity && storageDepth >= storageThickness ) - { - //... outflow from both layers equals limiting rate - maxRate = MIN(SoilPerc, StorageDrain); - SoilPerc = maxRate; - StorageDrain = maxRate; - - //... adjust inflow rate to soil layer - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... unit not full - else - { - //... limit drainmat outflow by available storage volume - maxRate = storageDepth * storageVoidFrac / Tstep - StorageEvap; - if ( storageDepth >= storageThickness ) maxRate += SoilPerc; - maxRate = MAX(maxRate, 0.0); - StorageDrain = MIN(StorageDrain, maxRate); - - //... limit soil perc inflow by unused storage volume - maxRate = (storageThickness - storageDepth) * storageVoidFrac / Tstep + - StorageDrain + StorageEvap; - SoilPerc = MIN(SoilPerc, maxRate); - - //... adjust surface infil. so soil porosity not exceeded - maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + - SoilPerc + SoilEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - // ... find surface outflow rate - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - - // ... compute overall layer flux rates - f[SURF] = (SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow) / - theLidProc->surface.voidFrac; - f[SOIL] = (SurfaceInfil - SoilEvap - SoilPerc) / - theLidProc->soil.thickness; - f[STOR] = (SoilPerc - StorageEvap - StorageDrain) / - theLidProc->storage.voidFrac; -} - -//============================================================================= - -void biocellFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates from the layers of a bio-retention cell LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - // Moisture level variables - double surfaceDepth; - double soilTheta; - double storageDepth; - - // Intermediate variables - double availVolume; - double maxRate; - - // LID layer properties - double soilThickness = theLidProc->soil.thickness; - double soilPorosity = theLidProc->soil.porosity; - double soilFieldCap = theLidProc->soil.fieldCap; - double soilWiltPoint = theLidProc->soil.wiltPoint; - double storageThickness = theLidProc->storage.thickness; - double storageVoidFrac = theLidProc->storage.voidFrac; - - //... retrieve moisture levels from input vector - surfaceDepth = x[SURF]; - soilTheta = x[SOIL]; - storageDepth = x[STOR]; - - //... convert moisture levels to volumes - SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; - SoilVolume = soilTheta * soilThickness; - StorageVolume = storageDepth * storageVoidFrac; - - //... get ET rates - availVolume = SoilVolume - soilWiltPoint * soilThickness; - getEvapRates(SurfaceVolume, 0.0, availVolume, StorageVolume, 1.0); - if ( soilTheta >= soilPorosity ) StorageEvap = 0.0; - - //... soil layer perc rate - SoilPerc = getSoilPercRate(soilTheta); - - //... limit perc rate by available water - availVolume = (soilTheta - soilFieldCap) * soilThickness; - maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; - SoilPerc = MIN(SoilPerc, maxRate); - SoilPerc = MAX(SoilPerc, 0.0); - - //... exfiltration rate out of storage layer - StorageExfil = getStorageExfilRate(); - - //... underdrain flow rate - StorageDrain = 0.0; - if ( theLidProc->drain.coeff > 0.0 ) - { - StorageDrain = getStorageDrainRate(storageDepth, soilTheta, 0.0, - surfaceDepth); - } - - //... special case of no storage layer present - if ( storageThickness == 0.0 ) - { - StorageEvap = 0.0; - maxRate = MIN(SoilPerc, StorageExfil); - SoilPerc = maxRate; - StorageExfil = maxRate; - - //... limit surface infil. by unused soil volume - maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + - SoilPerc + SoilEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - - } - - //... storage & soil layers are full - else if ( soilTheta >= soilPorosity && storageDepth >= storageThickness ) - { - //... limiting rate is smaller of soil perc and storage outflow - maxRate = StorageExfil + StorageDrain; - if ( SoilPerc < maxRate ) - { - maxRate = SoilPerc; - if ( maxRate > StorageExfil ) StorageDrain = maxRate - StorageExfil; - else - { - StorageExfil = maxRate; - StorageDrain = 0.0; - } - } - else SoilPerc = maxRate; - - //... apply limiting rate to surface infil. - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... either layer not full - else if ( storageThickness > 0.0 ) - { - //... limit storage exfiltration by available storage volume - maxRate = SoilPerc - StorageEvap + storageDepth*storageVoidFrac/Tstep; - StorageExfil = MIN(StorageExfil, maxRate); - StorageExfil = MAX(StorageExfil, 0.0); - - //... limit underdrain flow by volume above drain offset - if ( StorageDrain > 0.0 ) - { - maxRate = -StorageExfil - StorageEvap; - if ( storageDepth >= storageThickness) maxRate += SoilPerc; - if ( theLidProc->drain.offset <= storageDepth ) - { - maxRate += (storageDepth - theLidProc->drain.offset) * - storageVoidFrac/Tstep; - } - maxRate = MAX(maxRate, 0.0); - StorageDrain = MIN(StorageDrain, maxRate); - } - - //... limit soil perc by unused storage volume - maxRate = StorageExfil + StorageDrain + StorageEvap + - (storageThickness - storageDepth) * - storageVoidFrac/Tstep; - SoilPerc = MIN(SoilPerc, maxRate); - - //... limit surface infil. by unused soil volume - maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + - SoilPerc + SoilEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... find surface layer outflow rate - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - - //... compute overall layer flux rates - f[SURF] = (SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow) / - theLidProc->surface.voidFrac; - f[SOIL] = (SurfaceInfil - SoilEvap - SoilPerc) / - theLidProc->soil.thickness; - if ( storageThickness == 0.0 ) f[STOR] = 0.0; - else f[STOR] = (SoilPerc - StorageEvap - StorageExfil - StorageDrain) / - theLidProc->storage.voidFrac; -} - -//============================================================================= - -void trenchFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates from the layers of an infiltration trench LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - // Moisture level variables - double surfaceDepth; - double storageDepth; - - // Intermediate variables - double availVolume; - double maxRate; - - // Storage layer properties - double storageThickness = theLidProc->storage.thickness; - double storageVoidFrac = theLidProc->storage.voidFrac; - - //... retrieve moisture levels from input vector - surfaceDepth = x[SURF]; - storageDepth = x[STOR]; - - //... convert moisture levels to volumes - SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; - SoilVolume = 0.0; - StorageVolume = storageDepth * storageVoidFrac; - - //... get ET rates - availVolume = (storageThickness - storageDepth) * storageVoidFrac; - getEvapRates(SurfaceVolume, 0.0, 0.0, StorageVolume, 1.0); - - //... no storage evap if surface ponded - if ( surfaceDepth > 0.0 ) StorageEvap = 0.0; - - //... nominal storage inflow - StorageInflow = SurfaceInflow + SurfaceVolume / Tstep; - - //... exfiltration rate out of storage layer - StorageExfil = getStorageExfilRate(); - - //... underdrain flow rate - StorageDrain = 0.0; - if ( theLidProc->drain.coeff > 0.0 ) - { - StorageDrain = getStorageDrainRate(storageDepth, 0.0, 0.0, surfaceDepth); - } - - //... limit storage exfiltration by available storage volume - maxRate = StorageInflow - StorageEvap + storageDepth*storageVoidFrac/Tstep; - StorageExfil = MIN(StorageExfil, maxRate); - StorageExfil = MAX(StorageExfil, 0.0); - - //... limit underdrain flow by volume above drain offset - if ( StorageDrain > 0.0 ) - { - maxRate = -StorageExfil - StorageEvap; - if (storageDepth >= storageThickness ) maxRate += StorageInflow; - if ( theLidProc->drain.offset <= storageDepth ) - { - maxRate += (storageDepth - theLidProc->drain.offset) * - storageVoidFrac/Tstep; - } - maxRate = MAX(maxRate, 0.0); - StorageDrain = MIN(StorageDrain, maxRate); - } - - //... limit storage inflow to not exceed storage layer capacity - maxRate = (storageThickness - storageDepth)*storageVoidFrac/Tstep + - StorageExfil + StorageEvap + StorageDrain; - StorageInflow = MIN(StorageInflow, maxRate); - - //... equate surface infil to storage inflow - SurfaceInfil = StorageInflow; - - //... find surface outflow rate - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - - // ... find net fluxes for each layer - f[SURF] = SurfaceInflow - SurfaceEvap - StorageInflow - SurfaceOutflow / - theLidProc->surface.voidFrac;; - f[STOR] = (StorageInflow - StorageEvap - StorageExfil - StorageDrain) / - theLidProc->storage.voidFrac; - f[SOIL] = 0.0; -} - -//============================================================================= - -void pavementFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates for the layers of a porous pavement LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - //... Moisture level variables - double surfaceDepth; - double paveDepth; - double soilTheta; - double storageDepth; - - //... Intermediate variables - double pervFrac = (1.0 - theLidProc->pavement.impervFrac); - double storageInflow; // inflow rate to storage layer (ft/s) - double availVolume; - double maxRate; - - //... LID layer properties - double paveVoidFrac = theLidProc->pavement.voidFrac * pervFrac; - double paveThickness = theLidProc->pavement.thickness; - double soilThickness = theLidProc->soil.thickness; - double soilPorosity = theLidProc->soil.porosity; - double soilFieldCap = theLidProc->soil.fieldCap; - double soilWiltPoint = theLidProc->soil.wiltPoint; - double storageThickness = theLidProc->storage.thickness; - double storageVoidFrac = theLidProc->storage.voidFrac; - - //... retrieve moisture levels from input vector - surfaceDepth = x[SURF]; - paveDepth = x[PAVE]; - soilTheta = x[SOIL]; - storageDepth = x[STOR]; - - //... convert moisture levels to volumes - SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; - PaveVolume = paveDepth * paveVoidFrac; - SoilVolume = soilTheta * soilThickness; - StorageVolume = storageDepth * storageVoidFrac; - - //... get ET rates - availVolume = SoilVolume - soilWiltPoint * soilThickness; - getEvapRates(SurfaceVolume, PaveVolume, availVolume, StorageVolume, - pervFrac); - - //... no storage evap if soil or pavement layer saturated - if ( paveDepth >= paveThickness || - ( soilThickness > 0.0 && soilTheta >= soilPorosity ) - ) StorageEvap = 0.0; - - //... find nominal rate of surface infiltration into pavement layer - SurfaceInfil = SurfaceInflow + (SurfaceVolume / Tstep); - - //... find perc rate out of pavement layer - PavePerc = getPavementPermRate() * pervFrac; - - //... surface infiltration can't exceed pavement permeability - SurfaceInfil = MIN(SurfaceInfil, PavePerc); - - //... limit pavement perc by available water - maxRate = PaveVolume/Tstep + SurfaceInfil - PaveEvap; - maxRate = MAX(maxRate, 0.0); - PavePerc = MIN(PavePerc, maxRate); - - //... find soil layer perc rate - if ( soilThickness > 0.0 ) - { - SoilPerc = getSoilPercRate(soilTheta); - availVolume = (soilTheta - soilFieldCap) * soilThickness; - maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; - SoilPerc = MIN(SoilPerc, maxRate); - SoilPerc = MAX(SoilPerc, 0.0); - } - else SoilPerc = PavePerc; - - //... exfiltration rate out of storage layer - StorageExfil = getStorageExfilRate(); - - //... underdrain flow rate - StorageDrain = 0.0; - if ( theLidProc->drain.coeff > 0.0 ) - { - StorageDrain = getStorageDrainRate(storageDepth, soilTheta, paveDepth, - surfaceDepth); - } - - //... check for adjacent saturated layers - - //... no soil layer, pavement & storage layers are full - if ( soilThickness == 0.0 && - storageDepth >= storageThickness && - paveDepth >= paveThickness ) - { - //... pavement outflow can't exceed storage outflow - maxRate = StorageEvap + StorageDrain + StorageExfil; - if ( PavePerc > maxRate ) PavePerc = maxRate; - - //... storage outflow can't exceed pavement outflow - else - { - //... use up available exfiltration capacity first - StorageExfil = MIN(StorageExfil, PavePerc); - StorageDrain = PavePerc - StorageExfil; - } - - //... set soil perc to pavement perc - SoilPerc = PavePerc; - - //... limit surface infil. by pavement perc - SurfaceInfil = MIN(SurfaceInfil, PavePerc); - } - - //... pavement, soil & storage layers are full - else if ( soilThickness > 0 && - storageDepth >= storageThickness && - soilTheta >= soilPorosity && - paveDepth >= paveThickness ) - { - //... find which layer has limiting flux rate - maxRate = StorageExfil + StorageDrain; - if ( SoilPerc < maxRate) maxRate = SoilPerc; - else maxRate = MIN(maxRate, PavePerc); - - //... use up available storage exfiltration capacity first - if ( maxRate > StorageExfil ) StorageDrain = maxRate - StorageExfil; - else - { - StorageExfil = maxRate; - StorageDrain = 0.0; - } - SoilPerc = maxRate; - PavePerc = maxRate; - - //... limit surface infil. by pavement perc - SurfaceInfil = MIN(SurfaceInfil, PavePerc); - } - - //... storage & soil layers are full - else if ( soilThickness > 0.0 && - storageDepth >= storageThickness && - soilTheta >= soilPorosity ) - { - //... soil perc can't exceed storage outflow - maxRate = StorageDrain + StorageExfil; - if ( SoilPerc > maxRate ) SoilPerc = maxRate; - - //... storage outflow can't exceed soil perc - else - { - //... use up available exfiltration capacity first - StorageExfil = MIN(StorageExfil, SoilPerc); - StorageDrain = SoilPerc - StorageExfil; - } - - //... limit surface infil. by available pavement volume - availVolume = (paveThickness - paveDepth) * paveVoidFrac; - maxRate = availVolume / Tstep + PavePerc + PaveEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... soil and pavement layers are full - else if ( soilThickness > 0.0 && - paveDepth >= paveThickness && - soilTheta >= soilPorosity ) - { - PavePerc = MIN(PavePerc, SoilPerc); - SoilPerc = PavePerc; - SurfaceInfil = MIN(SurfaceInfil,PavePerc); - } - - //... no adjoining layers are full - else - { - //... limit storage exfiltration by available storage volume - // (if no soil layer, SoilPerc is same as PavePerc) - maxRate = SoilPerc - StorageEvap + StorageVolume / Tstep; - maxRate = MAX(0.0, maxRate); - StorageExfil = MIN(StorageExfil, maxRate); - - //... limit underdrain flow by volume above drain offset - if ( StorageDrain > 0.0 ) - { - maxRate = -StorageExfil - StorageEvap; - if (storageDepth >= storageThickness ) maxRate += SoilPerc; - if ( theLidProc->drain.offset <= storageDepth ) - { - maxRate += (storageDepth - theLidProc->drain.offset) * - storageVoidFrac/Tstep; - } - maxRate = MAX(maxRate, 0.0); - StorageDrain = MIN(StorageDrain, maxRate); - } - - //... limit soil & pavement outflow by unused storage volume - availVolume = (storageThickness - storageDepth) * storageVoidFrac; - maxRate = availVolume/Tstep + StorageEvap + StorageDrain + StorageExfil; - maxRate = MAX(maxRate, 0.0); - if ( soilThickness > 0.0 ) - { - SoilPerc = MIN(SoilPerc, maxRate); - maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + - SoilPerc; - } - PavePerc = MIN(PavePerc, maxRate); - - //... limit surface infil. by available pavement volume - availVolume = (paveThickness - paveDepth) * paveVoidFrac; - maxRate = availVolume / Tstep + PavePerc + PaveEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... surface outflow - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - - //... compute overall layer flux rates - f[SURF] = SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow; - f[PAVE] = (SurfaceInfil - PaveEvap - PavePerc) / paveVoidFrac; - if ( theLidProc->soil.thickness > 0.0) - { - f[SOIL] = (PavePerc - SoilEvap - SoilPerc) / soilThickness; - storageInflow = SoilPerc; - } - else - { - f[SOIL] = 0.0; - storageInflow = PavePerc; - SoilPerc = 0.0; - } - f[STOR] = (storageInflow - StorageEvap - StorageExfil - StorageDrain) / - storageVoidFrac; -} - -//============================================================================= - -void swaleFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates from a vegetative swale LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - double depth; // depth of surface water in swale (ft) - double topWidth; // top width of full swale (ft) - double botWidth; // bottom width of swale (ft) - double length; // length of swale (ft) - double surfInflow; // inflow rate to swale (cfs) - double surfWidth; // top width at current water depth (ft) - double surfArea; // surface area of current water depth (ft2) - double flowArea; // x-section flow area (ft2) - double lidArea; // surface area of full swale (ft2) - double hydRadius; // hydraulic radius for current depth (ft) - double slope; // slope of swale side wall (run/rise) - double volume; // swale volume at current water depth (ft3) - double dVdT; // change in volume w.r.t. time (cfs) - double dStore; // depression storage depth (ft) - double xDepth; // depth above depression storage (ft) - - //... retrieve state variable from work vector - depth = x[SURF]; - depth = MIN(depth, theLidProc->surface.thickness); - - //... depression storage depth - dStore = 0.0; - - //... get swale's bottom width - // (0.5 ft minimum to avoid numerical problems) - slope = theLidProc->surface.sideSlope; - topWidth = theLidUnit->fullWidth; - topWidth = MAX(topWidth, 0.5); - botWidth = topWidth - 2.0 * slope * theLidProc->surface.thickness; - if ( botWidth < 0.5 ) - { - botWidth = 0.5; - slope = 0.5 * (topWidth - 0.5) / theLidProc->surface.thickness; - } - - //... swale's length - lidArea = theLidUnit->area; - length = lidArea / topWidth; - - //... top width, surface area and flow area of current ponded depth - surfWidth = botWidth + 2.0 * slope * depth; - surfArea = length * surfWidth; - flowArea = (depth * (botWidth + slope * depth)) * - theLidProc->surface.voidFrac; - - //... wet volume and effective depth - volume = length * flowArea; - - //... surface inflow into swale (cfs) - surfInflow = SurfaceInflow * lidArea; - - //... ET rate in cfs - SurfaceEvap = EvapRate * surfArea; - SurfaceEvap = MIN(SurfaceEvap, volume/Tstep); - - //... infiltration rate to native soil in cfs - StorageExfil = SurfaceInfil * surfArea; - - //... no surface outflow if depth below depression storage - xDepth = depth - dStore; - if ( xDepth <= ZERO ) SurfaceOutflow = 0.0; - - //... otherwise compute a surface outflow - else - { - //... modify flow area to remove depression storage, - flowArea -= (dStore * (botWidth + slope * dStore)) * - theLidProc->surface.voidFrac; - if ( flowArea < ZERO ) SurfaceOutflow = 0.0; - else - { - //... compute hydraulic radius - botWidth = botWidth + 2.0 * dStore * slope; - hydRadius = botWidth + 2.0 * xDepth * sqrt(1.0 + slope*slope); - hydRadius = flowArea / hydRadius; - - //... use Manning Eqn. to find outflow rate in cfs - SurfaceOutflow = theLidProc->surface.alpha * flowArea * - pow(hydRadius, 2./3.); - } - } - - //... net flux rate (dV/dt) in cfs - dVdT = surfInflow - SurfaceEvap - StorageExfil - SurfaceOutflow; - - //... when full, any net positive inflow becomes spillage - if ( depth == theLidProc->surface.thickness && dVdT > 0.0 ) - { - SurfaceOutflow += dVdT; - dVdT = 0.0; - } - - //... convert flux rates to ft/s - SurfaceEvap /= lidArea; - StorageExfil /= lidArea; - SurfaceOutflow /= lidArea; - f[SURF] = dVdT / surfArea; - f[SOIL] = 0.0; - f[STOR] = 0.0; - - //... assign values to layer volumes - SurfaceVolume = volume / lidArea; - SoilVolume = 0.0; - StorageVolume = 0.0; -} - -//============================================================================= - -void barrelFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates for a rain barrel LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - double storageDepth = x[STOR]; - double head; - double maxValue; - - //... assign values to layer volumes - SurfaceVolume = 0.0; - SoilVolume = 0.0; - StorageVolume = storageDepth; - - //... initialize flows - SurfaceInfil = 0.0; - SurfaceOutflow = 0.0; - StorageDrain = 0.0; - - //... compute outflow if time since last rain exceeds drain delay - // (dryTime is updated in lid.evalLidUnit at each time step) - if ( theLidProc->drain.delay == 0.0 || - theLidUnit->dryTime >= theLidProc->drain.delay ) - { - head = storageDepth - theLidProc->drain.offset; - if ( head > 0.0 ) - { - StorageDrain = getStorageDrainRate(storageDepth, 0.0, 0.0, 0.0); - maxValue = (head/Tstep); - StorageDrain = MIN(StorageDrain, maxValue); - } - } - - //... limit inflow to available storage - StorageInflow = SurfaceInflow; - maxValue = (theLidProc->storage.thickness - storageDepth) / Tstep + - StorageDrain; - StorageInflow = MIN(StorageInflow, maxValue); - SurfaceInfil = StorageInflow; - - //... assign values to layer flux rates - f[SURF] = SurfaceInflow - StorageInflow; - f[STOR] = StorageInflow - StorageDrain; - f[SOIL] = 0.0; -} - -//============================================================================= - -double getSurfaceOutflowRate(double depth) -// -// Purpose: computes outflow rate from a LID's surface layer. -// Input: depth = depth of ponded water on surface layer (ft) -// Output: returns outflow from surface layer (ft/s) -// -// Note: this function should not be applied to swales or rain barrels. -// -{ - double delta; - double outflow; - - //... no outflow if ponded depth below storage depth - delta = depth - theLidProc->surface.thickness; - if ( delta < 0.0 ) return 0.0; - - //... compute outflow from overland flow Manning equation - outflow = theLidProc->surface.alpha * pow(delta, 5.0/3.0) * - theLidUnit->fullWidth / theLidUnit->area; - outflow = MIN(outflow, delta / Tstep); - return outflow; -} - -//============================================================================= - -double getPavementPermRate() -// -// Purpose: computes reduced permeability of a pavement layer due to -// clogging. -// Input: none -// Output: returns the reduced permeability of the pavement layer (ft/s). -// -{ - double permReduction = 0.0; - double clogFactor= theLidProc->pavement.clogFactor; - double regenDays = theLidProc->pavement.regenDays; - - // ... find permeability reduction due to clogging - if ( clogFactor > 0.0 ) - { - // ... see if permeability regeneration has occurred - // (regeneration is assumed to reduce the total - // volumetric loading that the pavement has received) - if ( regenDays > 0.0 ) - { - if ( OldRunoffTime / 1000.0 / SECperDAY >= theLidUnit->nextRegenDay ) - { - // ... reduce total volume treated by degree of regeneration - theLidUnit->volTreated *= - (1.0 - theLidProc->pavement.regenDegree); - - // ... update next day that regenration occurs - theLidUnit->nextRegenDay += regenDays; - } - } - - // ... find permeabiity reduction factor - permReduction = theLidUnit->volTreated / clogFactor; - permReduction = MIN(permReduction, 1.0); - } - - // ... return the effective pavement permeability - return theLidProc->pavement.kSat * (1.0 - permReduction); -} - -//============================================================================= - -double getSoilPercRate(double theta) -// -// Purpose: computes percolation rate of water through a LID's soil layer. -// Input: theta = moisture content (fraction) -// Output: returns percolation rate within soil layer (ft/s) -// -{ - double delta; // moisture deficit - - // ... no percolation if soil moisture <= field capacity - if ( theta <= theLidProc->soil.fieldCap ) return 0.0; - - // ... perc rate = unsaturated hydraulic conductivity - delta = theLidProc->soil.porosity - theta; - return theLidProc->soil.kSat * exp(-delta * theLidProc->soil.kSlope); - -} - -//============================================================================= - -double getStorageExfilRate() -// -// Purpose: computes exfiltration rate from storage zone into -// native soil beneath a LID. -// Input: depth = depth of water storage zone (ft) -// Output: returns infiltration rate (ft/s) -// -{ - double infil = 0.0; - double clogFactor = 0.0; - - if ( theLidProc->storage.kSat == 0.0 ) return 0.0; - if ( MaxNativeInfil == 0.0 ) return 0.0; - - //... reduction due to clogging - clogFactor = theLidProc->storage.clogFactor; - if ( clogFactor > 0.0 ) - { - clogFactor = theLidUnit->waterBalance.inflow / clogFactor; - clogFactor = MIN(clogFactor, 1.0); - } - - //... infiltration rate = storage Ksat reduced by any clogging - infil = theLidProc->storage.kSat * (1.0 - clogFactor); - - //... limit infiltration rate by any groundwater-imposed limit - return MIN(infil, MaxNativeInfil); -} - -//============================================================================= - -double getStorageDrainRate(double storageDepth, double soilTheta, - double paveDepth, double surfaceDepth) -// -// Purpose: computes underdrain flow rate in a LID's storage layer. -// Input: storageDepth = depth of water in storage layer (ft) -// soilTheta = moisture content of soil layer -// paveDepth = effective depth of water in pavement layer (ft) -// surfaceDepth = depth of ponded water on surface layer (ft) -// Output: returns flow in underdrain (ft/s) -// -// Note: drain eqn. is evaluated in user's units. -// Note: head on drain is water depth in storage layer plus the -// layers above it (soil, pavement, and surface in that order) -// minus the drain outlet offset. -{ - int curve = theLidProc->drain.qCurve; - double head = storageDepth; - double outflow = 0.0; - double paveThickness = theLidProc->pavement.thickness; - double soilThickness = theLidProc->soil.thickness; - double soilPorosity = theLidProc->soil.porosity; - double soilFieldCap = theLidProc->soil.fieldCap; - double storageThickness = theLidProc->storage.thickness; - - // --- storage layer is full - if ( storageDepth >= storageThickness ) - { - // --- a soil layer exists - if ( soilThickness > 0.0 ) - { - // --- increase head by fraction of soil layer saturated - if ( soilTheta > soilFieldCap ) - { - head += (soilTheta - soilFieldCap) / - (soilPorosity - soilFieldCap) * soilThickness; - - // --- soil layer is saturated, increase head by water - // depth in layer above it - if ( soilTheta >= soilPorosity ) - { - if ( paveThickness > 0.0 ) head += paveDepth; - else head += surfaceDepth; - } - } - } - - // --- no soil layer so increase head by water level in pavement - // layer and possibly surface layer - if ( paveThickness > 0.0 ) - { - head += paveDepth; - if ( paveDepth >= paveThickness ) head += surfaceDepth; - } - } - - // --- no outflow if: - // a) no prior outflow and head below open threshold - // b) prior outflow and head below closed threshold - if ( theLidUnit->oldDrainFlow == 0.0 && - head <= theLidProc->drain.hOpen ) return 0.0; - if ( theLidUnit->oldDrainFlow > 0.0 && - head <= theLidProc->drain.hClose ) return 0.0; - - // --- make head relative to drain offset - head -= theLidProc->drain.offset; - - // --- compute drain outflow from underdrain flow equation in user units - // (head in inches or mm, flow rate in in/hr or mm/hr) - if ( head > ZERO ) - { - // --- convert head to user units - head *= UCF(RAINDEPTH); - - // --- compute drain outflow in user units - outflow = theLidProc->drain.coeff * - pow(head, theLidProc->drain.expon); - - // --- apply user-supplied control curve to outflow - if (curve >= 0) outflow *= table_lookup(&Curve[curve], head); - - // --- convert outflow to ft/s - outflow /= UCF(RAINFALL); - } - return outflow; -} - -//============================================================================= - -double getDrainMatOutflow(double depth) -// -// Purpose: computes flow rate through a green roof's drainage mat. -// Input: depth = depth of water in drainage mat (ft) -// Output: returns flow in drainage mat (ft/s) -// -{ - //... default is to pass all inflow - double result = SoilPerc; - - //... otherwise use Manning eqn. if its parameters were supplied - if ( theLidProc->drainMat.alpha > 0.0 ) - { - result = theLidProc->drainMat.alpha * pow(depth, 5.0/3.0) * - theLidUnit->fullWidth / theLidUnit->area * - theLidProc->drainMat.voidFrac; - } - return result; -} - -//============================================================================= - -void getEvapRates(double surfaceVol, double paveVol, double soilVol, - double storageVol, double pervFrac) -// -// Purpose: computes surface, pavement, soil, and storage evaporation rates. -// Input: surfaceVol = volume/area of ponded water on surface layer (ft) -// paveVol = volume/area of water in pavement pores (ft) -// soilVol = volume/area of water in soil (or pavement) pores (ft) -// storageVol = volume/area of water in storage layer (ft) -// pervFrac = fraction of surface layer that is pervious -// Output: none -// -{ - double availEvap; - - //... surface evaporation flux - availEvap = EvapRate; - SurfaceEvap = MIN(availEvap, surfaceVol/Tstep); - SurfaceEvap = MAX(0.0, SurfaceEvap); - availEvap = MAX(0.0, (availEvap - SurfaceEvap)); - availEvap *= pervFrac; - - //... no subsurface evap if water is infiltrating - if ( SurfaceInfil > 0.0 ) - { - PaveEvap = 0.0; - SoilEvap = 0.0; - StorageEvap = 0.0; - } - else - { - //... pavement evaporation flux - PaveEvap = MIN(availEvap, paveVol / Tstep); - availEvap = MAX(0.0, (availEvap - PaveEvap)); - - //... soil evaporation flux - SoilEvap = MIN(availEvap, soilVol / Tstep); - availEvap = MAX(0.0, (availEvap - SoilEvap)); - - //... storage evaporation flux - StorageEvap = MIN(availEvap, storageVol / Tstep); - } -} - -//============================================================================= - -double getSurfaceOverflowRate(double* surfaceDepth) -// -// Purpose: finds surface overflow rate from a LID unit. -// Input: surfaceDepth = depth of water stored in surface layer (ft) -// Output: returns the overflow rate (ft/s) -// -{ - double delta = *surfaceDepth - theLidProc->surface.thickness; - if ( delta <= 0.0 ) return 0.0; - *surfaceDepth = theLidProc->surface.thickness; - return delta * theLidProc->surface.voidFrac / Tstep; -} - -//============================================================================= - -void updateWaterBalance(TLidUnit *lidUnit, double inflow, double evap, - double infil, double surfFlow, double drainFlow, double storage) -// -// Purpose: updates components of the water mass balance for a LID unit -// over the current time step. -// Input: lidUnit = a particular LID unit -// inflow = runon + rainfall to the LID unit (ft/s) -// evap = evaporation rate from the unit (ft/s) -// infil = infiltration out the bottom of the unit (ft/s) -// surfFlow = surface runoff from the unit (ft/s) -// drainFlow = underdrain flow from the unit -// storage = volume of water stored in the unit (ft) -// Output: none -// -{ - lidUnit->volTreated += inflow * Tstep; - lidUnit->waterBalance.inflow += inflow * Tstep; - lidUnit->waterBalance.evap += evap * Tstep; - lidUnit->waterBalance.infil += infil * Tstep; - lidUnit->waterBalance.surfFlow += surfFlow * Tstep; - lidUnit->waterBalance.drainFlow += drainFlow * Tstep; - lidUnit->waterBalance.finalVol = storage; -} - -//============================================================================= - -int modpuls_solve(int n, double* x, double* xOld, double* xPrev, - double* xMin, double* xMax, double* xTol, - double* qOld, double* q, double dt, double omega, - void (*derivs)(double*, double*)) -// -// Purpose: solves system of equations dx/dt = q(x) for x at end of time step -// dt using a modified Puls method. -// Input: n = number of state variables -// x = vector of state variables -// xOld = state variable values at start of time step -// xPrev = state variable values from previous iteration -// xMin = lower limits on state variables -// xMax = upper limits on state variables -// xTol = convergence tolerances on state variables -// qOld = flux rates at start of time step -// q = flux rates at end of time step -// dt = time step (sec) -// omega = time weighting parameter (use 0 for Euler method -// or 0.5 for modified Puls method) -// derivs = pointer to function that computes flux rates q as a -// function of state variables x -// Output: returns number of steps required for convergence (or 0 if -// process doesn't converge) -// -{ - int i; - int canStop; - int steps = 1; - int maxSteps = 20; - - //... initialize state variable values - for (i=0; i 0.0 && - fabs(x[i] - xPrev[i]) > xTol[i] ) canStop = 0; - xPrev[i] = x[i]; - } - - //... return if process converges - if (canStop) return steps; - steps++; - } - - //... no convergence so return 0 - return 0; -} diff --git a/src/link.c b/src/link.c deleted file mode 100644 index 6de3688d7..000000000 --- a/src/link.c +++ /dev/null @@ -1,2675 +0,0 @@ -//----------------------------------------------------------------------------- -// link.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// M. Tryby (EPA) -// -// Conveyance system link functions -// -// Update History -// ============== -// Build 5.1.007: -// - Optional surcharging of weirs introduced. -// Build 5.1.008: -// - Bug in finding flow through surcharged weir fixed. -// - Bug in finding if conduit is upstrm/dnstrm full fixed. -// - Monthly conductivity adjustment applied to conduit seepage. -// - Conduit seepage limited by conduit's flow rate. -// Build 5.1.010: -// - Support added for new ROADWAY_WEIR object. -// - Time of last setting change initialized for links. -// Build 5.1.011: -// - Crest elevation of regulator links raised to downstream invert. -// - Fixed converting roadWidth weir parameter to internal units. -// - Weir shape parameter deprecated. -// - Extra geometric parameters ignored for non-conduit open rectangular -// cross sections. -// Build 5.1.012: -// - Conduit seepage rate now based on flow width, not wetted perimeter. -// - Formula for side flow weir corrected. -// - Crest length contraction adjustments corrected. -// Build 5.1.013: -// - Maximum depth adjustments made for storage units that can surcharge. -// - Support added for head-dependent weir coefficient curves. -// - Adjustment of regulator link crest offset to match downstream node invert -// now only done for Dynamic Wave flow routing. -// Build 5.1.014: -// - Conduit evap. and seepage losses initialized to 0 in conduit_initState() -// and not allowed to exceed current flow rate in conduit_getLossRate(). -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for variable speed pumps. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" -#include "inlet.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double MIN_DELTA_Z = 0.001; // minimum elevation change for conduit - // slopes (ft) - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// link_readParams (called by parseLine in input.c) -// link_readXsectParams (called by parseLine in input.c) -// link_readLossParams (called by parseLine in input.c) -// link_validate (called by project_validate in project.c) -// link_initState (called by initObjects in swmm5.c) -// link_setOldHydState (called by routing_execute in routing.c) -// link_setOldQualState (called by routing_execute in routing.c) -// link_setTargetSetting (called by routing_execute in routing.c) -// link_setSetting (called by routing_execute in routing.c) -// link_getResults (called by output_saveLinkResults) -// link_getLength (called in dwflow.c, kinwave.c & flowrout.c) -// link_getFroude (called in dwflow.c) -// link_getInflow (called in flowrout.c & dynwave.c) -// link_setOutfallDepth (called in flowrout.c & dynwave.c) -// link_getYcrit (called by link_setOutfallDepth & in dwflow.c) -// link_getYnorm (called by conduit_initState, link_setOutfallDepth & in dwflow.c) -// link_getVelocity (called by link_getResults & stats_updateLinkStats) -// link_getPower (called by stats_updateLinkStats in stats.c) -// link_getLossRate (called in dwflow.c, kinwave.c & flowrout.c) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static void link_setParams(int j, int type, int n1, int n2, int k, double x[]); -static void link_convertOffsets(int j); -static double link_getOffsetHeight(int j, double offset, double elev); - -static int conduit_readParams(int j, int k, char* tok[], int ntoks); -static void conduit_validate(int j, int k); -static void conduit_initState(int j, int k); -static void conduit_reverse(int j, int k); -static double conduit_getLength(int j); -static double conduit_getLengthFactor(int j, int k, double roughness); -static double conduit_getSlope(int j); -static double conduit_getInflow(int j); -static double conduit_getLossRate(int j, double q); - -static int pump_readParams(int j, int k, char* tok[], int ntoks); -static void pump_validate(int j, int k); -static void pump_initState(int j, int k); -static double pump_getInflow(int j); - -static int orifice_readParams(int j, int k, char* tok[], int ntoks); -static void orifice_validate(int j, int k); -static void orifice_setSetting(int j, double tstep); -static double orifice_getWeirCoeff(int j, int k, double h); -static double orifice_getInflow(int j); -static double orifice_getFlow(int j, int k, double head, double f, - int hasFlapGate); - -static int weir_readParams(int j, int k, char* tok[], int ntoks); -static void weir_validate(int j, int k); -static void weir_setSetting(int j); -static double weir_getInflow(int j); -static double weir_getOpenArea(int j, double y); -static void weir_getFlow(int j, int k, double head, double dir, - int hasFlapGate, double* q1, double* q2); -static double weir_getOrificeFlow(int j, double head, double y, double cOrif); -static double weir_getdqdh(int k, double dir, double h, double q1, double q2); - -static int outlet_readParams(int j, int k, char* tok[], int ntoks); -static double outlet_getFlow(int k, double head); -static double outlet_getInflow(int j); - - -//============================================================================= - -int link_readParams(int j, int type, int k, char* tok[], int ntoks) -// -// Input: j = link index -// type = link type code -// k = link type index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads parameters for a specific type of link from a -// tokenized line of input data. -// -{ - switch ( type ) - { - case CONDUIT: return conduit_readParams(j, k, tok, ntoks); - case PUMP: return pump_readParams(j, k, tok, ntoks); - case ORIFICE: return orifice_readParams(j, k, tok, ntoks); - case WEIR: return weir_readParams(j, k, tok, ntoks); - case OUTLET: return outlet_readParams(j, k, tok, ntoks); - default: return 0; - } -} - -//============================================================================= - -int link_readXsectParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads a link's cross section parameters from a tokenized -// line of input data. -// Formats: -// Link Shape Geom1 Geom2 Geom3 Geom4 (Barrels Culvert) -// Link IRREGULAR TransectID -// Link STREET StreetID -// -{ - int i, j, k; - double x[4]; - - // --- check for minimum number of tokens - if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); - - // --- get index of link - j = project_findObject(LINK, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- get code of xsection shape - k = findmatch(tok[1], XsectTypeWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- assign default number of barrels to conduit - if ( Link[j].type == CONDUIT ) Conduit[Link[j].subIndex].barrels = 1; - - // --- assume link is not a culvert - Link[j].xsect.culvertCode = 0; - - // --- for irregular shape, find index of transect object - if ( k == IRREGULAR ) - { - i = project_findObject(TRANSECT, tok[2]); - if ( i < 0 ) return error_setInpError(ERR_NAME, tok[2]); - Link[j].xsect.type = k; - Link[j].xsect.transect = i; - return 0; - } - - // --- for street cross section, find index of Street object - else if (k == STREET_XSECT) - { - i = project_findObject(STREET, tok[2]); - if (i < 0) return error_setInpError(ERR_NAME, tok[2]); - Link[j].xsect.type = k; - Link[j].xsect.transect = i; - return 0; - } - - else - { - // --- check that geometric parameters are present - if (ntoks < 6) return error_setInpError(ERR_ITEMS, ""); - - // --- parse max. depth & shape curve for a custom shape - if ( k == CUSTOM ) - { - if ( !getDouble(tok[2], &x[0]) || x[0] <= 0.0 ) - return error_setInpError(ERR_NUMBER, tok[2]); - i = project_findObject(CURVE, tok[3]); - if ( i < 0 ) return error_setInpError(ERR_NAME, tok[3]); - Link[j].xsect.type = k; - Link[j].xsect.transect = i; - Link[j].xsect.yFull = x[0] / UCF(LENGTH); - } - - // --- parse and save geometric parameters - else for (i = 2; i <= 5; i++) - { - if ( !getDouble(tok[i], &x[i-2]) ) - return error_setInpError(ERR_NUMBER, tok[i]); - } - - // --- ignore extra parameters for non-conduit open rectangular shapes - if ( Link[j].type != CONDUIT && k == RECT_OPEN ) - { - x[2] = 0.0; - x[3] = 0.0; - } - if ( !xsect_setParams(&Link[j].xsect, k, x, UCF(LENGTH)) ) - { - return error_setInpError(ERR_NUMBER, ""); - } - - // --- parse number of barrels if present - if ( Link[j].type == CONDUIT && ntoks >= 7 ) - { - i = atoi(tok[6]); - if ( i <= 0 ) return error_setInpError(ERR_NUMBER, tok[6]); - else Conduit[Link[j].subIndex].barrels = (char)i; - } - - // --- parse culvert code if present - if ( Link[j].type == CONDUIT && ntoks >= 8 ) - { - i = atoi(tok[7]); - if ( i < 0 ) return error_setInpError(ERR_NUMBER, tok[7]); - else Link[j].xsect.culvertCode = i; - } - } - return 0; -} - -//============================================================================= - -int link_readLossParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads local loss parameters for a link from a tokenized -// line of input data. -// -// Format: LinkID cInlet cOutlet cAvg FlapGate(YES/NO) SeepRate -// -{ - int i, j, k; - double x[3]; - double seepRate = 0.0; - - if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); - j = project_findObject(LINK, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - for (i=1; i<=3; i++) - { - if ( ! getDouble(tok[i], &x[i-1]) || x[i-1] < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[i]); - } - k = 0; - if ( ntoks >= 5 ) - { - k = findmatch(tok[4], NoYesWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); - } - if ( ntoks >= 6 ) - { - if ( ! getDouble(tok[5], &seepRate) ) - return error_setInpError(ERR_NUMBER, tok[5]); - } - Link[j].cLossInlet = x[0]; - Link[j].cLossOutlet = x[1]; - Link[j].cLossAvg = x[2]; - Link[j].hasFlapGate = k; - Link[j].seepRate = seepRate / UCF(RAINFALL); - return 0; -} - -//============================================================================= - -void link_setParams(int j, int type, int n1, int n2, int k, double x[]) -// -// Input: j = link index -// type = link type code -// n1 = index of upstream node -// n2 = index of downstream node -// k = index of link's sub-type -// x = array of parameter values -// Output: none -// Purpose: sets parameters for a link. -// -{ - Link[j].node1 = n1; - Link[j].node2 = n2; - Link[j].type = type; - Link[j].subIndex = k; - Link[j].offset1 = 0.0; - Link[j].offset2 = 0.0; - Link[j].q0 = 0.0; - Link[j].qFull = 0.0; - Link[j].setting = 1.0; - Link[j].targetSetting = 1.0; - Link[j].hasFlapGate = 0; - Link[j].qLimit = 0.0; // 0 means that no limit is defined - Link[j].direction = 1; - - switch (type) - { - case CONDUIT: - Conduit[k].length = x[0] / UCF(LENGTH); - Conduit[k].modLength = Conduit[k].length; - Conduit[k].roughness = x[1]; - Link[j].offset1 = x[2] / UCF(LENGTH); - Link[j].offset2 = x[3] / UCF(LENGTH); - Link[j].q0 = x[4] / UCF(FLOW); - Link[j].qLimit = x[5] / UCF(FLOW); - break; - - case PUMP: - Pump[k].pumpCurve = (int)x[0]; - Link[j].hasFlapGate = FALSE; - Pump[k].initSetting = x[1]; - Pump[k].yOn = x[2] / UCF(LENGTH); - Pump[k].yOff = x[3] / UCF(LENGTH); - Pump[k].xMin = 0.0; - Pump[k].xMax = 0.0; - break; - - case ORIFICE: - Orifice[k].type = (int)x[0]; - Link[j].offset1 = x[1] / UCF(LENGTH); - Link[j].offset2 = Link[j].offset1; - Orifice[k].cDisch = x[2]; - Link[j].hasFlapGate = (x[3] > 0.0) ? 1 : 0; - Orifice[k].orate = x[4] * 3600.0; - break; - - case WEIR: - Weir[k].type = (int)x[0]; - Link[j].offset1 = x[1] / UCF(LENGTH); - Link[j].offset2 = Link[j].offset1; - Weir[k].cDisch1 = x[2]; - Link[j].hasFlapGate = (x[3] > 0.0) ? 1 : 0; - Weir[k].endCon = x[4]; - Weir[k].cDisch2 = x[5]; - Weir[k].canSurcharge = (int)x[6]; - Weir[k].roadWidth = x[7] / UCF(LENGTH); - Weir[k].roadSurface = (int)x[8]; - Weir[k].cdCurve = (int)x[9]; - break; - - case OUTLET: - Link[j].offset1 = x[0] / UCF(LENGTH); - Link[j].offset2 = Link[j].offset1; - Outlet[k].qCoeff = x[1]; - Outlet[k].qExpon = x[2]; - Outlet[k].qCurve = (int)x[3]; - Link[j].hasFlapGate = (x[4] > 0.0) ? 1 : 0; - Outlet[k].curveType = (int)x[5]; - - xsect_setParams(&Link[j].xsect, DUMMY, NULL, 0.0); - break; - - } -} - -//============================================================================= - -void link_validate(int j) -// -// Input: j = link index -// Output: none -// Purpose: validates a link's properties. -// -{ - int n; - - if ( LinkOffsets == ELEV_OFFSET ) link_convertOffsets(j); - switch ( Link[j].type ) - { - case CONDUIT: conduit_validate(j, Link[j].subIndex); break; - case PUMP: pump_validate(j, Link[j].subIndex); break; - case ORIFICE: orifice_validate(j, Link[j].subIndex); break; - case WEIR: weir_validate(j, Link[j].subIndex); break; - } - - // --- check if crest of regulator opening < invert of downstream node - switch ( Link[j].type ) - { - case ORIFICE: - case WEIR: - case OUTLET: - if ( Node[Link[j].node1].invertElev + Link[j].offset1 < - Node[Link[j].node2].invertElev ) - { - if (RouteModel == DW) - { - Link[j].offset1 = Node[Link[j].node2].invertElev - - Node[Link[j].node1].invertElev; - report_writeWarningMsg(WARN10b, Link[j].ID); - } - else report_writeWarningMsg(WARN10a, Link[j].ID); - } - } - - // --- force max. depth of end nodes to be >= link crown height - // at non-storage nodes - - // --- skip pumps and bottom orifices - if ( Link[j].type == PUMP || - (Link[j].type == ORIFICE && - Orifice[Link[j].subIndex].type == BOTTOM_ORIFICE) ) return; - - // --- extend upstream node's full depth to link's crown elevation - n = Link[j].node1; - if ( Node[n].type != STORAGE || Node[n].surDepth > 0.0 ) - { - Node[n].fullDepth = MAX(Node[n].fullDepth, - Link[j].offset1 + Link[j].xsect.yFull); - } - - // --- do same for downstream node only for conduit links - n = Link[j].node2; - if ( (Node[n].type != STORAGE || Node[n].surDepth > 0.0) && - Link[j].type == CONDUIT ) - { - Node[n].fullDepth = MAX(Node[n].fullDepth, - Link[j].offset2 + Link[j].xsect.yFull); - } -} - -//============================================================================= - -void link_convertOffsets(int j) -// -// Input: j = link index -// Output: none -// Purpose: converts offset elevations to offset heights for a link. -// -{ - double elev; - - elev = Node[Link[j].node1].invertElev; - Link[j].offset1 = link_getOffsetHeight(j, Link[j].offset1, elev); - if ( Link[j].type == CONDUIT ) - { - elev = Node[Link[j].node2].invertElev; - Link[j].offset2 = link_getOffsetHeight(j, Link[j].offset2, elev); - } - else Link[j].offset2 = Link[j].offset1; -} - -//============================================================================= - -double link_getOffsetHeight(int j, double offset, double elev) -// -// Input: j = link index -// offset = link elevation offset (ft) -// elev = node invert elevation (ft) -// Output: returns offset distance above node invert (ft) -// Purpose: finds offset height for one end of a link. -// -{ - if ( offset <= MISSING || Link[j].type == PUMP) return 0.0; - offset -= elev; - if ( offset >= 0.0 ) return offset; - if ( offset >= -MIN_DELTA_Z ) return 0.0; - report_writeWarningMsg(WARN03, Link[j].ID); - return 0.0; -} - -//============================================================================= - -void link_initState(int j) -// -// Input: j = link index -// Output: none -// Purpose: initializes a link's state variables at start of simulation. -// -{ - int p; - - // --- initialize hydraulic state - Link[j].oldFlow = Link[j].q0; - Link[j].newFlow = Link[j].q0; - Link[j].oldDepth = 0.0; - Link[j].newDepth = 0.0; - Link[j].oldVolume = 0.0; - Link[j].newVolume = 0.0; - Link[j].setting = 1.0; - Link[j].targetSetting = 1.0; - Link[j].timeLastSet = StartDate; - Link[j].inletControl = FALSE; - Link[j].normalFlow = FALSE; - if ( Link[j].type == CONDUIT ) conduit_initState(j, Link[j].subIndex); - if ( Link[j].type == PUMP ) pump_initState(j, Link[j].subIndex); - - // --- initialize water quality state - for (p = 0; p < Nobjects[POLLUT]; p++) - { - Link[j].oldQual[p] = 0.0; - Link[j].newQual[p] = 0.0; - Link[j].totalLoad[p] = 0.0; - } -} - -//============================================================================= - -double link_getInflow(int j) -// -// Input: j = link index -// Output: returns link flow rate (cfs) -// Purpose: finds total flow entering a link during current time step. -// -{ - if ( Link[j].setting == 0 ) return 0.0; - switch ( Link[j].type ) - { - case CONDUIT: return conduit_getInflow(j); - case PUMP: return pump_getInflow(j); - case ORIFICE: return orifice_getInflow(j); - case WEIR: return weir_getInflow(j); - case OUTLET: return outlet_getInflow(j); - default: return node_getOutflow(Link[j].node1, j); - } -} - -//============================================================================= - -void link_setOldHydState(int j) -// -// Input: j = link index -// Output: none -// Purpose: replaces link's old hydraulic state values with current ones. -// -{ - int k; - - Link[j].oldDepth = Link[j].newDepth; - Link[j].oldFlow = Link[j].newFlow; - Link[j].oldVolume = Link[j].newVolume; - - if ( Link[j].type == CONDUIT ) - { - k = Link[j].subIndex; - Conduit[k].q1Old = Conduit[k].q1; - Conduit[k].q2Old = Conduit[k].q2; - } -} - -//============================================================================= - -void link_setOldQualState(int j) -// -// Input: j = link index -// Output: none -// Purpose: replaces link's old water quality state values with current ones. -// -{ - int p; - for (p = 0; p < Nobjects[POLLUT]; p++) - { - Link[j].oldQual[p] = Link[j].newQual[p]; - Link[j].newQual[p] = 0.0; - } -} - -//============================================================================= - -void link_setTargetSetting(int j) -// -// Input: j = link index -// Output: none -// Purpose: updates a link's target setting. -// -{ - int k, n1; - if ( Link[j].type == PUMP ) - { - k = Link[j].subIndex; - n1 = Link[j].node1; - Link[j].targetSetting = Link[j].setting; - if ( Pump[k].yOff > 0.0 && - Link[j].setting > 0.0 && - Node[n1].newDepth < Pump[k].yOff ) Link[j].targetSetting = 0.0; - if ( Pump[k].yOn > 0.0 && - Link[j].setting == 0.0 && - Node[n1].newDepth > Pump[k].yOn ) Link[j].targetSetting = 1.0; - } -} - -//============================================================================= - -void link_setSetting(int j, double tstep) -// -// Input: j = link index -// tstep = time step over which setting is adjusted -// Output: none -// Purpose: updates a link's setting as a result of a control action. -// -{ - if ( Link[j].type == ORIFICE ) orifice_setSetting(j, tstep); - else if ( Link[j].type == WEIR ) weir_setSetting(j); - else Link[j].setting = Link[j].targetSetting; -} - -//============================================================================= - -int link_setFlapGate(int j, int n1, int n2, double q) -// -// Input: j = link index -// n1 = index of node on upstream end of link -// n2 = index of node on downstream end of link -// q = signed flow value (value and units don't matter) -// Output: returns TRUE if there is reverse flow through a flap gate -// associated with the link. -// Purpose: based on the sign of the flow, determines if a flap gate -// associated with the link should close or not. -// -{ - int n = -1; - - // --- check for reverse flow through link's flap gate - if ( Link[j].hasFlapGate ) - { - if ( q * (double)Link[j].direction < 0.0 ) return TRUE; - } - - // --- check for Outfall with flap gate node on inflow end of link - if ( q < 0.0 ) n = n2; - if ( q > 0.0 ) n = n1; - if ( n >= 0 && - Node[n].type == OUTFALL && - Outfall[Node[n].subIndex].hasFlapGate ) return TRUE; - return FALSE; -} - -//============================================================================= - -void link_getResults(int j, double f, float x[]) -// -// Input: j = link index -// f = time weighting factor -// Output: x = array of weighted results -// Purpose: retrieves time-weighted average of old and new results for a link. -// -{ - int p; // pollutant index - double y, // depth - q, // flow - u, // velocity - v, // volume - c; // capacity, setting or concentration - double f1 = 1.0 - f; - - y = f1*Link[j].oldDepth + f*Link[j].newDepth; - q = f1*Link[j].oldFlow + f*Link[j].newFlow; - v = f1*Link[j].oldVolume + f*Link[j].newVolume; - u = link_getVelocity(j, q, y); - c = 0.0; - if (Link[j].type == CONDUIT) - { - if (Link[j].xsect.type != DUMMY) - c = xsect_getAofY(&Link[j].xsect, y) / Link[j].xsect.aFull; - } - else c = Link[j].setting; - - // --- override time weighting for pump flow between on/off states - if (Link[j].type == PUMP && Link[j].oldFlow*Link[j].newFlow == 0.0) - { - if ( f >= f1 ) q = Link[j].newFlow; - else q = Link[j].oldFlow; - } - - y *= UCF(LENGTH); - v *= UCF(VOLUME); - q *= UCF(FLOW) * (double)Link[j].direction; - u *= UCF(LENGTH) * (double)Link[j].direction; - x[LINK_DEPTH] = (float)y; - x[LINK_FLOW] = (float)q; - x[LINK_VELOCITY] = (float)u; - x[LINK_VOLUME] = (float)v; - x[LINK_CAPACITY] = (float)c; - - if ( !IgnoreQuality ) for (p = 0; p < Nobjects[POLLUT]; p++) - { - c = f1*Link[j].oldQual[p] + f*Link[j].newQual[p]; - x[LINK_QUAL+p] = (float)c; - } -} - -//============================================================================= - -void link_setOutfallDepth(int j) -// -// Input: j = link index -// Output: none -// Purpose: sets depth at outfall node connected to link j. -// -{ - int k; // conduit index - int n; // outfall node index - double z; // invert offset height (ft) - double q; // flow rate (cfs) - double yCrit = 0.0; // critical flow depth (ft) - double yNorm = 0.0; // normal flow depth (ft) - - // --- find which end node of link is an outfall - if ( Node[Link[j].node2].type == OUTFALL ) - { - n = Link[j].node2; - z = Link[j].offset2; - } - else if ( Node[Link[j].node1].type == OUTFALL ) - { - n = Link[j].node1; - z = Link[j].offset1; - } - else return; - - // --- find both normal & critical depth for current flow - if ( Link[j].type == CONDUIT ) - { - k = Link[j].subIndex; - q = fabs(Link[j].newFlow / Conduit[k].barrels); - yNorm = link_getYnorm(j, q); - yCrit = link_getYcrit(j, q); - } - - // --- set new depth at node - node_setOutletDepth(n, yNorm, yCrit, z); -} - -//============================================================================= - -double link_getYcrit(int j, double q) -// -// Input: j = link index -// q = link flow rate (cfs) -// Output: returns critical depth (ft) -// Purpose: computes critical depth for given flow rate. -// -{ - return xsect_getYcrit(&Link[j].xsect, q); -} - -//============================================================================= - -double link_getYnorm(int j, double q) -// -// Input: j = link index -// q = link flow rate (cfs) -// Output: returns normal depth (ft) -// Purpose: computes normal depth for given flow rate. -// -{ - int k; - double s, a, y; - - if ( Link[j].type != CONDUIT ) return 0.0; - if ( Link[j].xsect.type == DUMMY ) return 0.0; - q = fabs(q); - k = Link[j].subIndex; - if ( q > Conduit[k].qMax ) q = Conduit[k].qMax; - if ( q <= 0.0 ) return 0.0; - s = q / Conduit[k].beta; - a = xsect_getAofS(&Link[j].xsect, s); - y = xsect_getYofA(&Link[j].xsect, a); - return y; -} - -//============================================================================= - -double link_getLength(int j) -// -// Input: j = link index -// Output: returns length (ft) -// Purpose: finds true length of a link. -// -{ - if ( Link[j].type == CONDUIT ) return conduit_getLength(j); - return 0.0; -} - -//============================================================================= - -double link_getVelocity(int j, double flow, double depth) -// -// Input: j = link index -// flow = link flow rate (cfs) -// depth = link flow depth (ft) -// Output: returns flow velocity (fps) -// Purpose: finds flow velocity given flow and depth. -// -{ - double area; - double veloc = 0.0; - int k; - - if ( depth <= 0.01 ) return 0.0; - if ( Link[j].type == CONDUIT ) - { - k = Link[j].subIndex; - flow /= Conduit[k].barrels; - area = xsect_getAofY(&Link[j].xsect, depth); - if (area > FUDGE ) veloc = flow / area; - } - return veloc; -} - -//============================================================================= - -double link_getFroude(int j, double v, double y) -// -// Input: j = link index -// v = flow velocity (fps) -// y = flow depth (ft) -// Output: returns Froude Number -// Purpose: computes Froude Number for given velocity and flow depth -// -{ - TXsect* xsect = &Link[j].xsect; - - // --- return 0 if link is not a conduit - if ( Link[j].type != CONDUIT ) return 0.0; - - // --- return 0 if link empty or closed conduit is full - if ( y <= FUDGE ) return 0.0; - if ( !xsect_isOpen(xsect->type) && - xsect->yFull - y <= FUDGE ) return 0.0; - - // --- compute hydraulic depth - y = xsect_getAofY(xsect, y) / xsect_getWofY(xsect, y); - - // --- compute Froude No. - return fabs(v) / sqrt(GRAVITY * y); -} - -//============================================================================= - -double link_getPower(int j) -// -// Input: j = link index -// Output: returns power consumed by link in kwatts -// Purpose: computes power consumed by head loss (or head gain) of -// water flowing through a link -// -{ - int n1 = Link[j].node1; - int n2 = Link[j].node2; - double dh = (Node[n1].invertElev + Node[n1].newDepth) - - (Node[n2].invertElev + Node[n2].newDepth); - double q = fabs(Link[j].newFlow); - return fabs(dh) * q / 8.814 * KWperHP; -} - -//============================================================================= - -double link_getLossRate(int j, double q) -// -// Input: j = link index -// q = flow rate (ft3/sec) -// tstep = time step (sec) -// Output: returns uniform loss rate in link (ft3/sec) -// Purpose: computes rate at which flow volume is lost in a link due to -// evaporation and seepage. -// -{ - if ( Link[j].type == CONDUIT ) return conduit_getLossRate(j, q); - else return 0.0; -} - -//============================================================================= - -char link_getFullState(double a1, double a2, double aFull) -// -// Input: a1 = upstream link area (ft2) -// a2 = downstream link area (ft2) -// aFull = area of full conduit -// Output: returns fullness state of a link -// Purpose: determines if a link is upstream, downstream or completely full. -// -{ - if ( a1 >= aFull ) - { - if ( a2 >= aFull ) return ALL_FULL; - else return UP_FULL; - } - if ( a2 >= aFull ) return DN_FULL; - return 0; -} - -//============================================================================= -// C O N D U I T M E T H O D S -//============================================================================= - -int conduit_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = conduit index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads conduit parameters from a tokenzed line of input. -// -{ - int n1, n2; - double x[6]; - char* id; - - // --- check for valid ID and end node IDs - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); // link ID - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); // upstrm. node - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); // dwnstrm. node - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- parse length & Mannings N - if ( !getDouble(tok[3], &x[0]) ) - return error_setInpError(ERR_NUMBER, tok[3]); - if ( !getDouble(tok[4], &x[1]) ) - return error_setInpError(ERR_NUMBER, tok[4]); - - // --- parse offsets - if ( LinkOffsets == ELEV_OFFSET && *tok[5] == '*' ) x[2] = MISSING; - else if ( !getDouble(tok[5], &x[2]) ) - return error_setInpError(ERR_NUMBER, tok[5]); - if ( LinkOffsets == ELEV_OFFSET && *tok[6] == '*' ) x[3] = MISSING; - else if ( !getDouble(tok[6], &x[3]) ) - return error_setInpError(ERR_NUMBER, tok[6]); - - // --- parse optional parameters - x[4] = 0.0; // init. flow - if ( ntoks >= 8 ) - { - if ( !getDouble(tok[7], &x[4]) ) - return error_setInpError(ERR_NUMBER, tok[7]); - } - x[5] = 0.0; - if ( ntoks >= 9 ) - { - if ( !getDouble(tok[8], &x[5]) ) - return error_setInpError(ERR_NUMBER, tok[8]); - } - - // --- add parameters to data base - Link[j].ID = id; - link_setParams(j, CONDUIT, n1, n2, k, x); - return 0; -} - -//============================================================================= - -void conduit_validate(int j, int k) -// -// Input: j = link index -// k = conduit index -// Output: none -// Purpose: validates a conduit's properties. -// -{ - double aa; - double lengthFactor, roughness, slope; - - // --- a storage node cannot have a dummy outflow link - if ( Link[j].xsect.type == DUMMY && RouteModel == DW ) - { - if ( Node[Link[j].node1].type == STORAGE ) - { - report_writeErrorMsg(ERR_DUMMY_LINK, Node[Link[j].node1].ID); - return; - } - } - - // --- if custom xsection, then set its parameters - if ( Link[j].xsect.type == CUSTOM ) - xsect_setCustomXsectParams(&Link[j].xsect); - - // --- if irreg. xsection, assign transect roughness to conduit - if ( Link[j].xsect.type == IRREGULAR ) - { - xsect_setIrregXsectParams(&Link[j].xsect); - Conduit[k].roughness = Transect[Link[j].xsect.transect].roughness; - } - - // --- if street xsection, then set its parameters - if (Link[j].xsect.type == STREET_XSECT) - { - xsect_setStreetXsectParams(&Link[j].xsect); - Conduit[k].roughness = Street[Link[j].xsect.transect].roughness; - } - - // --- if force main xsection, adjust units on D-W roughness height - if ( Link[j].xsect.type == FORCE_MAIN ) - { - if ( ForceMainEqn == D_W ) Link[j].xsect.rBot /= UCF(RAINDEPTH); - if ( Link[j].xsect.rBot <= 0.0 ) - report_writeErrorMsg(ERR_XSECT, Link[j].ID); - } - - // --- check for valid length & roughness - if ( Conduit[k].length <= 0.0 ) - report_writeErrorMsg(ERR_LENGTH, Link[j].ID); - if ( Conduit[k].roughness <= 0.0 ) - report_writeErrorMsg(ERR_ROUGHNESS, Link[j].ID); - if ( Conduit[k].barrels <= 0 ) - report_writeErrorMsg(ERR_BARRELS, Link[j].ID); - - // --- check for valid xsection - if ( Link[j].xsect.type != DUMMY ) - { - if ( Link[j].xsect.type < 0 ) - report_writeErrorMsg(ERR_NO_XSECT, Link[j].ID); - else if ( Link[j].xsect.aFull <= 0.0 ) - report_writeErrorMsg(ERR_XSECT, Link[j].ID); - } - if ( ErrorCode ) return; - - // --- check for negative offsets - if ( Link[j].offset1 < 0.0 ) - { - report_writeWarningMsg(WARN03, Link[j].ID); - Link[j].offset1 = 0.0; - } - if ( Link[j].offset2 < 0.0 ) - { - report_writeWarningMsg(WARN03, Link[j].ID); - Link[j].offset2 = 0.0; - } - - // --- adjust conduit offsets for partly filled circular xsection - if ( Link[j].xsect.type == FILLED_CIRCULAR ) - { - Link[j].offset1 += Link[j].xsect.yBot; - Link[j].offset2 += Link[j].xsect.yBot; - } - - // --- compute conduit slope - slope = conduit_getSlope(j); - Conduit[k].slope = slope; - - // --- reverse orientation of conduit if using dynamic wave routing - // and slope is negative - if ( RouteModel == DW && - slope < 0.0 && - Link[j].xsect.type != DUMMY ) - { - conduit_reverse(j, k); - } - - // --- get equivalent Manning roughness for Force Mains - // for use when pipe is partly full - roughness = Conduit[k].roughness; - if ( RouteModel == DW && Link[j].xsect.type == FORCE_MAIN ) - { - roughness = forcemain_getEquivN(j, k); - } - - // --- adjust roughness for meandering natural channels - if ( Link[j].xsect.type == IRREGULAR ) - { - lengthFactor = Transect[Link[j].xsect.transect].lengthFactor; - roughness *= sqrt(lengthFactor); - } - - // --- lengthen conduit if lengthening option is in effect - lengthFactor = 1.0; - if ( RouteModel == DW && - LengtheningStep > 0.0 && - Link[j].xsect.type != DUMMY ) - { - lengthFactor = conduit_getLengthFactor(j, k, roughness); - } - - if ( lengthFactor != 1.0 ) - { - Conduit[k].modLength = lengthFactor * conduit_getLength(j); - slope /= lengthFactor; - roughness = roughness / sqrt(lengthFactor); - } - - // --- compute roughness factor used when computing friction - // slope term in Dynamic Wave flow routing - - // --- special case for non-Manning Force Mains - // (roughness factor for full flow is saved in xsect.sBot) - if ( RouteModel == DW && Link[j].xsect.type == FORCE_MAIN ) - { - Link[j].xsect.sBot = - forcemain_getRoughFactor(j, lengthFactor); - } - Conduit[k].roughFactor = GRAVITY * SQR(roughness/PHI); - - // --- compute full flow through cross section - if ( Link[j].xsect.type == DUMMY ) Conduit[k].beta = 0.0; - else Conduit[k].beta = PHI * sqrt(fabs(slope)) / roughness; - Link[j].qFull = Link[j].xsect.sFull * Conduit[k].beta; - Conduit[k].qMax = Link[j].xsect.sMax * Conduit[k].beta; - - // --- see if flow is supercritical most of time - // by comparing normal & critical velocities. - // (factor of 0.3 is for circular pipe 95% full) - // NOTE: this factor was used in the past for a modified version of - // Kinematic Wave routing but is now deprecated. - aa = Conduit[k].beta / sqrt(32.2) * - pow(Link[j].xsect.yFull, 0.1666667) * 0.3; - if ( aa >= 1.0 ) Conduit[k].superCritical = TRUE; - else Conduit[k].superCritical = FALSE; - - // --- set value of hasLosses flag - if ( Link[j].cLossInlet == 0.0 && - Link[j].cLossOutlet == 0.0 && - Link[j].cLossAvg == 0.0 - ) Conduit[k].hasLosses = FALSE; - else Conduit[k].hasLosses = TRUE; -} - -//============================================================================= - -void conduit_reverse(int j, int k) -// -// Input: j = link index -// k = conduit index -// Output: none -// Purpose: reverses direction of a conduit -// -{ - int i; - double z; - double cLoss; - - // --- reverse end nodes - i = Link[j].node1; - Link[j].node1 = Link[j].node2; - Link[j].node2 = i; - - // --- reverse node offsets - z = Link[j].offset1; - Link[j].offset1 = Link[j].offset2; - Link[j].offset2 = z; - - // --- reverse loss coeffs. - cLoss = Link[j].cLossInlet; - Link[j].cLossInlet = Link[j].cLossOutlet; - Link[j].cLossOutlet = cLoss; - - // --- reverse direction & slope - Conduit[k].slope = -Conduit[k].slope; - Link[j].direction *= (signed char)-1; - - // --- reverse initial flow value - Link[j].q0 = -Link[j].q0; -} - -//============================================================================= - -double conduit_getLength(int j) -// -// Input: j = link index -// Output: returns conduit's length (ft) -// Purpose: finds true length of a conduit. -// -// Note: for irregular natural channels, user inputs length of main -// channel (for FEMA purposes) but program should use length -// associated with entire flood plain. Transect.lengthFactor -// is the ratio of these two lengths. -// -{ - int k = Link[j].subIndex; - int t; - if ( Link[j].xsect.type != IRREGULAR ) return Conduit[k].length; - t = Link[j].xsect.transect; - if ( t < 0 || t >= Nobjects[TRANSECT] ) return Conduit[k].length; - return Conduit[k].length / Transect[t].lengthFactor; -} - -//============================================================================= - -double conduit_getLengthFactor(int j, int k, double roughness) -// -// Input: j = link index -// k = conduit index -// roughness = conduit Manning's n -// Output: returns factor by which a conduit should be lengthened -// Purpose: computes amount of conduit lengthing to improve numerical stability. -// -// The following form of the Courant criterion is used: -// L = t * v * (1 + Fr) / Fr -// where L = conduit length, t = time step, v = velocity, & Fr = Froude No. -// After substituting Fr = v / sqrt(gy), where y = flow depth, we get: -// L = t * ( sqrt(gy) + v ) -// -{ - double ratio; - double yFull; - double vFull; - double tStep; - - // --- evaluate flow depth and velocity at full normal flow condition - yFull = Link[j].xsect.yFull; - if ( xsect_isOpen(Link[j].xsect.type) ) - { - yFull = Link[j].xsect.aFull / xsect_getWofY(&Link[j].xsect, yFull); - } - vFull = PHI / roughness * Link[j].xsect.sFull * - sqrt(fabs(Conduit[k].slope)) / Link[j].xsect.aFull; - - // --- determine ratio of Courant length to actual length - if ( LengtheningStep == 0.0 ) tStep = RouteStep; - else tStep = MIN(RouteStep, LengtheningStep); - ratio = (sqrt(GRAVITY*yFull) + vFull) * tStep / conduit_getLength(j); - - // --- return max. of 1.0 and ratio - if ( ratio > 1.0 ) return ratio; - else return 1.0; -} - -//============================================================================= - -double conduit_getSlope(int j) -// -// Input: j = link index -// Output: returns conduit slope -// Purpose: computes conduit slope. -// -{ - double elev1, elev2, delta, slope; - double length = conduit_getLength(j); - - // --- check that elevation drop > minimum allowable drop - elev1 = Link[j].offset1 + Node[Link[j].node1].invertElev; - elev2 = Link[j].offset2 + Node[Link[j].node2].invertElev; - delta = fabs(elev1 - elev2); - if ( delta < MIN_DELTA_Z ) - { - report_writeWarningMsg(WARN04, Link[j].ID); - delta = MIN_DELTA_Z; - } - - // --- elevation drop cannot exceed conduit length - if ( delta >= length ) - { - report_writeWarningMsg(WARN08, Link[j].ID); - slope = delta / length; - } - - // --- slope = elev. drop / horizontal distance - else slope = delta / sqrt(SQR(length) - SQR(delta)); - - // -- check that slope exceeds minimum allowable slope - if ( MinSlope > 0.0 && slope < MinSlope ) - { - report_writeWarningMsg(WARN05, Link[j].ID); - slope = MinSlope; - // keep min. slope positive for SF or KW routing - if (RouteModel == SF || RouteModel == KW) return slope; - } - - // --- change sign for adverse slope - if ( elev1 < elev2 ) slope = -slope; - return slope; -} - -//============================================================================= - -void conduit_initState(int j, int k) -// -// Input: j = link index -// k = conduit index -// Output: none -// Purpose: sets initial conduit depth to normal depth of initial flow -// -{ - Link[j].newDepth = link_getYnorm(j, Link[j].q0 / Conduit[k].barrels); - Link[j].oldDepth = Link[j].newDepth; - Conduit[k].evapLossRate = 0.0; - Conduit[k].seepLossRate = 0.0; -} - -//============================================================================= - -double conduit_getInflow(int j) -// -// Input: j = link index -// Output: returns flow in link (cfs) -// Purpose: finds inflow to conduit from upstream node. -// -{ - double qIn = node_getOutflow(Link[j].node1, j); - if ( Link[j].qLimit > 0.0 ) qIn = MIN(qIn, Link[j].qLimit); - return qIn; -} - -//============================================================================= - -double conduit_getLossRate(int j, double q) -// -// Input: j = link index -// q = current link flow rate (cfs) -// Output: returns rate of evaporation & seepage losses (ft3/sec) -// Purpose: computes volumetric rate of water evaporation & seepage -// from a conduit (per barrel). -// -{ - TXsect *xsect; - double depth = 0.5 * (Link[j].oldDepth + Link[j].newDepth); - double length; - double topWidth; - double evapLossRate = 0.0, - seepLossRate = 0.0, - totalLossRate = 0.0; - - if ( depth > FUDGE ) - { - xsect = &Link[j].xsect; - length = conduit_getLength(j); - - // --- find evaporation rate for open conduits - if ( xsect_isOpen(xsect->type) && Evap.rate > 0.0 ) - { - topWidth = xsect_getWofY(xsect, depth); - evapLossRate = topWidth * length * Evap.rate; - } - - // --- compute seepage loss rate - if ( Link[j].seepRate > 0.0 ) - { - // limit depth to depth at max width - if ( depth >= xsect->ywMax ) depth = xsect->ywMax; - - // compute seepage loss rate across length of conduit - seepLossRate = Link[j].seepRate * xsect_getWofY(xsect, depth) * - length; - seepLossRate *= Adjust.hydconFactor; - } - - // --- compute total loss rate - totalLossRate = evapLossRate + seepLossRate; - - // --- total loss rate cannot exceed flow rate - q = ABS(q); - if (totalLossRate > q) - { - evapLossRate = evapLossRate * q / totalLossRate; - seepLossRate = seepLossRate * q / totalLossRate; - totalLossRate = q; - } - } - - Conduit[Link[j].subIndex].evapLossRate = evapLossRate; - Conduit[Link[j].subIndex].seepLossRate = seepLossRate; - return totalLossRate; -} - - -//============================================================================= -// P U M P M E T H O D S -//============================================================================= - -int pump_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = pump index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads pump parameters from a tokenized line of input. -// -{ - int m; - int n1, n2; - double x[4]; - char* id; - - // --- check for valid ID and end node IDs - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- parse curve name - x[0] = -1.; - if ( ntoks >= 4 ) - { - if ( !strcomp(tok[3],"*") ) - { - m = project_findObject(CURVE, tok[3]); - if ( m < 0 ) return error_setInpError(ERR_NAME, tok[3]); - x[0] = m; - } - } - - // --- parse init. status if present - x[1] = 1.0; - if ( ntoks >= 5 ) - { - m = findmatch(tok[4], OffOnWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); - x[1] = m; - } - - // --- parse startup/shutoff depths if present - x[2] = 0.0; - if ( ntoks >= 6 ) - { - if ( !getDouble(tok[5], &x[2]) || x[2] < 0.0) - return error_setInpError(ERR_NUMBER, tok[5]); - } - x[3] = 0.0; - if ( ntoks >= 7 ) - { - if ( !getDouble(tok[6], &x[3]) || x[3] < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[6]); - } - - // --- add parameters to pump object - Link[j].ID = id; - link_setParams(j, PUMP, n1, n2, k, x); - return 0; -} - -//============================================================================= - -void pump_validate(int j, int k) -// -// Input: j = link index -// k = pump index -// Output: none -// Purpose: validates a pump's properties -// -{ - int m, n1; - double x, y; - - Link[j].xsect.yFull = 0.0; - - // --- check for valid curve type - m = Pump[k].pumpCurve; - if ( m < 0 ) - { - Pump[k].type = IDEAL_PUMP; - } - else - { - if ( Curve[m].curveType < PUMP1_CURVE || - Curve[m].curveType > PUMP5_CURVE ) - report_writeErrorMsg(ERR_NO_CURVE, Link[j].ID); - - // --- store pump curve type with pump's parameters - else - { - Pump[k].type = Curve[m].curveType - PUMP1_CURVE; - if ( table_getFirstEntry(&Curve[m], &x, &y) ) - { - Link[j].qFull = y; - Pump[k].xMin = x; - Pump[k].xMax = x; - while ( table_getNextEntry(&Curve[m], &x, &y) ) - { - Link[j].qFull = MAX(y, Link[j].qFull); - Pump[k].xMax = x; - } - } - Link[j].qFull /= UCF(FLOW); - } - } - - // --- check that shutoff depth < startup depth - if ( Pump[k].yOn > 0.0 && Pump[k].yOn <= Pump[k].yOff ) - report_writeErrorMsg(ERR_PUMP_LIMITS, Link[j].ID); - - // --- assign wet well volume to inlet node of Type 1 pump - if ( Pump[k].type == TYPE1_PUMP ) - { - n1 = Link[j].node1; - if ( Node[n1].type != STORAGE ) - Node[n1].fullVolume = MAX(Node[n1].fullVolume, - Pump[k].xMax / UCF(VOLUME)); - } - -} - -//============================================================================= - -void pump_initState(int j, int k) -// -// Input: j = link index -// k = pump index -// Output: none -// Purpose: initializes pump conditions at start of a simulation -// -{ - Link[j].setting = Pump[k].initSetting; - Link[j].targetSetting = Pump[k].initSetting; -} - -//============================================================================= - -double pump_getInflow(int j) -// -// Input: j = link index -// Output: returns pump flow (cfs) -// Purpose: finds flow produced by a pump. -// -{ - int k, m; - int n1, n2; - double vol, depth, head; - double qIn, qIn1, dh = 0.001; - double s = 1.0; // speed setting - - k = Link[j].subIndex; - m = Pump[k].pumpCurve; - n1 = Link[j].node1; - n2 = Link[j].node2; - - // --- no flow if setting is closed - Link[j].flowClass = NO; - Link[j].setting = Link[j].targetSetting; - if ( Link[j].setting == 0.0 ) return 0.0; - - // --- pump flow = node inflow for IDEAL_PUMP - if ( Pump[k].type == IDEAL_PUMP ) - qIn = Node[n1].inflow + Node[n1].overflow; - - // --- pumping rate depends on pump curve type - else switch(Curve[m].curveType) - { - case PUMP1_CURVE: - vol = Node[n1].newVolume * UCF(VOLUME); - qIn = table_intervalLookup(&Curve[m], vol) / UCF(FLOW); - - // --- check if off of pump curve - if ( vol < Pump[k].xMin || vol > Pump[k].xMax ) - Link[j].flowClass = YES; - break; - - case PUMP2_CURVE: - depth = Node[n1].newDepth * UCF(LENGTH); - qIn = table_intervalLookup(&Curve[m], depth) / UCF(FLOW); - - // --- check if off of pump curve - if ( depth < Pump[k].xMin || depth > Pump[k].xMax ) - Link[j].flowClass = YES; - break; - - case PUMP3_CURVE: - case PUMP5_CURVE: - if (Curve[m].curveType == PUMP5_CURVE) s = Link[j].setting; - head = ((Node[n2].newDepth + Node[n2].invertElev) - - (Node[n1].newDepth + Node[n1].invertElev)) / s / s; - head = MAX(head, 0.0) * UCF(LENGTH); - qIn = table_lookup(&Curve[m], head) / UCF(FLOW); - - // --- compute dQ/dh (slope of pump curve) and - // reverse sign since flow decreases with increasing head - Link[j].dqdh = -table_getSlope(&Curve[m], head) * - UCF(LENGTH) / UCF(FLOW) / s; - - // --- check if off of pump curve - if (head < Pump[k].xMin || head > Pump[k].xMax) - Link[j].flowClass = YES; - break; - - case PUMP4_CURVE: - depth = Node[n1].newDepth; - qIn = table_lookup(&Curve[m], depth*UCF(LENGTH)) / UCF(FLOW); - - // --- compute dQ/dh (slope of pump curve) - qIn1 = table_lookup(&Curve[m], (depth+dh)*UCF(LENGTH)) / UCF(FLOW); - Link[j].dqdh = (qIn1 - qIn) / dh; - - // --- check if off of pump curve - depth *= UCF(LENGTH); - if ( depth < Pump[k].xMin ) Link[j].flowClass = DN_DRY; - if ( depth > Pump[k].xMax ) Link[j].flowClass = UP_DRY; - break; - - default: qIn = 0.0; - } - - // --- do not allow reverse flow through pump - if ( qIn < 0.0 ) qIn = 0.0; - return qIn * Link[j].setting; -} - - -//============================================================================= -// O R I F I C E M E T H O D S -//============================================================================= - -int orifice_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = orifice index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads orifice parameters from a tokenized line of input. -// -{ - int m; - int n1, n2; - double x[5]; - char* id; - - // --- check for valid ID and end node IDs - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- parse orifice parameters - m = findmatch(tok[3], OrificeTypeWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); - x[0] = m; // type - if ( LinkOffsets == ELEV_OFFSET && *tok[4] == '*' ) x[1] = MISSING; - else if ( ! getDouble(tok[4], &x[1]) ) // crest height - return error_setInpError(ERR_NUMBER, tok[4]); - if ( ! getDouble(tok[5], &x[2]) || x[2] < 0.0 ) // cDisch - return error_setInpError(ERR_NUMBER, tok[5]); - x[3] = 0.0; - if ( ntoks >= 7 ) - { - m = findmatch(tok[6], NoYesWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); - x[3] = m; // flap gate - } - x[4] = 0.0; - if ( ntoks >= 8 ) - { - if ( ! getDouble(tok[7], &x[4]) || x[4] < 0.0 ) // orate - return error_setInpError(ERR_NUMBER, tok[7]); - } - - // --- add parameters to orifice object - Link[j].ID = id; - link_setParams(j, ORIFICE, n1, n2, k, x); - return 0; -} - -//============================================================================= - -void orifice_validate(int j, int k) -// -// Input: j = link index -// k = orifice index -// Output: none -// Purpose: validates an orifice's properties -// -{ - int err = 0; - - // --- check for valid xsection - if ( Link[j].xsect.type != RECT_CLOSED - && Link[j].xsect.type != CIRCULAR ) err = ERR_REGULATOR_SHAPE; - if ( err > 0 ) - { - report_writeErrorMsg(err, Link[j].ID); - return; - } - - // --- check for negative offset - if ( Link[j].offset1 < 0.0 ) Link[j].offset1 = 0.0; - - // --- compute partial flow adjustment - orifice_setSetting(j, 0.0); - - // --- compute an equivalent length - Orifice[k].length = 2.0 * RouteStep * sqrt(GRAVITY * Link[j].xsect.yFull); - Orifice[k].length = MAX(200.0, Orifice[k].length); - Orifice[k].surfArea = 0.0; -} - -//============================================================================= - -void orifice_setSetting(int j, double tstep) -// -// Input: j = link index -// tstep = time step over which setting is adjusted (sec) -// Output: none -// Purpose: updates an orifice's setting as a result of a control action. -// -{ - int k = Link[j].subIndex; - double delta, step; - double h, f; - - // --- case where adjustment rate is instantaneous - if ( Orifice[k].orate == 0.0 || tstep == 0.0) - Link[j].setting = Link[j].targetSetting; - - // --- case where orifice setting depends on time step - else - { - delta = Link[j].targetSetting - Link[j].setting; - step = tstep / Orifice[k].orate; - if ( step + 0.001 >= fabs(delta) ) - Link[j].setting = Link[j].targetSetting; - else Link[j].setting += SGN(delta) * step; - } - - // --- find effective orifice discharge coeff. - h = Link[j].setting * Link[j].xsect.yFull; - f = xsect_getAofY(&Link[j].xsect, h) * sqrt(2.0 * GRAVITY); - Orifice[k].cOrif = Orifice[k].cDisch * f; - - // --- find equiv. discharge coeff. for when weir flow occurs - Orifice[k].cWeir = orifice_getWeirCoeff(j, k, h) * f; -} - -//============================================================================= - -double orifice_getWeirCoeff(int j, int k, double h) -// -// Input: j = link index -// k = orifice index -// h = height of orifice opening (ft) -// Output: returns a discharge coefficient (ft^1/2) -// Purpose: computes the discharge coefficient for an orifice -// at the critical depth where weir flow begins. -// -{ - double w, aOverL; - - // --- this is for bottom orifices - if ( Orifice[k].type == BOTTOM_ORIFICE ) - { - // --- find critical height above opening where orifice flow - // turns into weir flow. It equals (Co/Cw)*(Area/Length) - // where Co is the orifice coeff., Cw is the weir coeff/sqrt(2g), - // Area is the area of the opening, and Length = circumference - // of the opening. For a basic sharp crested weir, Cw = 0.414. - if (Link[j].xsect.type == CIRCULAR) aOverL = h / 4.0; - else - { - w = Link[j].xsect.wMax; - aOverL = (h*w) / (2.0*(h+w)); - } - h = Orifice[k].cDisch / 0.414 * aOverL; - Orifice[k].hCrit = h; - } - - // --- this is for side orifices - else - { - // --- critical height is simply height of opening - Orifice[k].hCrit = h; - - // --- head on orifice is distance to center line - h = h / 2.0; - } - - // --- return a coefficient for the critical depth - return Orifice[k].cDisch * sqrt(h); -} - -//============================================================================= - -double orifice_getInflow(int j) -// -// Input: j = link index -// Output: returns orifice flow rate (cfs) -// Purpose: finds the flow through an orifice. -// -{ - int k, n1, n2; - double head, h1, h2, y1, dir; - double f; - double hcrest = 0.0; - double hcrown = 0.0; - double hmidpt; - double q, ratio; - - // --- get indexes of end nodes and link's orifice - n1 = Link[j].node1; - n2 = Link[j].node2; - k = Link[j].subIndex; - - // --- find heads at upstream & downstream nodes - if ( RouteModel == DW ) - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - } - else - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n1].invertElev; - } - dir = (h1 >= h2) ? +1.0 : -1.0; - - // --- exchange h1 and h2 for reverse flow - y1 = Node[n1].newDepth; - if ( dir < 0.0 ) - { - head = h1; - h1 = h2; - h2 = head; - y1 = Node[n2].newDepth; - } - - // --- orifice is a bottom orifice (oriented in horizontal plane) - if ( Orifice[k].type == BOTTOM_ORIFICE ) - { - // --- compute crest elevation - hcrest = Node[n1].invertElev + Link[j].offset1; - - // --- compute head on orifice - if (h1 < hcrest) head = 0.0; - else if (h2 > hcrest) head = h1 - h2; - else head = h1 - hcrest; - - // --- find fraction of critical height for which weir flow occurs - f = head / Orifice[k].hCrit; - f = MIN(f, 1.0); - } - - // --- otherwise orifice is a side orifice (oriented in vertical plane) - else - { - // --- compute elevations of orifice crest and crown - hcrest = Node[n1].invertElev + Link[j].offset1; - hcrown = hcrest + Link[j].xsect.yFull * Link[j].setting; - hmidpt = (hcrest + hcrown) / 2.0; - - // --- compute degree of inlet submergence - if ( h1 < hcrown && hcrown > hcrest ) - f = (h1 - hcrest) / (hcrown - hcrest); - else f = 1.0; - - // --- compute head on orifice - if ( f < 1.0 ) head = h1 - hcrest; - else if ( h2 < hmidpt ) head = h1 - hmidpt; - else head = h1 - h2; - } - - // --- return if head is negligible or flap gate closed - if ( head <= FUDGE || y1 <= FUDGE || - link_setFlapGate(j, n1, n2, dir) ) - { - Link[j].newDepth = 0.0; - Link[j].flowClass = DRY; - Orifice[k].surfArea = FUDGE * Orifice[k].length; - Link[j].dqdh = 0.0; - return 0.0; - } - - // --- determine flow class - Link[j].flowClass = SUBCRITICAL; - if ( hcrest > h2 ) - { - if ( dir == 1.0 ) Link[j].flowClass = DN_CRITICAL; - else Link[j].flowClass = UP_CRITICAL; - } - - // --- compute flow depth and surface area - y1 = Link[j].xsect.yFull * Link[j].setting; - if ( Orifice[k].type == SIDE_ORIFICE ) - { - Link[j].newDepth = y1 * f; - Orifice[k].surfArea = - xsect_getWofY(&Link[j].xsect, Link[j].newDepth) * - Orifice[k].length; - } - else - { - Link[j].newDepth = y1; - Orifice[k].surfArea = xsect_getAofY(&Link[j].xsect, y1); - } - - // --- find flow through the orifice - q = dir * orifice_getFlow(j, k, head, f, Link[j].hasFlapGate); - - // --- apply Villemonte eqn. to correct for submergence - if ( f < 1.0 && h2 > hcrest ) - { - ratio = (h2 - hcrest) / (h1 - hcrest); - q *= pow( (1.0 - pow(ratio, 1.5)), 0.385); - } - return q; -} - -//============================================================================= - -double orifice_getFlow(int j, int k, double head, double f, int hasFlapGate) -// -// Input: j = link index -// k = orifice index -// head = head across orifice -// f = fraction of critical depth filled -// hasFlapGate = flap gate indicator -// Output: returns flow through an orifice -// Purpose: computes flow through an orifice as a function of head. -// -{ - double area, q; - double veloc, hLoss; - - // --- case where orifice is closed - if ( head == 0.0 || f <= 0.0 ) - { - Link[j].dqdh = 0.0; - return 0.0; - } - - // --- case where inlet depth is below critical depth; - // orifice behaves as a weir - else if ( f < 1.0 ) - { - q = Orifice[k].cWeir * pow(f, 1.5); - Link[j].dqdh = 1.5 * q / (f * Orifice[k].hCrit); - } - - // --- case where normal orifice flow applies - else - { - q = Orifice[k].cOrif * sqrt(head); - Link[j].dqdh = q / (2.0 * head); - } - - // --- apply ARMCO adjustment for headloss from flap gate - if ( hasFlapGate ) - { - // --- compute velocity for current orifice flow - area = xsect_getAofY(&Link[j].xsect, - Link[j].setting * Link[j].xsect.yFull); - veloc = q / area; - - // --- compute head loss from gate - hLoss = (4.0 / GRAVITY) * veloc * veloc * - exp(-1.15 * veloc / sqrt(head) ); - - // --- update head (for orifice flow) - // or critical depth fraction (for weir flow) - if ( f < 1.0 ) - { - f = f - hLoss/Orifice[k].hCrit; - if ( f < 0.0 ) f = 0.0; - } - else - { - head = head - hLoss; - if ( head < 0.0 ) head = 0.0; - } - - // --- make recursive call to this function, with hasFlapGate - // set to false, to find flow values at adjusted head value - q = orifice_getFlow(j, k, head, f, FALSE); - } - return q; -} - -//============================================================================= -// W E I R M E T H O D S -//============================================================================= - -int weir_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = weir index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads weir parameters from a tokenized line of input. -// -{ - int m; - int n1, n2; - double x[10]; - char* id; - - // --- check for valid ID and end node IDs - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- parse weir parameters - m = findmatch(tok[3], WeirTypeWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); - x[0] = m; // type - if ( LinkOffsets == ELEV_OFFSET && *tok[4] == '*' ) x[1] = MISSING; - else if ( ! getDouble(tok[4], &x[1]) ) // height - return error_setInpError(ERR_NUMBER, tok[4]); - if ( ! getDouble(tok[5], &x[2]) || x[2] < 0.0 ) // cDisch1 - return error_setInpError(ERR_NUMBER, tok[5]); - x[3] = 0.0; - x[4] = 0.0; - x[5] = 0.0; - x[6] = 1.0; - x[7] = 0.0; - x[8] = 0.0; - x[9] = -1.0; - if ( ntoks >= 7 && *tok[6] != '*' ) - { - m = findmatch(tok[6], NoYesWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); - x[3] = m; // flap gate - } - if ( ntoks >= 8 && *tok[7] != '*' ) - { - if ( ! getDouble(tok[7], &x[4]) || x[4] < 0.0 ) // endCon - return error_setInpError(ERR_NUMBER, tok[7]); - } - if ( ntoks >= 9 && *tok[8] != '*' ) - { - if ( ! getDouble(tok[8], &x[5]) || x[5] < 0.0 ) // cDisch2 - return error_setInpError(ERR_NUMBER, tok[8]); - } - - if ( ntoks >= 10 && *tok[9] != '*' ) - { - m = findmatch(tok[9], NoYesWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[9]); - x[6] = m; // canSurcharge - } - - if ( (m = (int)x[0]) == ROADWAY_WEIR ) - { - if ( ntoks >= 11 ) // road width - { - if ( ! getDouble(tok[10], &x[7]) || x[7] < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[10]); - } - if ( ntoks >= 12 ) // road surface - { - if ( strcomp(tok[11], "PAVED") ) x[8] = 1.0; - else if ( strcomp(tok[11], "GRAVEL") ) x[8] = 2.0; - } - } - - if (ntoks >= 13 && *tok[12] != '*') - { - m = project_findObject(CURVE, tok[12]); // coeff. curve - if (m < 0) return error_setInpError(ERR_NAME, tok[12]); - x[9] = m; - } - - // --- add parameters to weir object - Link[j].ID = id; - link_setParams(j, WEIR, n1, n2, k, x); - return 0; -} - -//============================================================================= - -void weir_validate(int j, int k) -// -// Input: j = link index -// k = weir index -// Output: none -// Purpose: validates a weir's properties -// -{ - int err = 0; - double q, q1, q2, head; - - // --- check for valid cross section - switch ( Weir[k].type) - { - case TRANSVERSE_WEIR: - case SIDEFLOW_WEIR: - case ROADWAY_WEIR: - if ( Link[j].xsect.type != RECT_OPEN ) err = ERR_REGULATOR_SHAPE; - Weir[k].slope = 0.0; - break; - - case VNOTCH_WEIR: - if ( Link[j].xsect.type != TRIANGULAR ) err = ERR_REGULATOR_SHAPE; - else - { - Weir[k].slope = Link[j].xsect.sBot; - } - break; - - case TRAPEZOIDAL_WEIR: - if ( Link[j].xsect.type != TRAPEZOIDAL ) err = ERR_REGULATOR_SHAPE; - else - { - Weir[k].slope = Link[j].xsect.sBot; - } - break; - } - if ( err > 0 ) - { - report_writeErrorMsg(err, Link[j].ID); - return; - } - - // --- check for negative offset - if ( Link[j].offset1 < 0.0 ) Link[j].offset1 = 0.0; - - // --- compute an equivalent length - Weir[k].length = 2.0 * RouteStep * sqrt(GRAVITY * Link[j].xsect.yFull); - Weir[k].length = MAX(200.0, Weir[k].length); - Weir[k].surfArea = 0.0; - - // --- find flow through weir when water level equals weir height - head = Link[j].xsect.yFull; - weir_getFlow(j, k, head, 1.0, FALSE, &q1, &q2); - q = q1 + q2; - - // --- compute equivalent orifice coeff. (for CFS flow units) - head = head / 2.0; // head seen by equivalent orifice - Weir[k].cSurcharge = q / sqrt(head); -} - -//============================================================================= - -void weir_setSetting(int j) -// -// Input: j = link index -// Output: none -// Purpose: updates a weir's setting as a result of a control action. -// -{ - int k = Link[j].subIndex; - double h, q, q1, q2; - - // --- adjust weir setting - Link[j].setting = Link[j].targetSetting; - if ( !Weir[k].canSurcharge ) return; - if ( Weir[k].type == ROADWAY_WEIR ) return; - - // --- find orifice coeff. for surcharged flow - if ( Link[j].setting == 0.0 ) Weir[k].cSurcharge = 0.0; - else - { - // --- find flow through weir when water level equals weir height - h = Link[j].setting * Link[j].xsect.yFull; - weir_getFlow(j, k, h, 1.0, FALSE, &q1, &q2); - q = q1 + q2; - - // --- compute equivalent orifice coeff. (for CFS flow units) - h = h / 2.0; // head seen by equivalent orifice - Weir[k].cSurcharge = q / sqrt(h); - } -} - -//============================================================================= - -double weir_getInflow(int j) -// -// Input: j = link index -// Output: returns weir flow rate (cfs) -// Purpose: finds the flow over a weir. -// -{ - int n1; // index of upstream node - int n2; // index of downstream node - int k; // index of weir - double q1; // flow through central part of weir (cfs) - double q2; // flow through end sections of weir (cfs) - double head; // head on weir (ft) - double h1; // upstrm nodal head (ft) - double h2; // downstrm nodal head (ft) - double hcrest; // head at weir crest (ft) - double hcrown; // head at weir crown (ft) - double y; // water depth in weir (ft) - double dir; // direction multiplier - double ratio; - double weirPower[] = {1.5, // transverse weir - 5./3., // side flow weir - 2.5, // v-notch weir - 1.5}; // trapezoidal weir - - n1 = Link[j].node1; - n2 = Link[j].node2; - k = Link[j].subIndex; - if ( RouteModel == DW ) - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - } - else - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n1].invertElev; - } - dir = (h1 > h2) ? +1.0 : -1.0; - - // --- exchange h1 and h2 for reverse flow - if ( dir < 0.0 ) - { - head = h1; - h1 = h2; - h2 = head; - } - - // --- find head of weir's crest and crown - hcrest = Node[n1].invertElev + Link[j].offset1; - hcrown = hcrest + Link[j].xsect.yFull; - - // --- treat a roadway weir as a special case - if ( Weir[k].type == ROADWAY_WEIR ) - return roadway_getInflow(j, dir, hcrest, h1, h2); - - // --- adjust crest ht. for partially open weir - hcrest += (1.0 - Link[j].setting) * Link[j].xsect.yFull; - - // --- compute head relative to weir crest - head = h1 - hcrest; - - // --- return if head is negligible or flap gate closed - Link[j].dqdh = 0.0; - if ( head <= FUDGE || hcrest >= hcrown || - link_setFlapGate(j, n1, n2, dir) ) - { - Link[j].newDepth = 0.0; - Link[j].flowClass = DRY; - return 0.0; - } - - // --- determine flow class - Link[j].flowClass = SUBCRITICAL; - if ( hcrest > h2 ) - { - if ( dir == 1.0 ) Link[j].flowClass = DN_CRITICAL; - else Link[j].flowClass = UP_CRITICAL; - } - - // --- compute new equivalent surface area - y = Link[j].xsect.yFull - (hcrown - MIN(h1, hcrown)); - Weir[k].surfArea = xsect_getWofY(&Link[j].xsect, y) * Weir[k].length; - - // --- head is above crown - if ( h1 >= hcrown ) - { - // --- use equivalent orifice if weir can surcharge - if ( Weir[k].canSurcharge ) - { - y = (hcrest + hcrown) / 2.0; - if ( h2 < y ) head = h1 - y; - else head = h1 - h2; - y = hcrown - hcrest; - q1 = weir_getOrificeFlow(j, head, y, Weir[k].cSurcharge); - Link[j].newDepth = y; - return dir * q1; - } - - // --- otherwise limit head to height of weir opening - else head = hcrown - hcrest; - } - - // --- use weir eqn. to find flows through central (q1) - // and end sections (q2) of weir - weir_getFlow(j, k, head, dir, Link[j].hasFlapGate, &q1, &q2); - - // --- apply Villemonte eqn. to correct for submergence - if ( h2 > hcrest ) - { - ratio = (h2 - hcrest) / (h1 - hcrest); - q1 *= pow( (1.0 - pow(ratio, weirPower[Weir[k].type])), 0.385); - if ( q2 > 0.0 ) - q2 *= pow( (1.0 - pow(ratio, weirPower[VNOTCH_WEIR])), 0.385); - } - - // --- return total flow through weir - Link[j].newDepth = MIN((h1 - hcrest), Link[j].xsect.yFull); - return dir * (q1 + q2); -} - -//============================================================================= - -void weir_getFlow(int j, int k, double head, double dir, int hasFlapGate, - double* q1, double* q2) -// -// Input: j = link index -// k = weir index -// head = head across weir (ft) -// dir = flow direction indicator -// hasFlapGate = flap gate indicator -// Output: q1 = flow through central portion of weir (cfs) -// q2 = flow through end sections of weir (cfs) -// Purpose: computes flow over weir given head. -// -{ - double length; - double h; - double y; - double hLoss; - double area; - double veloc; - int wType; - int cdCurve = Weir[k].cdCurve; - double cDisch1 = Weir[k].cDisch1; - - // --- q1 = flow through central portion of weir, - // q2 = flow through end sections of trapezoidal weir - *q1 = 0.0; - *q2 = 0.0; - Link[j].dqdh = 0.0; - if ( head <= 0.0 ) return; - - // --- convert weir length & head to original units - length = Link[j].xsect.wMax * UCF(LENGTH); - h = head * UCF(LENGTH); - - // --- lookup tabulated discharge coeff. - if ( cdCurve >= 0 ) cDisch1 = table_lookup(&Curve[cdCurve], h); - - // --- use appropriate formula for weir flow - wType = Weir[k].type; - if ( wType == VNOTCH_WEIR && - Link[j].setting < 1.0 ) wType = TRAPEZOIDAL_WEIR; - switch (wType) - { - case TRANSVERSE_WEIR: - - // --- reduce length when end contractions present - length -= 0.1 * Weir[k].endCon * h; - length = MAX(length, 0.0); - *q1 = cDisch1 * length * pow(h, 1.5); - break; - - case SIDEFLOW_WEIR: - - // --- reduce length when end contractions present - length -= 0.1 * Weir[k].endCon * h; - length = MAX(length, 0.0); - - // --- weir behaves as a transverse weir under reverse flow - if ( dir < 0.0 ) - *q1 = cDisch1 * length * pow(h, 1.5); - else - - // Corrected formula (see Metcalf & Eddy, Inc., - // Wastewater Engineering, McGraw-Hill, 1972 p. 164). - *q1 = cDisch1 * pow(length, 0.83) * pow(h, 1.67); - - break; - - case VNOTCH_WEIR: - *q1 = cDisch1 * Weir[k].slope * pow(h, 2.5); - break; - - case TRAPEZOIDAL_WEIR: - y = (1.0 - Link[j].setting) * Link[j].xsect.yFull; - length = xsect_getWofY(&Link[j].xsect, y) * UCF(LENGTH); - *q1 = cDisch1 * length * pow(h, 1.5); - *q2 = Weir[k].cDisch2 * Weir[k].slope * pow(h, 2.5); - } - - // --- convert CMS flows to CFS - if ( UnitSystem == SI ) - { - *q1 /= M3perFT3; - *q2 /= M3perFT3; - } - - // --- apply ARMCO adjustment for headloss from flap gate - if ( hasFlapGate ) - { - // --- compute flow area & velocity for current weir flow - area = weir_getOpenArea(j, head); - if ( area > TINY ) - { - veloc = (*q1 + *q2) / area; - - // --- compute headloss and subtract from original head - hLoss = (4.0 / GRAVITY) * veloc * veloc * - exp(-1.15 * veloc / sqrt(head) ); - head = head - hLoss; - if ( head < 0.0 ) head = 0.0; - - // --- make recursive call to this function, with hasFlapGate - // set to false, to find flow values at adjusted head value - weir_getFlow(j, k, head, dir, FALSE, q1, q2); - } - } - Link[j].dqdh = weir_getdqdh(k, dir, head, *q1, *q2); -} - -//============================================================================= - -double weir_getOrificeFlow(int j, double head, double y, double cOrif) -// -// Input: j = link index -// head = head across weir (ft) -// y = height of upstream water level above weir crest (ft) -// cOrif = orifice flow coefficient -// Output: returns flow through weir -// Purpose: finds flow through a surcharged weir using the orifice equation. -// -{ - double a, q, v, hloss; - - // --- evaluate the orifice flow equation - q = cOrif * sqrt(head); - - // --- apply Armco adjustment if weir has a flap gate - if ( Link[j].hasFlapGate ) - { - a = weir_getOpenArea(j, y); - if ( a > 0.0 ) - { - v = q / a; - hloss = (4.0 / GRAVITY) * v * v * exp(-1.15 * v / sqrt(y) ); - head -= hloss; - head = MAX(head, 0.0); - q = cOrif * sqrt(head); - } - } - if ( head > 0.0 ) Link[j].dqdh = q / (2.0 * head); - else Link[j].dqdh = 0.0; - return q; -} - -//============================================================================= - -double weir_getOpenArea(int j, double y) -// -// Input: j = link index -// y = depth of water above weir crest (ft) -// Output: returns area between weir crest and y (ft2) -// Purpose: finds flow area through a weir. -// -{ - double z, zy; - - // --- find offset of weir crest due to control setting - z = (1.0 - Link[j].setting) * Link[j].xsect.yFull; - - // --- ht. of crest + ht of water above crest - zy = z + y; - zy = MIN(zy, Link[j].xsect.yFull); - - // --- return difference between area of offset + water depth - // and area of just the offset - return xsect_getAofY(&Link[j].xsect, zy) - - xsect_getAofY(&Link[j].xsect, z); -} - -//============================================================================= - -double weir_getdqdh(int k, double dir, double h, double q1, double q2) -{ - double q1h; - double q2h; - - if ( fabs(h) < FUDGE ) return 0.0; - q1h = fabs(q1/h); - q2h = fabs(q2/h); - - switch (Weir[k].type) - { - case TRANSVERSE_WEIR: return 1.5 * q1h; - - case SIDEFLOW_WEIR: - // --- weir behaves as a transverse weir under reverse flow - if ( dir < 0.0 ) return 1.5 * q1h; - else return 1.67 * q1h; - - case VNOTCH_WEIR: - if ( q2h == 0.0 ) return 2.5 * q1h; // Fully open - else return 1.5 * q1h + 2.5 * q2h; // Partly open - - case TRAPEZOIDAL_WEIR: return 1.5 * q1h + 2.5 * q2h; - } - return 0.0; -} - - -//============================================================================= -// O U T L E T D E V I C E M E T H O D S -//============================================================================= - -int outlet_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = outlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads outlet parameters from a tokenized line of input. -// -{ - int i, m, n; - int n1, n2; - double x[6]; - char* id; - char* s; - - // --- check for valid ID and end node IDs - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- get height above invert - if ( LinkOffsets == ELEV_OFFSET && *tok[3] == '*' ) x[0] = MISSING; - else - { - if ( ! getDouble(tok[3], &x[0]) ) - return error_setInpError(ERR_NUMBER, tok[3]); - if ( LinkOffsets == DEPTH_OFFSET && x[0] < 0.0 ) x[0] = 0.0; - } - - // --- see if outlet flow relation is tabular or functional - m = findmatch(tok[4], RelationWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); - x[1] = 0.0; - x[2] = 0.0; - x[3] = -1.0; - x[4] = 0.0; - - // --- see if rating curve is head or depth based - x[5] = NODE_DEPTH; //default is depth-based - s = strtok(tok[4], "/"); //parse token for - s = strtok(NULL, "/"); // qualifier term - if ( strcomp(s, w_HEAD) ) x[5] = NODE_HEAD; //check if its "HEAD" - - // --- get params. for functional outlet device - if ( m == FUNCTIONAL ) - { - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - if ( ! getDouble(tok[5], &x[1]) ) - return error_setInpError(ERR_NUMBER, tok[5]); - if ( ! getDouble(tok[6], &x[2]) ) - return error_setInpError(ERR_NUMBER, tok[6]); - n = 7; - } - - // --- get name of outlet rating curve - else - { - i = project_findObject(CURVE, tok[5]); - if ( i < 0 ) return error_setInpError(ERR_NAME, tok[5]); - x[3] = i; - n = 6; - } - - // --- check if flap gate specified - if ( ntoks > n) - { - i = findmatch(tok[n], NoYesWords); - if ( i < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); - x[4] = i; - } - - // --- add parameters to outlet object - Link[j].ID = id; - link_setParams(j, OUTLET, n1, n2, k, x); - return 0; -} - -//============================================================================= - -double outlet_getInflow(int j) -// -// Input: j = link index -// Output: outlet flow rate (cfs) -// Purpose: finds the flow through an outlet. -// -{ - int k, n1, n2; - double head, hcrest, h1, h2, y1, dir; - - // --- get indexes of end nodes - n1 = Link[j].node1; - n2 = Link[j].node2; - k = Link[j].subIndex; - - // --- find heads at upstream & downstream nodes - if ( RouteModel == DW ) - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - } - else - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n1].invertElev; - } - dir = (h1 >= h2) ? +1.0 : -1.0; - - // --- exchange h1 and h2 for reverse flow - y1 = Node[n1].newDepth; - if ( dir < 0.0 ) - { - y1 = h1; - h1 = h2; - h2 = y1; - y1 = Node[n2].newDepth; - } - - // --- for a NODE_DEPTH rating curve the effective head across the - // outlet is the depth above the crest elev. while for a NODE_HEAD - // curve it is the difference between upstream & downstream heads - hcrest = Node[n1].invertElev + Link[j].offset1; - if ( Outlet[k].curveType == NODE_HEAD && RouteModel == DW ) - head = h1 - MAX(h2, hcrest); - else head = h1 - hcrest; - - // --- no flow if either no effective head difference, - // no upstream water available, or closed flap gate - if ( head <= FUDGE || y1 <= FUDGE || - link_setFlapGate(j, n1, n2, dir) ) - { - Link[j].newDepth = 0.0; - Link[j].flowClass = DRY; - return 0.0; - } - - // --- otherwise use rating curve to compute flow - Link[j].newDepth = head; - Link[j].flowClass = SUBCRITICAL; - return dir * Link[j].setting * outlet_getFlow(k, head); -} - -//============================================================================= - -double outlet_getFlow(int k, double head) -// -// Input: k = outlet index -// head = head across outlet (ft) -// Output: returns outlet flow rate (cfs) -// Purpose: computes flow rate through an outlet given head. -// -{ - int m; - double h; - - // --- convert head to original units - h = head * UCF(LENGTH); - - // --- look-up flow in rating curve table if provided - m = Outlet[k].qCurve; - if ( m >= 0 ) return table_lookup(&Curve[m], h) / UCF(FLOW); - - // --- otherwise use function to find flow - else return Outlet[k].qCoeff * pow(h, Outlet[k].qExpon) / UCF(FLOW); -} diff --git a/src/macros.h b/src/macros.h deleted file mode 100644 index c15749e4b..000000000 --- a/src/macros.h +++ /dev/null @@ -1,45 +0,0 @@ -//----------------------------------------------------------------------------- -// macros.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -//----------------------------------------------------------------------------- - -#ifndef MACROS_H -#define MACROS_H - - -//-------------------------------------------------- -// Macro to test for successful allocation of memory -//-------------------------------------------------- -#define MEMCHECK(x) (((x) == NULL) ? 101 : 0 ) - -//-------------------------------------------------- -// Macro to free a non-null pointer -//-------------------------------------------------- -#define FREE(x) { if (x) { free(x); x = NULL; } } - -//--------------------------------------------------- -// Conversion macros to be used in place of functions -//--------------------------------------------------- -#define ABS(x) (((x)<0) ? -(x) : (x)) /* absolute value of x */ -#define MIN(x,y) (((x)<=(y)) ? (x) : (y)) /* minimum of x and y */ -#define MAX(x,y) (((x)>=(y)) ? (x) : (y)) /* maximum of x and y */ -#define MOD(x,y) ((x)%(y)) /* x modulus y */ -#define LOG10(x) ((x) > 0.0 ? log10((x)) : (x)) /* safe log10 of x */ -#define SQR(x) ((x)*(x)) /* x-squared */ -#define SGN(x) (((x)<0) ? (-1) : (1)) /* sign of x */ -#define SIGN(x,y) ((y) >= 0.0 ? fabs(x) : -fabs(x)) -#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) - /* uppercase char of x */ -#define ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0])) /* length of array x */ - -//------------------------------------------------- -// Macro to evaluate function x with error checking -//------------------------------------------------- -#define CALL(x) (ErrorCode = ((ErrorCode>0) ? (ErrorCode) : (x))) - - -#endif //MACROS_H diff --git a/src/main.c b/src/main.c deleted file mode 100644 index cc99865b6..000000000 --- a/src/main.c +++ /dev/null @@ -1,104 +0,0 @@ -//----------------------------------------------------------------------------- -// main.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 03/24/2021 -// Author: L. Rossman - -// Main stub for the command line version of EPA SWMM 5.2 -// to be run with swmm5.dll. - -#include -#include -#include -#include "swmm5.h" - -int main(int argc, char *argv[]) -// -// Input: argc = number of command line arguments -// argv = array of command line arguments -// Output: returns error status -// Purpose: runs the command line version of EPA SWMM 5.2. -// -// Command line is: runswmm f1 f2 f3 -// where f1 = name of input file, f2 = name of report file, and -// f3 = name of binary output file if saved (or blank if not saved). -// -{ - char *inputFile; - char *reportFile; - char *binaryFile; - char *arg1; - char blank[] = ""; - int version, vMajor, vMinor, vRelease; - char errMsg[128]; - int msgLen = 127; - time_t start; - double runTime; - - version = swmm_getVersion(); - vMajor = version / 10000; - vMinor = (version - 10000 * vMajor) / 1000; - vRelease = (version - 10000 * vMajor - 1000 * vMinor); - start = time(0); - - // --- check for proper number of command line arguments - if (argc == 1) - { - printf("\nNot Enough Arguments (See Help --help)\n\n"); - } - else if (argc == 2) - { - // --- extract first argument - arg1 = argv[1]; - - if (strcmp(arg1, "--help") == 0 || strcmp(arg1, "-h") == 0) - { - // Help - printf("\n\nSTORMWATER MANAGEMENT MODEL (SWMM) HELP\n\n"); - printf("COMMANDS:\n"); - printf("\t--help (-h) SWMM Help\n"); - printf("\t--version (-v) Build Version\n"); - printf("\nRUNNING A SIMULATION:\n"); - printf("\t runswmm + +This repository contains the source code to the model. A user interface for the model is here: [https://github.com/USEPA/SWMM-EPANET_User_Interface](USEPA/SWMM-EPANET_User_Interface) + ## Build Status [![Build and Test](https://github.com/USEPA/Stormwater-Management-Model/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/USEPA/Stormwater-Management-Model/actions/workflows/build-and-test.yml) From 67ed571da7ff53a4e4ce21c85d78f0fcb0172acc Mon Sep 17 00:00:00 2001 From: Andy Chase Date: Thu, 17 Feb 2022 11:49:54 -0800 Subject: [PATCH 222/266] Fix link in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6269f0f52..cb573b728 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ORD Stormwater Management Model (aka "SWMM") -This repository contains the source code to the model. A user interface for the model is here: [https://github.com/USEPA/SWMM-EPANET_User_Interface](USEPA/SWMM-EPANET_User_Interface) +This repository contains the source code to the model. A user interface for the model is here: [USEPA/SWMM-EPANET_User_Interface](https://github.com/USEPA/SWMM-EPANET_User_Interface) ## Build Status From 928b68996e87c16b869d33d6464a698ea0256457 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 28 Feb 2022 10:21:45 -0500 Subject: [PATCH 223/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index bb38f3628..158a15c77 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,9 +2,9 @@ name: Build and Test on: push: - branches: [ release ] + branches: [ develop release ] pull_request: - branches: [ release ] + branches: [ master develop release ] env: OMP_NUM_THREADS: 1 From 107771439936b8760b75ebcc6030ce89c705f5da Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 28 Feb 2022 10:42:46 -0500 Subject: [PATCH 224/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 158a15c77..ed9ccb62e 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,7 +2,7 @@ name: Build and Test on: push: - branches: [ develop release ] + branches: [ master develop release ] pull_request: branches: [ master develop release ] From fa0020bb6e5caf4ddf9b42ae759297628f3ce685 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 28 Feb 2022 10:44:41 -0500 Subject: [PATCH 225/266] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b84872ef1..a3c56b462 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,4 @@ SWMM is a dynamic hydrology-hydraulic water quality simulation model. It is used Find Out More ------------- The source code distributed here is identical to the code found at the official [SWMM Website](http://www2.epa.gov/water-research/storm-water-management-model-swmm). + From 6425547933ef6f9dba18b0c063ecbb517b37a3e8 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 28 Feb 2022 10:47:21 -0500 Subject: [PATCH 226/266] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index a3c56b462..b84872ef1 100644 --- a/README.md +++ b/README.md @@ -12,4 +12,3 @@ SWMM is a dynamic hydrology-hydraulic water quality simulation model. It is used Find Out More ------------- The source code distributed here is identical to the code found at the official [SWMM Website](http://www2.epa.gov/water-research/storm-water-management-model-swmm). - From 2dcf163c898a6529f2525c7aa41b6eb3f2d15c31 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 28 Feb 2022 10:50:05 -0500 Subject: [PATCH 227/266] Trigger build --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b84872ef1..f04fa81f3 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,4 @@ SWMM is a dynamic hydrology-hydraulic water quality simulation model. It is used Find Out More ------------- -The source code distributed here is identical to the code found at the official [SWMM Website](http://www2.epa.gov/water-research/storm-water-management-model-swmm). +The source code distributed here is identical to the code found at the official [SWMM Website](http://www2.epa.gov/water-research/storm-water-management-model-swmm). From a868ebc954530dbc4dbddcf2b9ecfb07a09d12c2 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 28 Feb 2022 11:02:29 -0500 Subject: [PATCH 228/266] Fix typo --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ed9ccb62e..6e9d77013 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,9 +2,9 @@ name: Build and Test on: push: - branches: [ master develop release ] + branches: [ master, develop, release ] pull_request: - branches: [ master develop release ] + branches: [ master, develop, release ] env: OMP_NUM_THREADS: 1 From fb1ccc99c427fb4d52f752e7bee3c67b781873d8 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Mon, 28 Feb 2022 11:41:15 -0500 Subject: [PATCH 229/266] Update build-and-test.yml Update release tag for benchmark --- .github/workflows/build-and-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 6e9d77013..1026e4035 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -97,7 +97,8 @@ jobs: - name: Before test env: NRTESTS_URL: https://github.com/SWMM-Project/swmm-nrtestsuite - run: before-nrtest.cmd + RELEASE_TAG: v2.0.0 + run: before-nrtest.cmd %RELEASE_TAG% - name: Reg test run: run-nrtests.cmd %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% From fc69629ba15774e1429f6e1b45ab09b27a13ea9d Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 1 Mar 2022 12:27:45 -0500 Subject: [PATCH 230/266] Removing duplicate swmm5.def --- src/solver/swmm5.def | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 src/solver/swmm5.def diff --git a/src/solver/swmm5.def b/src/solver/swmm5.def deleted file mode 100644 index 98391ee06..000000000 --- a/src/solver/swmm5.def +++ /dev/null @@ -1,14 +0,0 @@ -LIBRARY SWMM5.DLL - -EXPORTS - swmm_close = _swmm_close@0 - swmm_end = _swmm_end@0 - swmm_getError = _swmm_getError@8 - swmm_getMassBalErr = _swmm_getMassBalErr@12 - swmm_getVersion = _swmm_getVersion@0 - swmm_getWarnings = _swmm_getWarnings@0 - swmm_open = _swmm_open@12 - swmm_report = _swmm_report@0 - swmm_run = _swmm_run@12 - swmm_start = _swmm_start@4 - swmm_step = _swmm_step@4 From 6ed6c25872dc5295f7831055030773eebd68a950 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 1 Mar 2022 12:40:58 -0500 Subject: [PATCH 231/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 3301becdd..07b4d1981 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,9 +2,9 @@ name: Build and Test on: push: - branches: [ release ] + branches: [ master, develop, release ] pull_request: - branches: [ release ] + branches: [ master, develop, release ] env: OMP_NUM_THREADS: 1 From f5b791d83477f65d5c3deeaa755c78963bcf37b3 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 1 Mar 2022 12:51:02 -0500 Subject: [PATCH 232/266] Fix bug --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 3301becdd..dfe4ff864 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -102,8 +102,8 @@ jobs: - name: Run reg test env: - BUILD_ID: ${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER} - run: run-nrtests.cmd ${{ env.BUILD_ID }} + SUT_BUILD_ID: ${{ GITHUB_RUN_ID }}_${{ GITHUB_RUN_NUMBER }} + run: run-nrtests.cmd ${{ env.SUT_BUILD_ID }} - name: Upload artifacts if: ${{ always() }} From 664a72e2632b3e929948613e8d3e7d7170cdd429 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 1 Mar 2022 12:54:47 -0500 Subject: [PATCH 233/266] Fix bug --- .github/workflows/build-and-test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 4c9fddc2c..8bc38490f 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,9 +2,9 @@ name: Build and Test on: push: - branches: [ master, develop, release ] + branches: [ release ] pull_request: - branches: [ master, develop, release ] + branches: [ release ] env: OMP_NUM_THREADS: 1 @@ -102,7 +102,7 @@ jobs: - name: Run reg test env: - SUT_BUILD_ID: ${{ GITHUB_RUN_ID }}_${{ GITHUB_RUN_NUMBER }} + SUT_BUILD_ID: %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% run: run-nrtests.cmd ${{ env.SUT_BUILD_ID }} - name: Upload artifacts From d5b741686b56f2f7fa7b4faaca8f1788118d220d Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 1 Mar 2022 12:57:17 -0500 Subject: [PATCH 234/266] Fix bug --- .github/workflows/build-and-test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 8bc38490f..ea81aec98 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -101,9 +101,7 @@ jobs: run: before-nrtest.cmd - name: Run reg test - env: - SUT_BUILD_ID: %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - run: run-nrtests.cmd ${{ env.SUT_BUILD_ID }} + run: run-nrtests.cmd %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Upload artifacts if: ${{ always() }} From 137b8dfb89ed390372fe3c23cfa5c74c6e414ea6 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 1 Mar 2022 12:59:54 -0500 Subject: [PATCH 235/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ea81aec98..5deff9d51 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -101,7 +101,7 @@ jobs: run: before-nrtest.cmd - name: Run reg test - run: run-nrtests.cmd %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% + run: run-nrtests.cmd %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Upload artifacts if: ${{ always() }} From ee0b3dc67a7ac6a79bc707177bde086a751069ab Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 1 Mar 2022 13:02:39 -0500 Subject: [PATCH 236/266] Fix bug --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ea81aec98..b5fc42174 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,9 +2,9 @@ name: Build and Test on: push: - branches: [ release ] + branches: [ master, develop, release ] pull_request: - branches: [ release ] + branches: [ master, develop, release ] env: OMP_NUM_THREADS: 1 From e26cb7b9896cebbe0da4b9d0efe81a29796e21e0 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 17 May 2022 11:16:45 -0400 Subject: [PATCH 237/266] Update README.md --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 95bedc872..3f78824c5 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,15 @@ Stormwater-Management-Model ORD Stormwater Management Model (aka "SWMM") - - -This repository contains the source code to the model. A user interface for the model is here: [USEPA/SWMM-EPANET_User_Interface](https://github.com/USEPA/SWMM-EPANET_User_Interface) - +This repository contains the source code to the solver. ## Build Status [![Build and Test](https://github.com/USEPA/Stormwater-Management-Model/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/USEPA/Stormwater-Management-Model/actions/workflows/build-and-test.yml) +## Disclaimer +The United States Environmental Protection Agency (EPA) GitHub project code is provided on an "as is" basis and the user assumes responsibility for its use. EPA has relinquished control of the information and no longer has responsibility to protect the integrity, confidentiality, or availability of the information. Any reference to specific commercial products, processes, or services by service mark, trademark, manufacturer, or otherwise, does not constitute or imply their endorsement, recommendation or favoring by EPA. The EPA seal and logo shall not be used in any manner to imply endorsement of any commercial product or activity by EPA or the United States Government. + + ## Introduction This is the official SWMM source code repository maintained by US EPA Office of Research and Development, Center For Environmental Solutions & Emergency Response, Water Infrastructure Division located in Cincinnati, Ohio. From 0c481f350431e0c83d2605aeea1480a765f98ad0 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 17 May 2022 11:19:09 -0400 Subject: [PATCH 238/266] Update README.md --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3f78824c5..1c35a7366 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ -Stormwater-Management-Model -=========================== +ORD Stormwater-Management-Model Solver +================================== -ORD Stormwater Management Model (aka "SWMM") +Stormwater Management Model (aka "SWMM") solver only -This repository contains the source code to the solver. ## Build Status [![Build and Test](https://github.com/USEPA/Stormwater-Management-Model/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/USEPA/Stormwater-Management-Model/actions/workflows/build-and-test.yml) From 4968cc82ada1de1644018ef4f9bd6d150d0dd48b Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Wed, 1 Jun 2022 15:04:20 -0400 Subject: [PATCH 239/266] Release 5.2.1 --- src/solver/consts.h | 4 ++-- src/solver/controls.c | 13 ++++++++----- src/solver/dwflow.c | 17 +++++++++-------- src/solver/enums.h | 7 +++++-- src/solver/flowrout.c | 6 ++++-- src/solver/inlet.c | 12 +++++++----- src/solver/input.c | 12 +++++++----- src/solver/keywords.c | 6 ++++-- src/solver/link.c | 14 +++++++++----- src/solver/node.c | 15 ++++++++++----- src/solver/output.c | 8 +++++--- src/solver/qualrout.c | 18 +++++++++++++----- src/solver/statsrpt.c | 10 ++++++---- src/solver/text.h | 4 ++-- 14 files changed, 91 insertions(+), 55 deletions(-) diff --git a/src/solver/consts.h b/src/solver/consts.h index c42235c99..3cb4cd990 100644 --- a/src/solver/consts.h +++ b/src/solver/consts.h @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) +// Date: 06/01/22 (Build 5.2.1) // Author: L. Rossman // // Various Constants @@ -16,7 +16,7 @@ // General Constants //------------------ -#define VERSION 52000 +#define VERSION 52001 #define MAGICNUMBER 516114522 #define EOFMARK 0x1A // Use 0x04 for UNIX systems #define MAXTITLE 3 // Max. # title lines diff --git a/src/solver/controls.c b/src/solver/controls.c index d2af28131..b46054006 100644 --- a/src/solver/controls.c +++ b/src/solver/controls.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) +// Date: 06/01/22 (Build 5.2.1) // Author: L. Rossman // // Rule-based controls functions. @@ -48,6 +48,9 @@ // - Additional attributes added to condition clauses. // - Support added for named variables in condition clauses. // - Support added for math expressions in condition clauses. +// Build 5.2.1: +// - A refactoring bug from 5.2.0 causing duplicate actions to be added +// to the list of control actions to take was fixed. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -1188,14 +1191,14 @@ void updateActionList(struct TAction* a) listItem = listItem->next; } - // --- action not listed so add it to ActionList - listItem = (struct TActionList *) malloc(sizeof(struct TActionList)); - if (listItem) + // --- action not listed so add it to ActionList //5.2.1 + if ( !listItem ) { + listItem = (struct TActionList *) malloc(sizeof(struct TActionList)); listItem->next = ActionList; ActionList = listItem; - listItem->action = a; } + listItem->action = a; } //============================================================================= diff --git a/src/solver/dwflow.c b/src/solver/dwflow.c index 14338b2de..9d3c2595e 100644 --- a/src/solver/dwflow.c +++ b/src/solver/dwflow.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) +// Date: 04/02/22 (Build 5.2.1) // Author: L. Rossman // M. Tryby (EPA) // R. Dickinson (CDM) @@ -24,6 +24,8 @@ // - Conduit evap. and seepage loss initialized to 0 in dwflow_findConduitFlow. // - Most current flow (qLast) used instead of previous time period flow // (qOld) in call to link_getLossRate. +// Build 5.2.1: +// - Implements the new option to skip checking for normal flow limitations. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -248,11 +250,10 @@ void dwflow_findConduitFlow(int j, int steps, double omega, double dt) q = culvert_getInflow(j, q, h1); // --- check for normal flow limitation based on surface slope & Fr - else - if ( y1 < Link[j].xsect.yFull && - ( Link[j].flowClass == SUBCRITICAL || - Link[j].flowClass == SUPCRITICAL ) - ) q = checkNormalFlow(j, q, y1, y2, a1, r1); + else if (NormalFlowLtd != NEITHER && y1 < Link[j].xsect.yFull && + ( Link[j].flowClass == SUBCRITICAL || + Link[j].flowClass == SUPCRITICAL )) + q = checkNormalFlow(j, q, y1, y2, a1, r1); } // --- apply under-relaxation weighting between new & old flows; @@ -436,8 +437,8 @@ void findSurfArea(int j, double q, double length, double* h1, double* h2, double surfArea2 = 0.0; // surface area st downstrm node (ft2) double criticalDepth; // critical flow depth (ft) double normalDepth; // normal flow depth (ft) - double fullDepth; // full depth (ft) //(5.1.013) - double fasnh = 1.0; // fraction between norm. & crit. depth //(5.1.013) + double fullDepth; // full depth (ft) + double fasnh = 1.0; // fraction between norm. & crit. depth TXsect* xsect = &Link[j].xsect; // pointer to cross-section data // --- get node indexes & current flow depths diff --git a/src/solver/enums.h b/src/solver/enums.h index fc3cc2b4b..c260e4150 100644 --- a/src/solver/enums.h +++ b/src/solver/enums.h @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) +// Date: 06/01/22 (Build 5.2.1) // Author: L. Rossman // // Enumerated constants @@ -31,6 +31,8 @@ // - Support added for Streets and Inlets. // - Support added for variable speed pumps. // - Support added for analytical storage shapes. +// Build 5.2.1: +// - Adds a NEITHER option to the NormalFlowType enumeration. //----------------------------------------------------------------------------- #ifndef ENUMS_H @@ -356,7 +358,8 @@ enum CompatibilityType { enum NormalFlowType { SLOPE, // based on slope only FROUDE, // based on Fr only - BOTH}; // based on slope & Fr + BOTH, // based on slope & Fr + NEITHER}; enum InertialDampingType { NO_DAMPING, // no inertial damping diff --git a/src/solver/flowrout.c b/src/solver/flowrout.c index d38b68488..f296c3fb0 100644 --- a/src/solver/flowrout.c +++ b/src/solver/flowrout.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) +// Date: 05/02/22 (Build 5.2.1) // Author: L. Rossman // M. Tryby (EPA) // @@ -24,6 +24,9 @@ // - Arguments to function link_getLossRate changed. // Build 5.2.0: // - Correction made to updating state of terminal storage nodes. +// Build 5.2.1: +// - For storage routing, after convergence the reported depth is now +// based on the last volume found rather than the next trial depth. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -587,7 +590,6 @@ void updateStorageState(int i, int j, int links[], double dt) if ( fabs(d2 - d1) <= STOPTOL ) stopped = TRUE; // --- update old depth with new value and continue to iterate - Node[i].newDepth = d2; d1 = d2; iter++; } diff --git a/src/solver/inlet.c b/src/solver/inlet.c index 2a09642d3..1ec914c4d 100644 --- a/src/solver/inlet.c +++ b/src/solver/inlet.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) +// Date: 06/01/22 (Build 5.2.1) // Author: L. Rossman // // Street/Channel Inlet Functions @@ -14,6 +14,8 @@ // Administration Hydraulic Engineering Circular No. 22, 3rd Edition, // FHWA-NHI-10-009, August 2013). // +// Build 5.2.1: +// - Substitutes the constant BIG for HUGE. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -1567,7 +1569,7 @@ double getOnSagCapturedFlow(TInlet* inlet, double q, double d) // { int linkIndex, designIndex, totalInlets; - double qCaptured = 0.0, qMax = HUGE; + double qCaptured = 0.0, qMax = BIG; if (inlet->numInlets == 0) return 0.0; totalInlets = Nsides * inlet->numInlets; @@ -1680,7 +1682,7 @@ void findOnSagGrateFlows(int i, double d, double *Qw, double *Qo) // --- orifice flow applies else { - *Qo = 0.67 * Ao * sqrt(2.0 * GRAVITY * di); //HEC-22 Eq(4-27) + *Qo = 0.67 * Ao * sqrt(2.0 * 32.16 * di); //HEC-22 Eq(4-27) } } @@ -1763,7 +1765,7 @@ double getCurbOrificeFlow(double di, double h, double L, int throatAngle) d = di - h / 2.0; else if (throatAngle == INCLINED_THROAT) d = di + (h / 2.0) * 0.7071; - return 0.67 * h * L * sqrt(2.0 * GRAVITY * d); //HEC-22 Eq(4-31a) + return 0.67 * h * L * sqrt(2.0 * 32.16 * d); //HEC-22 Eq(4-31a) } //============================================================================= @@ -1895,7 +1897,7 @@ double getCustomCapturedFlow(TInlet* inlet, double q, double d) qBypassed, // inlet's bypassed flow (cfs) qCaptured, // inlet's captured flow (cfs) qIncrement, // increment to captured flow (cfs) - qMax = HUGE; // user-supplied flow capture limit (cfs) + qMax = BIG; // user-supplied flow capture limit (cfs) if (inlet->numInlets == 0) return 0.0; diff --git a/src/solver/input.c b/src/solver/input.c index 3613297b9..c0f7a0d9f 100644 --- a/src/solver/input.c +++ b/src/solver/input.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) +// Date: 06/01/22 (Build 5.2.1) // Author: L. Rossman // // Input data processing functions. @@ -19,6 +19,8 @@ // Build 5.2.0: // - Support added for Streets and Inlets. // - Support added for named variables & math expressions in control rules. +// Build 5.2.1: +// - Possible integer underflow avoided in getTokens() function. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -889,8 +891,8 @@ int getTokens(char *s) // in CONSTS.H. Text between quotes is treated as a single token. // { - int n; - size_t len, m; + int len, n; + int m; char *c; // --- begin with no tokens @@ -905,7 +907,7 @@ int getTokens(char *s) // --- scan s for tokens until nothing left while (len > 0 && n < MAXTOKS) { - m = strcspn(s,SEPSTR); // find token length + m = (int)strcspn(s,SEPSTR); // find token length if (m == 0) s++; // no token found else { @@ -913,7 +915,7 @@ int getTokens(char *s) { s++; // start token after quote len--; // reduce length of s - m = strcspn(s,"\"\n"); // find end quote or new line + m = (int)strcspn(s,"\"\n"); // find end quote or new line } s[m] = '\0'; // null-terminate the token Tok[n] = s; // save pointer to token diff --git a/src/solver/keywords.c b/src/solver/keywords.c index 17973d182..5f4b4e443 100644 --- a/src/solver/keywords.c +++ b/src/solver/keywords.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) +// Date: 06/01/22 (Build 5.2.1) // Author: L. Rossman // // Exportable keyword dictionary @@ -34,6 +34,8 @@ // - Support added for variable speed pumps. // - Support added for analytical storage shapes. // - Support added for RptFlags.disabled option. +// Build 5.2.1: +// - Adds NONE to the list of NormalFlowWords. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -65,7 +67,7 @@ char* LoadUnitsWords[] = { w_LBS, w_KG, w_LOGN }; char* NodeTypeWords[] = { w_JUNCTION, w_OUTFALL, w_STORAGE, w_DIVIDER }; char* NoneAllWords[] = { w_NONE, w_ALL, NULL}; -char* NormalFlowWords[] = { w_SLOPE, w_FROUDE, w_BOTH, NULL}; +char* NormalFlowWords[] = { w_SLOPE, w_FROUDE, w_BOTH, w_NONE, NULL}; char* NormalizerWords[] = { w_PER_AREA, w_PER_CURB, NULL}; char* NoYesWords[] = { w_NO, w_YES, NULL}; char* OffOnWords[] = { w_OFF, w_ON, NULL}; diff --git a/src/solver/link.c b/src/solver/link.c index 6de3688d7..b93cdfbaa 100644 --- a/src/solver/link.c +++ b/src/solver/link.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) +// Date: 06/01/22 (Build 5.2.1) // Author: L. Rossman // M. Tryby (EPA) // @@ -36,12 +36,14 @@ // - Support added for head-dependent weir coefficient curves. // - Adjustment of regulator link crest offset to match downstream node invert // now only done for Dynamic Wave flow routing. -// Build 5.1.014: -// - Conduit evap. and seepage losses initialized to 0 in conduit_initState() -// and not allowed to exceed current flow rate in conduit_getLossRate(). -// Build 5.2.0: +// Build 5.1.014: +// - Conduit evap. and seepage losses initialized to 0 in conduit_initState() +// and not allowed to exceed current flow rate in conduit_getLossRate(). +// Build 5.2.0: // - Support added for Streets and Inlets. // - Support added for variable speed pumps. +// Build 5.2.1 +// - Warning no longer issued when conduit elevation drop < MIN_DELTA_Z. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -1262,7 +1264,9 @@ double conduit_getSlope(int j) delta = fabs(elev1 - elev2); if ( delta < MIN_DELTA_Z ) { + /* Deprecated as of v.5.2.1 report_writeWarningMsg(WARN04, Link[j].ID); + */ delta = MIN_DELTA_Z; } diff --git a/src/solver/node.c b/src/solver/node.c index 65843252d..2b26ecd82 100644 --- a/src/solver/node.c +++ b/src/solver/node.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) +// Date: 06/01/22 (Build 5.2.1) // Author: L. Rossman // // Conveyance system node functions. @@ -27,9 +27,13 @@ // Build 5.1.015: // - Fatal error issued if a storage node's area curve produces a negative // volume when extrapolated to the node's full depth. -// Build 5.2.0: -// - Support added Streets and Inlets. -// - Support added for analytical storage shapes. +// Build 5.2.0: +// - Support added Streets and Inlets. +// - Support added for analytical storage shapes. +// Build 5.2.1: +// - Warning no longer issued when node full depth is increased to match +// crown of highest connecting link. +// - a2 term for paraboloid shaped storage units was corrected //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -200,11 +204,12 @@ void node_validate(int j) TDwfInflow* inflow; // --- see if full depth was increased to accommodate conduit crown +/* Deprecated as of v.5.2.1 if ( Node[j].fullDepth > Node[j].oldDepth && Node[j].oldDepth > 0.0 ) { report_writeWarningMsg(WARN02, Node[j].ID); } - +*/ // --- check that initial depth does not exceed max. depth if ( Node[j].initDepth > Node[j].fullDepth + Node[j].surDepth ) report_writeErrorMsg(ERR_NODE_DEPTH, Node[j].ID); diff --git a/src/solver/output.c b/src/solver/output.c index e45423b63..9a48f1200 100644 --- a/src/solver/output.c +++ b/src/solver/output.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) +// Date: 06/01/22 (Build 5.2.1) // Author: L. Rossman // // Binary output file access functions. @@ -26,6 +26,8 @@ // - Changed how time step averaged flow is computed. // - Object's rptFlag changed to record its index in output file. // - Large file support added. +// Build5.2.1: +// - Corrects the definition of F_OFF for non-Microsoft C/C++ compilers. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -33,8 +35,8 @@ #ifdef _MSC_VER // Windows (32-bit and 64-bit) #define F_OFF __int64 #define F_SEEK _fseeki64 -#elif // Other platforms - #define F_OFF off64_t +#else // Other platforms + #define F_OFF off_t #define F_SEEK fseeko #endif diff --git a/src/solver/qualrout.c b/src/solver/qualrout.c index 89788ba8e..31f6d3d7a 100644 --- a/src/solver/qualrout.c +++ b/src/solver/qualrout.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) +// Date: 06/01/22 (Build 5.2.1) // Author: L. Rossman // // Water quality routing functions. @@ -23,6 +23,8 @@ // Build 5.2.0: // - Support added for flow capture by inlet structures. // - Definition of a dry node/link modified. +// Build 5.2.1: +// - Wet non-storage nodes with no inflow now have no change in quality. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -110,10 +112,10 @@ void qualrout_execute(double tStep) // --- 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 @@ -235,8 +237,14 @@ void findNodeQual(int j) } } - // --- otherwise concen. is 0 - else for (p = 0; p < Nobjects[POLLUT]; p++) Node[j].newQual[p] = 0.0; + // --- otherwise concen. remains the same //5.2.1 + else for (p = 0; p < Nobjects[POLLUT]; p++) + { + if (Node[j].newDepth > ZeroDepth) + Node[j].newQual[p] = Node[j].oldQual[p]; + else + Node[j].newQual[p] = 0.0; + } } //============================================================================= diff --git a/src/solver/statsrpt.c b/src/solver/statsrpt.c index 7cd18576f..3bd799b9f 100644 --- a/src/solver/statsrpt.c +++ b/src/solver/statsrpt.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) +// Date: 06/01/22 (Build 5.2.1) // Author: L. Rossman // // Report writing functions for summary statistics. @@ -25,6 +25,8 @@ // Build 5.2.0: // - Adds a new Street & Inlet Summary table. // - Fixes value used for total reporting time. +// Build 5.2.1: +// - Replaces the "3" in "ft3" and "m3" with ANSI superscript (\xB3). //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -87,7 +89,7 @@ void statsrpt_writeReport() if ( FlowUnits == MGD || FlowUnits == CMS ) sstrncpy(FlowFmt, "%9.3f", 5); else sstrncpy(FlowFmt, "%9.2f", 5); - // --- volume conversion factor from ft3 to Mgal or Mliters + // --- conversion factor from cu. ft. to mil. gallons or megaliters if (UnitSystem == US) Vcf = 7.48 / 1.0e6; else Vcf = 28.317 / 1.0e6; @@ -524,9 +526,9 @@ void writeStorageVolumes() "\n Average Avg Evap Exfil Maximum Max Time of Max Maximum" "\n Volume Pcnt Pcnt Pcnt Volume Pcnt Occurrence Outflow"); if ( UnitSystem == US ) fprintf(Frpt.file, -"\n Storage Unit 1000 ft3 Full Loss Loss 1000 ft3 Full days hr:min "); +"\n Storage Unit 1000 ft\xB3 Full Loss Loss 1000 ft\xB3 Full days hr:min "); else fprintf(Frpt.file, -"\n Storage Unit 1000 m3 Full Loss Loss 1000 m3 Full days hr:min "); +"\n Storage Unit 1000 m\xB3 Full Loss Loss 1000 m\xB3 Full days hr:min "); fprintf(Frpt.file, "%3s", FlowUnitWords[FlowUnits]); fprintf(Frpt.file, "\n --------------------------------------------------------------------------------------------------"); diff --git a/src/solver/text.h b/src/solver/text.h index a9c52be33..70bfc1f50 100644 --- a/src/solver/text.h +++ b/src/solver/text.h @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) +// Date: 06/01/22 (Build 5.2.1) // Author: L. Rossman // // Text strings @@ -25,7 +25,7 @@ #define FMT03 " There are errors.\n" #define FMT04 " There are warnings.\n" #define FMT08 \ - "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.2 (Build 5.2.0)" + "\n EPA STORM WATER MANAGEMENT MODEL - VERSION 5.2 (Build 5.2.1)" #define FMT09 \ "\n ------------------------------------------------------------" #define FMT10 "\n" From 24c29cdf01151ae2d8e900486be2251cbcf04300 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 8 Jun 2022 09:19:40 -0400 Subject: [PATCH 240/266] Update build-and-test.yml Workaround for latest tag glitch --- .github/workflows/build-and-test.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 67b93f894..8368e768a 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -96,6 +96,11 @@ jobs: - name: Before test run: before-nrtest.cmd + - name: Before reg test + env: + NRTESTS_URL: https://github.com/SWMM-Project/swmm-nrtestsuite + BENCHMARK_TAG: v2.0.0 + run: before-nrtest.cmd ${{ env.BENCHMARK_TAG }} - name: Reg test run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% From 6243a72c77e8279f62c516eb95b1ff677fc11edd Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 8 Jun 2022 09:20:58 -0400 Subject: [PATCH 241/266] Update build-and-test.yml Fix typo --- .github/workflows/build-and-test.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 8368e768a..a19a28c69 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -94,8 +94,6 @@ jobs: - name: Build run: make.cmd /g "Visual Studio 16 2019" - - name: Before test - run: before-nrtest.cmd - name: Before reg test env: NRTESTS_URL: https://github.com/SWMM-Project/swmm-nrtestsuite From e91ad951b5d7da6314be766af080b66aaca07240 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 8 Jun 2022 09:36:21 -0400 Subject: [PATCH 242/266] Update build-and-test.yml Workaround for latest tag glitch --- .github/workflows/build-and-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 166162c1f..c43d047ed 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -98,7 +98,8 @@ jobs: - name: Before test env: NRTESTS_URL: https://github.com/SWMM-Project/swmm-nrtestsuite - run: before-nrtest.cmd + BENCHMARK_TAG: v2.0.0 + run: before-nrtest.cmd ${{ env.BENCHMARK_TAG }} - name: Run reg test run: run-nrtests.cmd %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% From 9d050198d2432d8196193d5a0de6bbd708d5fcef Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Sun, 12 Jun 2022 11:25:48 -0400 Subject: [PATCH 243/266] v5.2.1 correction to Egg geometry tables --- src/solver/xsect.dat | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/solver/xsect.dat b/src/solver/xsect.dat index fa199bb40..10b10ed33 100644 --- a/src/solver/xsect.dat +++ b/src/solver/xsect.dat @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 03/24/21 (Build 5.2.0) +// Date: 06/01/22 (Build 5.2.1) // Author: L. Rossman // // Tables of relative geometric properties for rounded cross-sections. @@ -18,6 +18,10 @@ // area at full depth for shape xxx // N_Z_xxx = number of equal intervals between 0.0 and 1.0 in the // table for parameter Z and shape xxx. +// +// Release 5.2.1: +// - The second and third entries in tables A_Egg, R_Egg, and W_Egg +// were corrected. //----------------------------------------------------------------------------- //============================================================================= @@ -76,13 +80,13 @@ double W_Circ[51] = // W/Wmax v. Y/Yfull int N_A_Egg = 26; double A_Egg[26] = -{.0000,.0150,.0400,.0550,.0850,.1200,.1555,.1900,.2250,.2750, +{.0000,.0116,.0316,.0550,.0850,.1200,.1555,.1900,.2250,.2750, .3200,.3700,.4200,.4700,.5150,.5700,.6200,.6800,.7300,.7800, .8350,.8850,.9250,.9550,.9800,1.000}; int N_R_Egg = 26; double R_Egg[26] = -{.0100,.0970,.2160,.3020,.3860,.4650,.5360,.6110,.6760,.7350, +{.0100,.1300,.2440,.3020,.3860,.4650,.5360,.6110,.6760,.7350, .7910,.8540,.9040,.9410,1.008,1.045,1.076,1.115,1.146,1.162, 1.186,1.193,1.186,1.162,1.107,1.000}; @@ -108,7 +112,7 @@ double S_Egg[51] = int N_W_Egg = 26; double W_Egg[26] = -{.0, .2980, .4330, .5080, .5820, .6420, .6960, .7460, .7910, +{.0, .3250, .4270, .5080, .5820, .6420, .6960, .7460, .7910, .8360, .8660, .8960, .9260, .9560, .9700, .9850, 1.000, .9850, .9700, .9400, .8960, .8360, .7640, .6420, .3100, .0}; From fc9b694f0d84d5b68d2ead214994cda7bcc1d813 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Tue, 19 Jul 2022 15:06:07 -0400 Subject: [PATCH 244/266] Change how dry node quality is updated --- src/solver/dwflow.c | 4 ++-- src/solver/qualrout.c | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/solver/dwflow.c b/src/solver/dwflow.c index 9d3c2595e..d6484fe57 100644 --- a/src/solver/dwflow.c +++ b/src/solver/dwflow.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 04/02/22 (Build 5.2.1) +// Date: 08/01/22 (Build 5.2.1) // Author: L. Rossman // M. Tryby (EPA) // R. Dickinson (CDM) @@ -656,7 +656,7 @@ double checkNormalFlow(int j, double q, double y1, double y2, double a1, // --- check if water surface slope < conduit slope if ( NormalFlowLtd == SLOPE || NormalFlowLtd == BOTH || hasOutfall ) { - if ( y1 < y2 ) check = TRUE; + if ( y1 < y2) check = TRUE; } // --- check if Fr >= 1.0 at upstream end of conduit diff --git a/src/solver/qualrout.c b/src/solver/qualrout.c index 31f6d3d7a..9fb0b263a 100644 --- a/src/solver/qualrout.c +++ b/src/solver/qualrout.c @@ -3,7 +3,7 @@ // // Project: EPA SWMM5 // Version: 5.2 -// Date: 06/01/22 (Build 5.2.1) +// Date: 08/01/22 (Build 5.2.1) // Author: L. Rossman // // Water quality routing functions. @@ -24,6 +24,7 @@ // - 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. // - Wet non-storage nodes with no inflow now have no change in quality. //----------------------------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE @@ -229,7 +230,7 @@ void findNodeQual(int j) // --- if there is flow into node then concen. = mass inflow/node flow qNode = Node[j].qualInflow; - if ( qNode > ZERO && Node[j].newDepth > ZeroDepth) + if ( qNode > ZERO ) { for (p = 0; p < Nobjects[POLLUT]; p++) { @@ -237,7 +238,7 @@ void findNodeQual(int j) } } - // --- otherwise concen. remains the same //5.2.1 + // --- otherwise concen. remains the same else for (p = 0; p < Nobjects[POLLUT]; p++) { if (Node[j].newDepth > ZeroDepth) From 3cf22021b125faf15d792f5e703d26dba6f8a468 Mon Sep 17 00:00:00 2001 From: Tryby Date: Tue, 16 Aug 2022 16:02:10 -0400 Subject: [PATCH 245/266] Update workflow triggers --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a19a28c69..4da6f663d 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,9 +2,9 @@ name: Build and Test on: push: - branches: [ lew-develop ] + branches: [ master, develop, release, lew-develop ] pull_request: - branches: [ lew-develop ] + branches: [ master, develop, release, lew-develop ] env: OMP_NUM_THREADS: 1 From b27308288c0965d34dac4a3b11978beb2c2f05f9 Mon Sep 17 00:00:00 2001 From: Tryby Date: Tue, 16 Aug 2022 17:16:43 -0400 Subject: [PATCH 246/266] Bump version --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 18cfa7c7a..4e6e0154f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ # CMakeLists.txt - CMake configuration file for swmm-solver # # Created: July 11, 2019 -# Modified: Nov 25, 2019 +# Modified: Aug 16, 2022 # # Author: Michael E. Tryby # US EPA ORD/CESER @@ -17,7 +17,7 @@ endif() project(swmm-solver - VERSION 5.2.0 + VERSION 5.2.1 LANGUAGES C CXX ) From d319b6f32b9c180c25b99b0078ebb43f58864098 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Wed, 17 Aug 2022 11:10:20 -0400 Subject: [PATCH 247/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 4da6f663d..6ffe329b0 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -101,7 +101,7 @@ jobs: run: before-nrtest.cmd ${{ env.BENCHMARK_TAG }} - name: Reg test - run: run-nrtests.cmd %GITHUB_SHA% %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% + run: run-nrtests.cmd %GITHUB_RUN_ID%_%GITHUB_RUN_NUMBER% - name: Upload artifacts if: ${{ always() }} From 08a85e66b9f038e897b7d68b26b2d83d6c2fd8d3 Mon Sep 17 00:00:00 2001 From: Michael Tryby Date: Tue, 18 Oct 2022 11:15:53 -0400 Subject: [PATCH 248/266] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 6ffe329b0..730409adb 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -96,8 +96,8 @@ jobs: - name: Before reg test env: - NRTESTS_URL: https://github.com/SWMM-Project/swmm-nrtestsuite - BENCHMARK_TAG: v2.0.0 + NRTESTS_URL: https://github.com/USEPA/swmm-nrtestsuite + BENCHMARK_TAG: v2.1.0-rc.1 run: before-nrtest.cmd ${{ env.BENCHMARK_TAG }} - name: Reg test From 8ff5fffe7364a3512caf813eacc688e13af5ebdc Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 15:54:10 -0400 Subject: [PATCH 249/266] WIP use package --- .github/workflows/build-and-test.yml | 43 ++++++++++------------------ extern/boost.cmake | 23 +++++---------- 2 files changed, 22 insertions(+), 44 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 4414b2820..824cf4dc3 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -10,11 +10,14 @@ env: OMP_NUM_THREADS: 1 BUILD_HOME: build TEST_HOME: nrtests + PACKAGE_NAME: vcpkg-export-20220826-200052.1.0.0 + PKG_NAME: vcpkg-export-20220826-200052 jobs: unit_test: name: Build and unit test runs-on: windows-2019 + environment: testing defaults: run: shell: cmd @@ -30,37 +33,21 @@ jobs: repository: michaeltryby/ci-tools path: ci-tools - - name: Add tar.exe - if: ${{ runner.os == 'Windows' }} - shell: pwsh - run: | - "C:\Program Files\Git\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 - - - - name: Cache boost - id: cache-boost - uses: actions/cache@v2 - env: - cache_paths: | - C:/ProgramData/chocolatey/lib/boost-* - C:/local/boost_* - pkg_file_hash: ${{ hashFiles('**/windows/packages.config') }} - with: - path: ${{ env.cache_paths }} - key: ${{ runner.os }}-cache-boost-${{ env.pkg_file_hash }} - restore-keys: | - ${{ runner.os }}-cache-boost- - ${{ runner.os }}-cache- - ${{ runner.os }}- - - - name: Install boost - if: steps.cache-boost.outputs.cache-hit != 'true' + - name: Install boost-test env: - pkg_cmnd: choco install -y packages.config - run: ${{ env.pkg_cmnd }} + REMOTE_STORE: "https://nuget.pkg.github.com/michaeltryby/index.json" + USERNAME: michaeltryby + run: | + nuget sources add -Name github -Source ${{ env.REMOTE_STORE }} -Username ${{ env.USERNAME }} -Password ${{ secrets.ACCESS_TOKEN }} + nuget install ${{env.PKG_NAME}} -Source github - name: Build and unit test - run: make.cmd /t /g "Visual Studio 16 2019" + env: + TOOL_CHAIN_PATH: \scripts\buildsystems\vcpkg.cmake + run: | + cd build + cmake .. -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME_LONG}}\scripts\buildsystems\vcpkg.cmake + cmake --build . reg_test: diff --git a/extern/boost.cmake b/extern/boost.cmake index 84d29175a..28a5568a5 100644 --- a/extern/boost.cmake +++ b/extern/boost.cmake @@ -2,7 +2,7 @@ # CMakeLists.txt - CMake configuration file for swmm-solver/extern # # Created: March 16, 2020 -# Updated: May 21, 2020 +# Updated: Oct 19, 2022 # # Author: Michael E. Tryby # US EPA - ORD/CESER @@ -17,22 +17,13 @@ else() endif() -# Environment variable "BOOST_ROOT_X_XX_X" points to local install location -if(DEFINED ENV{BOOST_ROOT_1_74_0}) - set(BOOST_ROOT $ENV{BOOST_ROOT_1_74_0}) - -elseif(DEFINED ENV{BOOST_ROOT_1_72_0}) - set(BOOST_ROOT $ENV{BOOST_ROOT_1_72_0}) - -elseif(DEFINED ENV{BOOST_ROOT_1_67_0}) - set(BOOST_ROOT $ENV{BOOST_ROOT_1_67_0}) - -endif() - - -find_package(Boost 1.67.0 +find_package( + Boost + REQUIRED COMPONENTS unit_test_framework ) -include_directories (${Boost_INCLUDE_DIRS}) +include_directories( + ${Boost_INCLUDE_DIRS} +) From c987969a84c296d7955ddcccdd07dad39c2d6d87 Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 16:03:34 -0400 Subject: [PATCH 250/266] Fix path issue --- .github/workflows/build-and-test.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 824cf4dc3..2a7e47fbe 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -21,18 +21,11 @@ jobs: defaults: run: shell: cmd - working-directory: ci-tools/windows steps: - name: Checkout repo uses: actions/checkout@v2 - - name: Checkout submodule - uses: actions/checkout@v2 - with: - repository: michaeltryby/ci-tools - path: ci-tools - - name: Install boost-test env: REMOTE_STORE: "https://nuget.pkg.github.com/michaeltryby/index.json" From 7ec29311efe6b20f4072fc8b33d00f876aa8ccf6 Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 16:18:35 -0400 Subject: [PATCH 251/266] Build path issue --- .github/workflows/build-and-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 2a7e47fbe..fb727ec37 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -26,6 +26,7 @@ jobs: - name: Checkout repo uses: actions/checkout@v2 + - name: Install boost-test env: REMOTE_STORE: "https://nuget.pkg.github.com/michaeltryby/index.json" @@ -39,7 +40,7 @@ jobs: TOOL_CHAIN_PATH: \scripts\buildsystems\vcpkg.cmake run: | cd build - cmake .. -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME_LONG}}\scripts\buildsystems\vcpkg.cmake + cmake -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=..\${{env.PACKAGE_NAME_LONG}}${{env.TOOL_CHAIN_PATH}} .. cmake --build . From 450fdba82e3906417281bb45a9b37cbb75830082 Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 16:27:46 -0400 Subject: [PATCH 252/266] Fix build path issue --- .github/workflows/build-and-test.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index fb727ec37..ba45e7733 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -39,9 +39,8 @@ jobs: env: TOOL_CHAIN_PATH: \scripts\buildsystems\vcpkg.cmake run: | - cd build - cmake -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=..\${{env.PACKAGE_NAME_LONG}}${{env.TOOL_CHAIN_PATH}} .. - cmake --build . + cmake -B.\build -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME_LONG}}${{env.TOOL_CHAIN_PATH}} . + cmake --build .\build --config DEBUG reg_test: From cd00d6c092eade5058d02944fb3d4b988550fa28 Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 16:30:14 -0400 Subject: [PATCH 253/266] Fix path bug --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ba45e7733..fb5fc3e1c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -39,7 +39,7 @@ jobs: env: TOOL_CHAIN_PATH: \scripts\buildsystems\vcpkg.cmake run: | - cmake -B.\build -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME_LONG}}${{env.TOOL_CHAIN_PATH}} . + cmake -B.\build -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME}}${{env.TOOL_CHAIN_PATH}} . cmake --build .\build --config DEBUG From 91c408b6cffe223ec97f13e462ffc6c6912e0ed1 Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 16:57:23 -0400 Subject: [PATCH 254/266] Fix variable name --- .github/workflows/build-and-test.yml | 7 +++++-- extern/boost.cmake | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index fb5fc3e1c..0da560ae4 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -35,13 +35,16 @@ jobs: nuget sources add -Name github -Source ${{ env.REMOTE_STORE }} -Username ${{ env.USERNAME }} -Password ${{ secrets.ACCESS_TOKEN }} nuget install ${{env.PKG_NAME}} -Source github - - name: Build and unit test + - name: Build env: TOOL_CHAIN_PATH: \scripts\buildsystems\vcpkg.cmake run: | - cmake -B.\build -DBUILD-TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME}}${{env.TOOL_CHAIN_PATH}} . + cmake -B.\build -DBUILD_TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME}}${{env.TOOL_CHAIN_PATH}} . cmake --build .\build --config DEBUG + -name: Unit Test + run: ctest -B.\build -C Debug --output-on-failure + reg_test: name: Build and reg test diff --git a/extern/boost.cmake b/extern/boost.cmake index 28a5568a5..c512fb559 100644 --- a/extern/boost.cmake +++ b/extern/boost.cmake @@ -19,7 +19,6 @@ endif() find_package( Boost - REQUIRED COMPONENTS unit_test_framework ) From 9d42aaf7f2364c0978489b85b5ee96be596e1375 Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 17:42:13 -0400 Subject: [PATCH 255/266] Fix yml syntax error --- .github/workflows/build-and-test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 0da560ae4..f95800832 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -26,7 +26,6 @@ jobs: - name: Checkout repo uses: actions/checkout@v2 - - name: Install boost-test env: REMOTE_STORE: "https://nuget.pkg.github.com/michaeltryby/index.json" @@ -42,7 +41,7 @@ jobs: cmake -B.\build -DBUILD_TESTS=ON -DCMAKE_TOOLCHAIN_FILE=.\${{env.PACKAGE_NAME}}${{env.TOOL_CHAIN_PATH}} . cmake --build .\build --config DEBUG - -name: Unit Test + - name: Unit Test run: ctest -B.\build -C Debug --output-on-failure From ca1c302cbd0448c5b9353bb183a9f70a4cc11e9e Mon Sep 17 00:00:00 2001 From: Tryby Date: Wed, 19 Oct 2022 17:51:15 -0400 Subject: [PATCH 256/266] Config test runner --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index f95800832..9e2d4f1ff 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -42,7 +42,7 @@ jobs: cmake --build .\build --config DEBUG - name: Unit Test - run: ctest -B.\build -C Debug --output-on-failure + run: ctest --test-dir .\build -C Debug --output-on-failure reg_test: From c54b60f60641453f7f43b4b244a837d666f848b8 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Mon, 21 Nov 2022 11:10:47 -0500 Subject: [PATCH 257/266] Release 5.2.2 --- Readme.txt | 4 +- src/climate.c | 1619 +++++++++++++++++++++++++++++ src/consts.h | 102 ++ src/controls.c | 1553 ++++++++++++++++++++++++++++ src/culvert.c | 411 ++++++++ src/datetime.c | 528 ++++++++++ src/datetime.h | 72 ++ src/dwflow.c | 684 +++++++++++++ src/dynwave.c | 908 ++++++++++++++++ src/enums.h | 500 +++++++++ src/error.c | 49 + src/error.h | 182 ++++ src/error.txt | 135 +++ src/exfil.c | 252 +++++ src/exfil.h | 35 + src/findroot.c | 138 +++ src/findroot.h | 18 + src/flowrout.c | 800 +++++++++++++++ src/forcmain.c | 157 +++ src/funcs.h | 547 ++++++++++ src/gage.c | 705 +++++++++++++ src/globals.h | 172 ++++ src/gwater.c | 872 ++++++++++++++++ src/hash.c | 117 +++ src/hash.h | 30 + src/headers.h | 20 + src/hotstart.c | 544 ++++++++++ src/iface.c | 648 ++++++++++++ src/infil.c | 1006 ++++++++++++++++++ src/infil.h | 112 ++ src/inflow.c | 484 +++++++++ src/inlet.c | 1955 +++++++++++++++++++++++++++++++++++ src/inlet.h | 30 + src/input.c | 930 +++++++++++++++++ src/inputrpt.c | 354 +++++++ src/keywords.c | 172 ++++ src/keywords.h | 73 ++ src/kinwave.c | 272 +++++ src/landuse.c | 723 +++++++++++++ src/lid.c | 2031 ++++++++++++++++++++++++++++++++++++ src/lid.h | 236 +++++ src/lidproc.c | 1592 ++++++++++++++++++++++++++++ src/link.c | 2679 ++++++++++++++++++++++++++++++++++++++++++++++++ src/macros.h | 45 + src/main.c | 104 ++ src/massbal.c | 1046 +++++++++++++++++++ src/mathexpr.c | 768 ++++++++++++++ src/mathexpr.h | 36 + src/mempool.c | 204 ++++ src/mempool.h | 26 + src/node.c | 1494 +++++++++++++++++++++++++++ src/objects.h | 1090 ++++++++++++++++++++ src/odesolve.c | 234 +++++ src/odesolve.h | 19 + src/output.c | 956 +++++++++++++++++ src/project.c | 1335 ++++++++++++++++++++++++ src/qualrout.c | 519 ++++++++++ src/rain.c | 1166 +++++++++++++++++++++ src/rdii.c | 1539 ++++++++++++++++++++++++++++ src/report.c | 1519 +++++++++++++++++++++++++++ src/roadway.c | 194 ++++ src/routing.c | 962 +++++++++++++++++ src/runoff.c | 527 ++++++++++ src/shape.c | 369 +++++++ src/snow.c | 865 ++++++++++++++++ src/stats.c | 865 ++++++++++++++++ src/statsrpt.c | 958 +++++++++++++++++ src/street.c | 162 +++ src/street.h | 21 + src/subcatch.c | 1159 +++++++++++++++++++++ src/surfqual.c | 478 +++++++++ src/swmm5.c | 1721 +++++++++++++++++++++++++++++++ src/swmm5.def | 23 + src/swmm5.h | 157 +++ src/table.c | 895 ++++++++++++++++ src/text.h | 462 +++++++++ src/toposort.c | 533 ++++++++++ src/transect.c | 678 ++++++++++++ src/treatmnt.c | 456 +++++++++ src/xsect.c | 2618 ++++++++++++++++++++++++++++++++++++++++++++++ src/xsect.dat | 491 +++++++++ 81 files changed, 51143 insertions(+), 2 deletions(-) create mode 100644 src/climate.c create mode 100644 src/consts.h create mode 100644 src/controls.c create mode 100644 src/culvert.c create mode 100644 src/datetime.c create mode 100644 src/datetime.h create mode 100644 src/dwflow.c create mode 100644 src/dynwave.c create mode 100644 src/enums.h create mode 100644 src/error.c create mode 100644 src/error.h create mode 100644 src/error.txt create mode 100644 src/exfil.c create mode 100644 src/exfil.h create mode 100644 src/findroot.c create mode 100644 src/findroot.h create mode 100644 src/flowrout.c create mode 100644 src/forcmain.c create mode 100644 src/funcs.h create mode 100644 src/gage.c create mode 100644 src/globals.h create mode 100644 src/gwater.c create mode 100644 src/hash.c create mode 100644 src/hash.h create mode 100644 src/headers.h create mode 100644 src/hotstart.c create mode 100644 src/iface.c create mode 100644 src/infil.c create mode 100644 src/infil.h create mode 100644 src/inflow.c create mode 100644 src/inlet.c create mode 100644 src/inlet.h create mode 100644 src/input.c create mode 100644 src/inputrpt.c create mode 100644 src/keywords.c create mode 100644 src/keywords.h create mode 100644 src/kinwave.c create mode 100644 src/landuse.c create mode 100644 src/lid.c create mode 100644 src/lid.h create mode 100644 src/lidproc.c create mode 100644 src/link.c create mode 100644 src/macros.h create mode 100644 src/main.c create mode 100644 src/massbal.c create mode 100644 src/mathexpr.c create mode 100644 src/mathexpr.h create mode 100644 src/mempool.c create mode 100644 src/mempool.h create mode 100644 src/node.c create mode 100644 src/objects.h create mode 100644 src/odesolve.c create mode 100644 src/odesolve.h create mode 100644 src/output.c create mode 100644 src/project.c create mode 100644 src/qualrout.c create mode 100644 src/rain.c create mode 100644 src/rdii.c create mode 100644 src/report.c create mode 100644 src/roadway.c create mode 100644 src/routing.c create mode 100644 src/runoff.c create mode 100644 src/shape.c create mode 100644 src/snow.c create mode 100644 src/stats.c create mode 100644 src/statsrpt.c create mode 100644 src/street.c create mode 100644 src/street.h create mode 100644 src/subcatch.c create mode 100644 src/surfqual.c create mode 100644 src/swmm5.c create mode 100644 src/swmm5.def create mode 100644 src/swmm5.h create mode 100644 src/table.c create mode 100644 src/text.h create mode 100644 src/toposort.c create mode 100644 src/transect.c create mode 100644 src/treatmnt.c create mode 100644 src/xsect.c create mode 100644 src/xsect.dat diff --git a/Readme.txt b/Readme.txt index f092f1de5..7bbb52e66 100644 --- a/Readme.txt +++ b/Readme.txt @@ -1,8 +1,8 @@ -CONTENTS OF SWMM520_ENGINE.ZIP +CONTENTS OF SWMM522_ENGINE.ZIP ============================== The 'src' folder of this archive contains the C source code for -version 5.2.0 of the Storm Water Management Model's computational +version 5.2.2 of the Storm Water Management Model's computational engine. Consult the included 'Roadmap.txt' file for an overview of the various code modules. The code can be compiled into both a shared object library and a command line executable. Under Windows, the diff --git a/src/climate.c b/src/climate.c new file mode 100644 index 000000000..336f0aaf0 --- /dev/null +++ b/src/climate.c @@ -0,0 +1,1619 @@ +//----------------------------------------------------------------------------- +// climate.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Climate related functions. +// +// Update History +// ============== +// Build 5.1.007: +// - NCDC GHCN climate file format added. +// - Monthly adjustments for temperature, evaporation & rainfall added. +// Build 5.1.008: +// - Monthly adjustments for hyd. conductivity added. +// - Time series evaporation rates can now vary within a day. +// - Evaporation rates are now properly updated when only flow routing +// is being simulated. +// Build 5.1.010: +// - Hargreaves evaporation now computed using 7-day average temperatures. +// Build 5.1.011: +// - Monthly adjustment for hyd. conductivity <= 0 is ignored. +// Build 5.1.013: +// - Reads names of monthly adjustment patterns for various parameters +// of a subcatchment from the [ADJUSTMENTS] section of input file. +// Build 5.2.0: +// - Reads temperature units for use with GHCND climate files. +// - Support added for relative file names. +///----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +enum ClimateFileFormats {UNKNOWN_FORMAT, + USER_PREPARED, // SWMM 5's own user format + GHCND, // NCDC GHCN Daily format + TD3200, // NCDC TD3200 format + DLY0204}; // Canadian DLY02 or DLY04 format +static const int MAXCLIMATEVARS = 4; +static const int MAXDAYSPERMONTH = 32; + +// These variables are used when processing climate files. +enum ClimateVarType {TMIN, TMAX, EVAP, WIND}; +enum WindSpeedType {WDMV, AWND}; +enum TempUnitsType {DEG_C10, DEG_C, DEG_F}; +static char* ClimateVarWords[] = {"TMIN", "TMAX", "EVAP", "WDMV", "AWND", + NULL}; +static char* TempUnitsWords[] = {"C10", "C", "F", NULL}; + +//----------------------------------------------------------------------------- +// Data Structures +//----------------------------------------------------------------------------- +typedef struct +{ + double tAve; // moving avg. for daily temperature (deg F) + double tRng; // moving avg. for daily temp. range (deg F) + double ta[7]; // data window for tAve + double tr[7]; // data window for tRng + int count; // length of moving average window + int maxCount; // maximum length of moving average window + int front; // index of front of moving average window +} TMovAve; + + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +// Temperature variables +static double Tmin; // min. daily temperature (deg F) +static double Tmax; // max. daily temperature (deg F) +static double Trng; // 1/2 range of daily temperatures +static double Trng1; // prev. max - current min. temp. +static double Tave; // average daily temperature (deg F) +static double Hrsr; // time of min. temp. (hrs) +static double Hrss; // time of max. temp (hrs) +static double Hrday; // avg. of min/max temp times +static double Dhrdy; // hrs. between min. & max. temp. times +static double Dydif; // hrs. between max. & min. temp. times +static DateTime LastDay; // date of last day with temp. data +static TMovAve Tma; // moving average of daily temperatures + +// Evaporation variables +static DateTime NextEvapDate; // next date when evap. rate changes +static double NextEvapRate; // next evaporation rate (user units) + +// Climate file variables +static int FileFormat; // file format (see ClimateFileFormats) +static int FileYear; // current year of file data +static int FileMonth; // current month of year of file data +static int FileDay; // current day of month of file data +static int FileLastDay; // last day of current month of file data +static int FileElapsedDays; // number of days read from file +static double FileValue[4]; // current day's values of climate data +static double FileData[4][32]; // month's worth of daily climate data +static char FileLine[MAXLINE+1]; // line from climate data file + +static int FileFieldPos[4]; // start of data fields for file record +static int FileDateFieldPos; // start of date field for file record +static int FileWindType; // wind speed type +static int FileTempUnits; // GHCND file temperature units (C10, C or F) + +//----------------------------------------------------------------------------- +// External functions (defined in funcs.h) +//----------------------------------------------------------------------------- +// climate_readParams // called by input_parseLine +// climate_readEvapParams // called by input_parseLine +// climate_validate // called by project_validate +// climate_openFile // called by runoff_open +// climate_initState // called by project_init +// climate_setState // called by runoff_execute +// climate_getNextEvapDate // called by runoff_getTimeStep + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int getFileFormat(void); +static void readFileLine(int *year, int *month); +static void readUserFileLine(int *year, int *month); +static void readTD3200FileLine(int *year, int *month); +static void readDLY0204FileLine(int *year, int *month); +static void readFileValues(void); + +static void setNextEvapDate(DateTime thedate); +static void setEvap(DateTime theDate); +static void setTemp(DateTime theDate); +static void setWind(DateTime theDate); +static void updateTempTimes(int day); +static void updateTempMoveAve(double tmin, double tmax); +static double getTempEvap(int day, double ta, double tr); + +static void updateFileValues(DateTime theDate); +static void parseUserFileLine(void); +static void parseTD3200FileLine(void); +static void parseDLY0204FileLine(void); +static void setTD3200FileValues(int param); + +static int isGhcndFormat(char* line); +static void readGhcndFileLine(int *year, int *month); +static void parseGhcndFileLine(void); +static double convertGhcndValue(int var, double v); + +//============================================================================= + +int climate_readParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads climate/temperature parameters from input line of data +// +// Format of data can be +// TIMESERIES name +// FILE name (start) (units) +// WINDSPEED MONTHLY v1 v2 ... v12 +// WINDSPEED FILE +// SNOWMELT v1 v2 ... v6 +// ADC IMPERV/PERV v1 v2 ... v10 +// +{ + int i, j, k; + double x[6], y; + DateTime aDate; + char fname[MAXFNAME + 1]; + + // --- identify keyword + k = findmatch(tok[0], TempKeyWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); + switch (k) + { + case 0: // Time series name + // --- check that time series name exists + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + i = project_findObject(TSERIES, tok[1]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[1]); + + // --- record the time series as being the data source for temperature + Temp.dataSource = TSERIES_TEMP; + Temp.tSeries = i; + Tseries[i].refersTo = TSERIES_TEMP; + break; + + case 1: // Climate file + // --- record file as being source of temperature data + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + Temp.dataSource = FILE_TEMP; + + // --- save name and usage mode of external climate file + Fclimate.mode = USE_FILE; + sstrncpy(fname, tok[1], MAXFNAME); + sstrncpy(Fclimate.name, addAbsolutePath(fname), MAXFNAME); + + // --- save starting date to read from file if one is provided + Temp.fileStartDate = NO_DATE; + if ( ntoks > 2 ) + { + if ( *tok[2] != '*') + { + if ( !datetime_strToDate(tok[2], &aDate) ) + return error_setInpError(ERR_DATETIME, tok[2]); + Temp.fileStartDate = aDate; + } + } + + // --- file temperature units + FileTempUnits = DEG_F; + if (UnitSystem == SI) + FileTempUnits = DEG_C; + if (ntoks > 3) + { + i = findmatch(tok[3], TempUnitsWords); + if (i < 0) + return error_setInpError(ERR_KEYWORD, tok[3]); + FileTempUnits = i; + } + break; + + case 2: // Wind speeds + // --- check if wind speeds will be supplied from climate file + if ( strcomp(tok[1], w_FILE) ) + { + Wind.type = FILE_WIND; + } + + // --- otherwise read 12 monthly avg. wind speed values + else + { + if ( ntoks < 14 ) return error_setInpError(ERR_ITEMS, ""); + Wind.type = MONTHLY_WIND; + for (i=0; i<12; i++) + { + if ( !getDouble(tok[i+2], &y) ) + return error_setInpError(ERR_NUMBER, tok[i+2]); + Wind.aws[i] = y; + } + } + break; + + case 3: // Snowmelt params + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + for (i=1; i<7; i++) + { + if ( !getDouble(tok[i], &x[i-1]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + // --- convert deg. C to deg. F for snowfall temperature + if ( UnitSystem == SI ) x[0] = 9./5.*x[0] + 32.0; + Snow.snotmp = x[0]; + Snow.tipm = x[1]; + Snow.rnm = x[2]; + Temp.elev = x[3] / UCF(LENGTH); + Temp.anglat = x[4]; + Temp.dtlong = x[5] / 60.0; + break; + + case 4: // Areal Depletion Curve data + // --- check if data is for impervious or pervious areas + if ( ntoks < 12 ) return error_setInpError(ERR_ITEMS, ""); + if ( match(tok[1], w_IMPERV) ) i = 0; + else if ( match(tok[1], w_PERV) ) i = 1; + else return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- read 10 fractional values + for (j=0; j<10; j++) + { + if ( !getDouble(tok[j+2], &y) || y < 0.0 || y > 1.0 ) + return error_setInpError(ERR_NUMBER, tok[j+2]); + Snow.adc[i][j] = y; + } + break; + } + return 0; +} + +//============================================================================= + +int climate_readEvapParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads evaporation parameters from input line of data. +// +// Data formats are: +// CONSTANT value +// MONTHLY v1 ... v12 +// TIMESERIES name +// TEMPERATURE +// FILE (v1 ... v12) +// RECOVERY name +// DRY_ONLY YES/NO +// +{ + int i, k; + double x; + + // --- find keyword indicating what form the evaporation data is in + k = findmatch(tok[0], EvapTypeWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); + + // --- check for RECOVERY pattern data + if ( k == RECOVERY ) + { + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + i = project_findObject(TIMEPATTERN, tok[1]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[1]); + Evap.recoveryPattern = i; + return 0; + } + + // --- check for no evaporation in wet periods + if ( k == DRYONLY ) + { + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + if ( strcomp(tok[1], w_NO ) ) Evap.dryOnly = FALSE; + else if ( strcomp(tok[1], w_YES ) ) Evap.dryOnly = TRUE; + else return error_setInpError(ERR_KEYWORD, tok[1]); + return 0; + } + + // --- process data depending on its form + Evap.type = k; + if ( k != TEMPERATURE_EVAP && ntoks < 2 ) + return error_setInpError(ERR_ITEMS, ""); + switch ( k ) + { + case CONSTANT_EVAP: + // --- for constant evap., fill monthly avg. values with same number + if ( !getDouble(tok[1], &x) ) + return error_setInpError(ERR_NUMBER, tok[1]); + for (i=0; i<12; i++) Evap.monthlyEvap[i] = x; + break; + + case MONTHLY_EVAP: + // --- for monthly evap., read a value for each month of year + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + for ( i=0; i<12; i++) + if ( !getDouble(tok[i+1], &Evap.monthlyEvap[i]) ) + return error_setInpError(ERR_NUMBER, tok[i+1]); + break; + + case TIMESERIES_EVAP: + // --- for time series evap., read name of time series + i = project_findObject(TSERIES, tok[1]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[1]); + Evap.tSeries = i; + Tseries[i].refersTo = TIMESERIES_EVAP; + break; + + case FILE_EVAP: + // --- for evap. from climate file, read monthly pan coeffs. + // if they are provided (default values are 1.0) + if ( ntoks > 1 ) + { + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + for (i=0; i<12; i++) + { + if ( !getDouble(tok[i+1], &Evap.panCoeff[i]) ) + return error_setInpError(ERR_NUMBER, tok[i+1]); + } + } + break; + } + return 0; +} + +//============================================================================= + +int climate_readAdjustments(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads adjustments to monthly evaporation or rainfall +// from input line of data. +// +// Data formats are: +// TEMPERATURE v1 ... v12 +// EVAPORATION v1 ... v12 +// RAINFALL v1 ... v12 +// CONDUCTIVITY v1 ... v12 +// N-PERV subcatchID patternID +// DSTORE subcatchID patternID +// INFIL subcatchID patternID +{ + int i, j; + + if (ntoks == 1) return 0; + + if ( match(tok[0], "TEMP") ) + { + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 1; i < 13; i++) + { + if ( !getDouble(tok[i], &Adjust.temp[i-1]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + return 0; + } + + if ( match(tok[0], "EVAP") ) + { + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 1; i < 13; i++) + { + if ( !getDouble(tok[i], &Adjust.evap[i-1]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + return 0; + } + + if ( match(tok[0], "RAIN") ) + { + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 1; i < 13; i++) + { + if ( !getDouble(tok[i], &Adjust.rain[i-1]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + return 0; + } + + if ( match(tok[0], "CONDUCT") ) + { + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 1; i < 13; i++) + { + if ( !getDouble(tok[i], &Adjust.hydcon[i-1]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + if ( Adjust.hydcon[i-1] <= 0.0 ) Adjust.hydcon[i-1] = 1.0; + } + return 0; + } + + if ( match(tok[0], "N-PERV") ) + { + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + i = project_findObject(SUBCATCH, tok[1]); + if (i < 0) return error_setInpError(ERR_NAME, tok[1]); + j = project_findObject(TIMEPATTERN, tok[2]); + if (j < 0) return error_setInpError(ERR_NAME, tok[2]); + Subcatch[i].nPervPattern = j; + return 0; + } + + if ( match(tok[0], "DSTORE") ) + { + if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); + i = project_findObject(SUBCATCH, tok[1]); + if (i < 0) return error_setInpError(ERR_NAME, tok[1]); + j = project_findObject(TIMEPATTERN, tok[2]); + if (j < 0) return error_setInpError(ERR_NAME, tok[2]); + Subcatch[i].dStorePattern = j; + return 0; + } + + if (match(tok[0], "INFIL")) + { + if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); + i = project_findObject(SUBCATCH, tok[1]); + if (i < 0) return error_setInpError(ERR_NAME, tok[1]); + j = project_findObject(TIMEPATTERN, tok[2]); + if (j < 0) return error_setInpError(ERR_NAME, tok[2]); + Subcatch[i].infilPattern = j; + return 0; + } + return error_setInpError(ERR_KEYWORD, tok[0]); +} + +//============================================================================= + +void climate_validate() +// +// Input: none +// Output: none +// Purpose: validates climatological variables +// +{ + int i; + double a, z, pa; + + // --- check if climate data comes from external data file + if ( Wind.type == FILE_WIND || Evap.type == FILE_EVAP || + Evap.type == TEMPERATURE_EVAP ) + { + if ( Fclimate.mode == NO_FILE ) + { + report_writeErrorMsg(ERR_NO_CLIMATE_FILE, ""); + } + } + + // --- open the climate data file + if ( Fclimate.mode == USE_FILE ) climate_openFile(); + + // --- snow melt parameters tipm & rnm must be fractions + if ( Snow.tipm < 0.0 || + Snow.tipm > 1.0 || + Snow.rnm < 0.0 || + Snow.rnm > 1.0 ) report_writeErrorMsg(ERR_SNOWMELT_PARAMS, ""); + + // --- latitude should be between -90 & 90 degrees + a = Temp.anglat; + if ( a <= -89.99 || + a >= 89.99 ) report_writeErrorMsg(ERR_SNOWMELT_PARAMS, ""); + else Temp.tanAnglat = tan(a * PI / 180.0); + + // --- compute psychrometric constant + z = Temp.elev / 1000.0; + if ( z <= 0.0 ) pa = 29.9; + else pa = 29.9 - 1.02*z + 0.0032*pow(z, 2.4); // atmos. pressure + Temp.gamma = 0.000359 * pa; + + // --- convert units of monthly temperature & evap adjustments + for (i = 0; i < 12; i++) + { + if (UnitSystem == SI) Adjust.temp[i] *= 9.0/5.0; + Adjust.evap[i] /= UCF(EVAPRATE); + } +} + +//============================================================================= + +void climate_openFile() +// +// Input: none +// Output: none +// Purpose: opens a climate file and reads in first set of values. +// +{ + int i, m, y; + + // --- open the file + if ( (Fclimate.file = fopen(Fclimate.name, "rt")) == NULL ) + { + report_writeErrorMsg(ERR_CLIMATE_FILE_OPEN, Fclimate.name); + return; + } + + // --- initialize values of file's climate variables + // (Temp.ta was previously initialized in project.c) + FileValue[TMIN] = Temp.ta; + FileValue[TMAX] = Temp.ta; + FileValue[EVAP] = 0.0; + FileValue[WIND] = 0.0; + + // --- find climate file's format + FileFormat = getFileFormat(); + if ( FileFormat == UNKNOWN_FORMAT ) + { + report_writeErrorMsg(ERR_CLIMATE_FILE_READ, Fclimate.name); + return; + } + + // --- position file to begin reading climate file at either user-specified + // month/year or at start of simulation period. + rewind(Fclimate.file); + sstrncpy(FileLine, "", 0); + if ( Temp.fileStartDate == NO_DATE ) + datetime_decodeDate(StartDate, &FileYear, &FileMonth, &FileDay); + else + datetime_decodeDate(Temp.fileStartDate, &FileYear, &FileMonth, &FileDay); + while ( !feof(Fclimate.file) ) + { + sstrncpy(FileLine, "", 0); + readFileLine(&y, &m); + if ( y == FileYear && m == FileMonth ) break; + } + if ( feof(Fclimate.file) ) + { + report_writeErrorMsg(ERR_CLIMATE_END_OF_FILE, Fclimate.name); + return; + } + + // --- initialize file dates and current climate variable values + if ( !ErrorCode ) + { + FileElapsedDays = 0; + FileLastDay = datetime_daysPerMonth(FileYear, FileMonth); + readFileValues(); + for (i=TMIN; i<=WIND; i++) + { + if ( FileData[i][FileDay] == MISSING ) continue; + FileValue[i] = FileData[i][FileDay]; + } + } +} + +//============================================================================= + +void climate_initState() +// +// Input: none +// Output: none +// Purpose: initializes climate state variables. +// +{ + LastDay = NO_DATE; + Temp.tmax = MISSING; + Snow.removed = 0.0; + NextEvapDate = StartDate; + NextEvapRate = 0.0; + + // --- initialize variables for time series evaporation + if ( Evap.type == TIMESERIES_EVAP && Evap.tSeries >= 0 ) + { + // --- initialize NextEvapDate & NextEvapRate to first entry of + // time series whose date <= the simulation start date + table_getFirstEntry(&Tseries[Evap.tSeries], + &NextEvapDate, &NextEvapRate); + if ( NextEvapDate < StartDate ) + { + setNextEvapDate(StartDate); + } + Evap.rate = NextEvapRate / UCF(EVAPRATE); + + // --- find the next time evaporation rates change after this + setNextEvapDate(NextEvapDate); + } + + // --- initialize variables for temperature evaporation + if ( Evap.type == TEMPERATURE_EVAP ) + { + Tma.maxCount = sizeof(Tma.ta) / sizeof(double); + Tma.count = 0; + Tma.front = 0; + Tma.tAve = 0.0; + Tma.tRng = 0.0; + } +} + +//============================================================================= + +void climate_setState(DateTime theDate) +// +// Input: theDate = simulation date +// Output: none +// Purpose: sets climate variables for current date. +// +{ + if ( Fclimate.mode == USE_FILE ) updateFileValues(theDate); + if ( Temp.dataSource != NO_TEMP ) setTemp(theDate); + setEvap(theDate); + setWind(theDate); + Adjust.rainFactor = Adjust.rain[datetime_monthOfYear(theDate)-1]; + Adjust.hydconFactor = Adjust.hydcon[datetime_monthOfYear(theDate)-1]; + setNextEvapDate(theDate); +} + +//============================================================================= + +DateTime climate_getNextEvapDate() +// +// Input: none +// Output: returns the current value of NextEvapDate +// Purpose: gets the next date when evaporation rate changes. +// +{ + return NextEvapDate; +} + +//============================================================================= + +void setNextEvapDate(DateTime theDate) +// +// Input: theDate = current simulation date +// Output: sets a new value for NextEvapDate +// Purpose: finds date for next change in evaporation after the current date. +// +{ + int yr, mon, day, k; + double d, e; + + // --- do nothing if current date hasn't reached the current next date + if ( NextEvapDate > theDate ) return; + + switch ( Evap.type ) + { + // --- for constant evaporation, use a next date far in the future + case CONSTANT_EVAP: + NextEvapDate = theDate + 365.; + break; + + // --- for monthly evaporation, use the start of the next month + case MONTHLY_EVAP: + datetime_decodeDate(theDate, &yr, &mon, &day); + if ( mon == 12 ) + { + mon = 1; + yr++; + } + else mon++; + NextEvapDate = datetime_encodeDate(yr, mon, 1); + break; + + // --- for time series evaporation, find the next entry in the + // series on or after the current date + case TIMESERIES_EVAP: + k = Evap.tSeries; + if ( k >= 0 ) + { + NextEvapDate = theDate + 365.; + while ( table_getNextEntry(&Tseries[k], &d, &e) && + d <= EndDateTime ) + { + if ( d >= theDate ) + { + NextEvapDate = d; + NextEvapRate = e; + break; + } + } + } + break; + + // --- for climate file daily evaporation, use the next day + case FILE_EVAP: + NextEvapDate = floor(theDate) + 1.0; + break; + + default: NextEvapDate = theDate + 365.; + } +} + +//============================================================================= + +void updateFileValues(DateTime theDate) +// +// Input: theDate = current simulation date +// Output: none +// Purpose: updates daily climate variables for new day or reads in +// another month worth of values if a new month begins. +// +// NOTE: counters FileElapsedDays, FileDay, FileMonth, FileYear and +// FileLastDay were initialized in climate_openFile(). +// +{ + int i; + int deltaDays; + + // --- see if a new day has begun + deltaDays = (int)(floor(theDate) - floor(StartDateTime)); + if ( deltaDays > FileElapsedDays ) + { + // --- advance day counters + FileElapsedDays++; + FileDay++; + + // --- see if new month of data needs to be read from file + if ( FileDay > FileLastDay ) + { + FileMonth++; + if ( FileMonth > 12 ) + { + FileMonth = 1; + FileYear++; + } + readFileValues(); + FileDay = 1; + FileLastDay = datetime_daysPerMonth(FileYear, FileMonth); + } + + // --- set climate variables for new day + for (i=TMIN; i<=WIND; i++) + { + // --- no change in current value if its missing + if ( FileData[i][FileDay] == MISSING ) continue; + FileValue[i] = FileData[i][FileDay]; + } + } +} + +//============================================================================= + +void setTemp(DateTime theDate) +// +// Input: theDate = simulation date +// Output: none +// Purpose: updates temperatures for new simulation date. +// +{ + int j; // snow data object index + int k; // time series index + int mon; // month of year + int day; // day of year + DateTime theDay; // calendar day + double hour; // hour of day + double tmp; // temporary temperature + + // --- see if a new day has started + mon = datetime_monthOfYear(theDate); + theDay = floor(theDate); + if ( theDay > LastDay ) + { + // --- update min. & max. temps & their time of day + day = datetime_dayOfYear(theDate); + if ( Temp.dataSource == FILE_TEMP ) + { + Tmin = FileValue[TMIN] + Adjust.temp[mon-1]; + Tmax = FileValue[TMAX] + Adjust.temp[mon-1]; + if ( Tmin > Tmax ) + { + tmp = Tmin; + Tmin = Tmax; + Tmax = tmp; + } + updateTempTimes(day); + if ( Evap.type == TEMPERATURE_EVAP ) + { + updateTempMoveAve(Tmin, Tmax); + FileValue[EVAP] = getTempEvap(day, Tma.tAve, Tma.tRng); + } + } + + // --- compute snow melt coefficients based on day of year + Snow.season = sin(0.0172615*(day-81.0)); + for (j=0; j= Hrsr && hour <= Hrss ) + Temp.ta = Tave + Trng * sin(PI/Dhrdy * (Hrday - hour)); + else + Temp.ta = Tmax - Trng * sin(PI/Dydif * (hour - Hrss)); + } + + // --- for user-supplied temperature time series, + // get temperature value from time series + if ( Temp.dataSource == TSERIES_TEMP ) + { + k = Temp.tSeries; + if ( k >= 0) + { + Temp.ta = table_tseriesLookup(&Tseries[k], theDate, TRUE); + + // --- convert from deg. C to deg. F if need be + if ( UnitSystem == SI ) + { + Temp.ta = (9./5.) * Temp.ta + 32.0; + } + + // --- apply climate change adjustment factor + Temp.ta += Adjust.temp[mon-1]; + } + } + + // --- compute saturation vapor pressure + Temp.ea = 8.1175e6 * exp(-7701.544 / (Temp.ta + 405.0265) ); +} + +//============================================================================= + +void setEvap(DateTime theDate) +// +// Input: theDate = simulation date +// Output: none +// Purpose: sets evaporation rate (ft/sec) for a specified date. +// +{ + int k; + int mon = datetime_monthOfYear(theDate); + + switch ( Evap.type ) + { + case CONSTANT_EVAP: + Evap.rate = Evap.monthlyEvap[0] / UCF(EVAPRATE); + break; + + case MONTHLY_EVAP: + Evap.rate = Evap.monthlyEvap[mon-1] / UCF(EVAPRATE); + break; + + case TIMESERIES_EVAP: + if ( theDate >= NextEvapDate ) + Evap.rate = NextEvapRate / UCF(EVAPRATE); + break; + + case FILE_EVAP: + Evap.rate = FileValue[EVAP] / UCF(EVAPRATE); + Evap.rate *= Evap.panCoeff[mon-1]; + break; + + case TEMPERATURE_EVAP: + Evap.rate = FileValue[EVAP] / UCF(EVAPRATE); + break; + + default: Evap.rate = 0.0; + } + + // --- apply climate change adjustment + Evap.rate += Adjust.evap[mon-1]; + + // --- set soil recovery factor + Evap.recoveryFactor = 1.0; + k = Evap.recoveryPattern; + if ( k >= 0 && Pattern[k].type == MONTHLY_PATTERN ) + { + Evap.recoveryFactor = Pattern[k].factor[mon-1]; + } +} + +//============================================================================= + +void setWind(DateTime theDate) +// +// Input: theDate = simulation date +// Output: none +// Purpose: sets wind speed (mph) for a specified date. +// +{ + int yr, mon, day; + + switch ( Wind.type ) + { + case MONTHLY_WIND: + datetime_decodeDate(theDate, &yr, &mon, &day); + Wind.ws = Wind.aws[mon-1] / UCF(WINDSPEED); + break; + + case FILE_WIND: + Wind.ws = FileValue[WIND]; + break; + + default: Wind.ws = 0.0; + } +} + +//============================================================================= + +void updateTempTimes(int day) +// +// Input: day = day of year +// Output: none +// Purpose: computes time of day when min/max temperatures occur. +// (min. temp occurs at sunrise, max. temp. at 3 hrs. < sunset) +// +{ + double decl; // earth's declination + double hrang; // hour angle of sunrise/sunset + double arg; + + decl = 0.40928*cos(0.017202*(172.0-day)); + arg = -tan(decl)*Temp.tanAnglat; + if ( arg <= -1.0 ) arg = PI; + else if ( arg >= 1.0 ) arg = 0.0; + else arg = acos(arg); + hrang = 3.8197 * arg; + Hrsr = 12.0 - hrang + Temp.dtlong; + Hrss = 12.0 + hrang + Temp.dtlong - 3.0; + Dhrdy = Hrsr - Hrss; + Dydif = 24.0 + Hrsr - Hrss; + Hrday = (Hrsr + Hrss) / 2.0; + Tave = (Tmin + Tmax) / 2.0; + Trng = (Tmax - Tmin) / 2.0; + if ( Temp.tmax == MISSING ) Trng1 = Tmax - Tmin; + else Trng1 = Temp.tmax - Tmin; + Temp.tmax = Tmax; +} + +//============================================================================= + +double getTempEvap(int day, double tave, double trng) +// +// Input: day = day of year +// tave = 7-day average temperature (deg F) +// trng = 7-day average daily temperature range (deg F) +// Output: returns evaporation rate in user's units (US:in/day, SI:mm/day) +// Purpose: uses Hargreaves method to compute daily evaporation rate +// from daily average temperatures and Julian day. +// +{ + double a = 2.0*PI/365.0; + double ta = (tave - 32.0)*5.0/9.0; //average temperature (deg C) + double tr = trng*5.0/9.0; //temperature range (deg C) + double lamda = 2.50 - 0.002361 * ta; //latent heat of vaporization + double dr = 1.0 + 0.033*cos(a*day); //relative earth-sun distance + double phi = Temp.anglat*2.0*PI/360.0; //latitude angle (rad) + double del = 0.4093*sin(a*(284.+(double)day)); //solar declination angle (rad) + double omega = acos(-tan(phi)*tan(del)); //sunset hour angle (rad) + double ra = 37.6*dr* //extraterrestrial radiation + (omega*sin(phi)*sin(del) + + cos(phi)*cos(del)*sin(omega)); + double e = 0.0023*ra/lamda*sqrt(tr)*(ta+17.8); //evap. rate (mm/day) + if ( e < 0.0 ) e = 0.0; + if ( UnitSystem == US ) e /= MMperINCH; //evap rate (in/day) + return e; +} + +//============================================================================= + +int getFileFormat() +// +// Input: none +// Output: returns code number of climate file's format +// Purpose: determines what format the climate file is in. +// +{ + char recdType[4] = ""; + char elemType[4] = ""; + char filler[5] = ""; + char staID[80]; + char s[80]; + char line[MAXLINE]; + + int y, m, d, n; + + // --- read first line of file + if ( fgets(line, MAXLINE, Fclimate.file) == NULL ) return UNKNOWN_FORMAT; + + // --- check for TD3200 format + sstrncpy(recdType, line, 3); + sstrncpy(filler, &line[23], 4); + if ( strcmp(recdType, "DLY") == 0 && + strcmp(filler, "9999") == 0 ) return TD3200; + + // --- check for DLY0204 format + if ( strlen(line) >= 233 ) + { + sstrncpy(elemType, &line[13], 3); + n = atoi(elemType); + if ( n == 1 || n == 2 || n == 151 ) return DLY0204; + } + + // --- check for USER_PREPARED format + n = sscanf(line, "%s %d %d %d %s", staID, &y, &m, &d, s); + if ( n == 5 ) return USER_PREPARED; + + // --- check for GHCND format + if ( isGhcndFormat(line) ) return GHCND; + + return UNKNOWN_FORMAT; +} + +//============================================================================= + +void readFileLine(int *y, int *m) +// +// Input: none +// Output: y = year +// m = month +// Purpose: reads year & month from next line of climate file. +// +{ + // --- read next line from climate data file + while ( strlen(FileLine) == 0 ) + { + if ( fgets(FileLine, MAXLINE, Fclimate.file) == NULL ) return; + if ( FileLine[0] == '\n' ) FileLine[0] = '\0'; + } + + // --- parse year & month from line + switch (FileFormat) + { + case USER_PREPARED: readUserFileLine(y, m); break; + case TD3200: readTD3200FileLine(y,m); break; + case DLY0204: readDLY0204FileLine(y,m); break; + case GHCND: readGhcndFileLine(y,m); break; + } +} + +//============================================================================= + +void readUserFileLine(int* y, int* m) +// +// Input: none +// Output: y = year +// m = month +// Purpose: reads year & month from line of User-Prepared climate file. +// +{ + int n; + char staID[80]; + n = sscanf(FileLine, "%s %d %d", staID, y, m); + if ( n < 3 ) + { + report_writeErrorMsg(ERR_CLIMATE_FILE_READ, Fclimate.name); + } +} + +//============================================================================= + +void readTD3200FileLine(int* y, int* m) +// +// Input: none +// Output: y = year +// m = month +// Purpose: reads year & month from line of TD-3200 climate file. +// +{ + char recdType[4] = ""; + char year[5] = ""; + char month[3] = ""; + + // --- check for minimum number of characters + if ( strlen(FileLine) < 30 ) + { + report_writeErrorMsg(ERR_CLIMATE_FILE_READ, Fclimate.name); + return; + } + + // --- check for proper type of record + sstrncpy(recdType, FileLine, 3); + if ( strcmp(recdType, "DLY") != 0 ) + { + report_writeErrorMsg(ERR_CLIMATE_FILE_READ, Fclimate.name); + return; + } + + // --- get record's date + sstrncpy(year, &FileLine[17], 4); + sstrncpy(month, &FileLine[21], 2); + *y = atoi(year); + *m = atoi(month); +} + +//============================================================================= + +void readDLY0204FileLine(int* y, int* m) +// +// Input: none +// Output: y = year +// m = month +// Purpose: reads year & month from line of DLY02 or DLY04 climate file. +// +{ + char year[5] = ""; + char month[3] = ""; + + // --- check for minimum number of characters + if ( strlen(FileLine) < 16 ) + { + report_writeErrorMsg(ERR_CLIMATE_FILE_READ, Fclimate.name); + return; + } + + // --- get record's date + sstrncpy(year, &FileLine[7], 4); + sstrncpy(month, &FileLine[11], 2); + *y = atoi(year); + *m = atoi(month); +} + +//============================================================================= + +void readFileValues() +// +// Input: none +// Output: none +// Purpose: reads next month's worth of data from climate file. +// +{ + int i, j; + int y, m; + + // --- initialize FileData array to missing values + for ( i=0; i FileYear || m > FileMonth ) return; + + // --- parse climate values from file line + switch (FileFormat) + { + case USER_PREPARED: parseUserFileLine(); break; + case TD3200: parseTD3200FileLine(); break; + case DLY0204: parseDLY0204FileLine(); break; + case GHCND: parseGhcndFileLine(); break; + } + sstrncpy(FileLine, "", 0); + } +} + +//============================================================================= + +void parseUserFileLine() +// +// Input: none +// Output: none +// Purpose: parses climate variable values from a line of a user-prepared +// climate file. +// +{ + int n; + int y, m, d; + char staID[80]; + char s0[80] = ""; + char s1[80] = ""; + char s2[80] = ""; + char s3[80] = ""; + double x; + + // --- read day, Tmax, Tmin, Evap, & Wind from file line + n = sscanf(FileLine, "%s %d %d %d %s %s %s %s", + staID, &y, &m, &d, s0, s1, s2, s3); + if ( n < 4 ) return; + if ( d < 1 || d > 31 ) return; + + // --- process TMAX + if ( strlen(s0) > 0 && *s0 != '*' ) + { + x = atof(s0); + if ( UnitSystem == SI ) x = 9./5.*x + 32.0; + FileData[TMAX][d] = x; + } + + // --- process TMIN + if ( strlen(s1) > 0 && *s1 != '*' ) + { + x = atof(s1); + if ( UnitSystem == SI ) x = 9./5.*x + 32.0; + FileData[TMIN][d] = x; + } + + // --- process EVAP + if ( strlen(s2) > 0 && *s2 != '*' ) FileData[EVAP][d] = atof(s2); + + // --- process WIND + if ( strlen(s3) > 0 && *s3 != '*' ) FileData[WIND][d] = atof(s3); +} + +//============================================================================= + +void parseTD3200FileLine() +// +// Input: none +// Output: none +// Purpose: parses climate variable values from a line of a TD3200 file. +// +{ + int i; + char param[5] = ""; + + // --- parse parameter name + sstrncpy(param, &FileLine[11], 4); + + // --- see if parameter is temperature, evaporation or wind speed + for (i=0; i= 12*nValues + 30 ) + { + // --- for each day's value + for (j=0; j 0 + && d <= 31 ) + { + // --- convert from string value to numerical value + x = atof(value); + if ( sign[0] == '-' ) x = -x; + + // --- convert evaporation from hundreths of inches + if ( i == EVAP ) + { + x /= 100.0; + + // --- convert to mm if using SI units + if ( UnitSystem == SI ) x *= MMperINCH; + } + + // --- convert wind speed from miles/day to miles/hour + if ( i == WIND ) x /= 24.0; + + // --- store value + FileData[i][d] = x; + } + } + } +} + +//============================================================================= + +void parseDLY0204FileLine() +// +// Input: none +// Output: none +// Purpose: parses a month's worth of climate variable values from a line of +// a DLY02 or DLY04 climate file. +// +{ + int j, k, p; + char param[4] = ""; + char sign[2] = ""; + char value[6] = ""; + char code[2] = ""; + double x; + + // --- parse parameter name + sstrncpy(param, &FileLine[13], 3); + + // --- see if parameter is min or max temperature + p = atoi(param); + if ( p == 1 ) p = TMAX; + else if ( p == 2 ) p = TMIN; + else if ( p == 151 ) p = EVAP; + else return; + + // --- check for 233 characters on line + if ( strlen(FileLine) < 233 ) return; + + // --- for each of 31 days + k = 16; + for (j=1; j<=31; j++) + { + // --- parse value & flag from file line + sstrncpy(sign, &FileLine[k], 1); + sstrncpy(value, &FileLine[k+1], 5); + sstrncpy(code, &FileLine[k+6], 1); + k += 7; + + // --- if value is valid then store it in FileData array + + if ( strcmp(value, "99999") != 0 && strcmp(value, " ") != 0 ) + { + switch (p) + { + case TMAX: + case TMIN: + // --- convert from integer tenths of a degree C to degrees F + x = atof(value) / 10.0; + if ( sign[0] == '-' ) x = -x; + x = 9./5.*x + 32.0; + break; + case EVAP: + // --- convert from 0.1 mm to inches or mm + x = atof(value) / 10.0; + if ( UnitSystem == US ) x /= MMperINCH; + break; + default: return; + } + FileData[p][j] = x; + } + } +} + +//============================================================================= + +int isGhcndFormat(char* line) +// +// Input: line = first line of text from a climate file +// Output: returns TRUE if climate file is in NCDC GHCN Daily format. +// Purpose: Checks if a climate file is in the NCDC GHCN Daily format +// and determines the position of each climate variable field. +// +{ + int i; + char* ptr; + + // --- find starting position of the DATE field + ptr = strstr(line, "DATE"); + if ( ptr == NULL ) return FALSE; + FileDateFieldPos = (int)(ptr - line); + + // --- initialize starting position of each data field + for ( i = TMIN; i <= WIND; i++) FileFieldPos[i] = -1; + + // --- find starting position of each climate variable's data field + ptr = strstr(line, "TMIN"); + if ( ptr ) FileFieldPos[TMIN] = (int)(ptr - line); + ptr = strstr(line, "TMAX"); + if ( ptr ) FileFieldPos[TMAX] = (int)(ptr - line); + ptr = strstr(line, "EVAP"); + if ( ptr ) FileFieldPos[EVAP] = (int)(ptr - line); + + // --- WIND can either be daily movement or average speed + FileWindType = WDMV; + ptr = strstr(line, "WDMV"); + if ( ptr == NULL ) + { + FileWindType = AWND; + ptr = strstr(line, "AWND"); + } + if ( ptr ) FileFieldPos[WIND] = (int)(ptr - line); + + // --- check if at least one climate variable was found + for (i = TMIN; i <= WIND; i++) if (FileFieldPos[i] >= 0 ) return TRUE; + return FALSE; +} + +//============================================================================= + +void readGhcndFileLine(int* y, int* m) +// +// Input: none +// Output: y = year +// m = month +// Purpose: reads year & month from line of a NCDC GHCN Daily climate file. +// +{ + int n = sscanf(&FileLine[FileDateFieldPos], "%4d%2d", y, m); + if ( n != 2 ) + { + *y = -99999; + *m = -99999; + } +} + +//============================================================================= + +void parseGhcndFileLine() +// +// Input: none +// Output: none +// Purpose: parses a line of a NCDC GHCN Daily file for daily +// values of max/min temperature, pan evaporation and +// wind speed. +// +{ + int y, m, d, n, i; + double v; + + // --- parse day of month from date field + n = sscanf(&FileLine[FileDateFieldPos], "%4d%2d%2d", &y, &m, &d); + if ( n < 3 ) return; + if ( d < 1 || d > 31 ) return; + + // --- parse climate variables + for (i = TMIN; i <= WIND; i++) + { + if ( FileFieldPos[i] >= 0 ) + { + if ( sscanf(&FileLine[FileFieldPos[i]], "%8lf", &v) > 0 ) + { + if ( fabs(v) < 9999. ) + FileData[i][d] = convertGhcndValue(i, v); + } + } + } +} + +//============================================================================= + +double convertGhcndValue(int var, double v) +// +// Input: var = climate variable code +// v = climate variable value +// Output: climate variable value in SWMM's internal units +// Purpose: converts a climate variable value read from a NCDC GHCN Daily file +// to SWMM's internal units. +// +{ + switch (var) + { + case TMIN: + case TMAX: + switch (FileTempUnits) + { + case DEG_C10: // tenths deg. C ==> deg. F + return v / 10. * 9.0 / 5.0 + 32.0; + + case DEG_C: // deg. C ==> deg. F + return v * 9.0 / 5.0 + 32.0; + + default: // deg. F + return v; + } + case EVAP: + switch (FileTempUnits) + { + case DEG_C10: // tenths mm ==> inches or mm + v /= 10.; + if (UnitSystem == US) v /= MMperINCH; + return v; + + case DEG_C: // mm ==> inches or mm + if (UnitSystem == US) v /= MMperINCH; + return v; + + default: // inches ==> inches or mm + if (UnitSystem == SI) v *= MMperINCH; + return v; + } + case WIND: + switch (FileTempUnits) + { + case DEG_C10: + // km/day ==> miles/hr + if (FileWindType == WDMV) + return v * 0.62137 / 24.; + // tenths m/s ==> miles/hr + else + return v / 10. / 1000. * 0.62137 * 3600.; + case DEG_C: + // km/day ==> miles/hr + if (FileWindType == WDMV) + return v * 0.62137 / 24.; + // m/s ==> miles/hr + else + return v / 1000. * 0.62137 * 3600.; + + default: + // miles ==> miles/hr + if (FileWindType == WDMV) + return v / 24.; + // miles/hr + else + return v; + } + default: + return v; + } +} + +//============================================================================= + +void updateTempMoveAve(double tmin, double tmax) +// +// Input: tmin = minimum daily temperature (deg F) +// tmax = maximum daily temperature (deg F) +// Output: none +// Purpose: updates moving averages of average daily temperature +// and daily temperature range stored in structure Tma. +// +{ + double ta, // new day's average temperature (deg F) + tr; // new day's temperature range (deg F) + int kount = Tma.count; + double count = kount; + + // --- find ta and tr from new day's min and max temperature + ta = (tmin + tmax) / 2.0; + tr = fabs(tmax - tmin); + + // --- if the array used to store previous days' temperatures is full + if ( kount == Tma.maxCount ) + { + // --- update the moving averages with the new day's value + Tma.tAve = (Tma.tAve * count + ta - Tma.ta[Tma.front]) / count; + Tma.tRng = (Tma.tRng * count + tr - Tma.tr[Tma.front]) / count; + + // --- replace the values at the front of the moving average window + Tma.ta[Tma.front] = ta; + Tma.tr[Tma.front] = tr; + + // --- move the front one position forward + Tma.front++; + if ( Tma.front == count ) Tma.front = 0; + } + + // --- array of previous day's values not full (at start of simulation) + else + { + // --- find new moving averages by adding new values to previous ones + Tma.tAve = (Tma.tAve * count + ta) / (count + 1); + Tma.tRng = (Tma.tRng * count + tr) / (count + 1); + + // --- save new day's values + Tma.ta[Tma.front] = ta; + Tma.tr[Tma.front] = tr; + + // --- increment count and front of moving average window + Tma.count++; + Tma.front++; + if ( Tma.count == Tma.maxCount ) Tma.front = 0; + } +} diff --git a/src/consts.h b/src/consts.h new file mode 100644 index 000000000..f3deb70ef --- /dev/null +++ b/src/consts.h @@ -0,0 +1,102 @@ +//----------------------------------------------------------------------------- +// consts.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 10/18/22 (Build 5.2.2) +// Author: L. Rossman +// +// Various Constants +//----------------------------------------------------------------------------- + +#ifndef CONSTS_H +#define CONSTS_H + +//------------------ +// General Constants +//------------------ + +#define VERSION 52002 +#define MAGICNUMBER 516114522 +#define EOFMARK 0x1A // Use 0x04 for UNIX systems +#define MAXTITLE 3 // Max. # title lines +#define MAXMSG 1024 // Max. # characters in message text +#define MAXLINE 1024 // Max. # characters per input line +#define MAXFNAME 259 // Max. # characters in file name +#define MAXTOKS 40 // Max. items per line of input +#define MAXSTATES 10 // Max. # computed hyd. variables +#define MAXODES 4 // Max. # ODE's to be solved +#define NA -1 // NOT APPLICABLE code +#define TRUE 1 // Value for TRUE state +#define FALSE 0 // Value for FALSE state +#define BIG 1.E10 // Generic large value +#define TINY 1.E-6 // Generic small value +#define ZERO 1.E-10 // Effective zero value +#define MISSING -1.E10 // Missing value code +#define PI 3.141592654 // Value of pi +#define GRAVITY 32.2 // accel. of gravity in US units +#define SI_GRAVITY 9.81 // accel of gravity in SI units +/* DEPRECATED +#define MAXFILESIZE 2147483647L // largest file size in bytes +*/ + +//----------------------------- +// Units factor in Manning Eqn. +//----------------------------- +#define PHI 1.486 + +//---------------------------------------------- +// Definition of measureable runoff flow & depth +//---------------------------------------------- +#define MIN_RUNOFF_FLOW 0.001 // cfs +#define MIN_EXCESS_DEPTH 0.0001 // ft, = 0.03 mm +#define MIN_TOTAL_DEPTH 0.004167 // ft, = 0.05 inches +#define MIN_RUNOFF 2.31481e-8 // ft/sec = 0.001 in/hr + +//---------------------------------------------------------------------- +// Minimum flow, depth & volume used to evaluate steady state conditions +//---------------------------------------------------------------------- +#define FLOW_TOL 0.00001 // cfs +#define DEPTH_TOL 0.00001 // ft +#define VOLUME_TOL 0.01 // ft3 + +//--------------------------------------------------- +// Minimum depth for reporting non-zero water quality +//--------------------------------------------------- +//#define MIN_WQ_DEPTH 0.01 // ft (= 3 mm) +//#define MIN_WQ_FLOW 0.001 // cfs + +//----------------------------------------------------- +// Minimum flow depth and area for dynamic wave routing +//----------------------------------------------------- +#define FUDGE 0.0001 // ft or ft2 + +//--------------------------- +// Various conversion factors +//--------------------------- +#define GPMperCFS 448.831 +#define AFDperCFS 1.9837 +#define MGDperCFS 0.64632 +#define IMGDperCFS 0.5382 +#define LPSperCFS 28.317 +#define LPMperCFS 1699.0 +#define CMHperCFS 101.94 +#define CMDperCFS 2446.6 +#define MLDperCFS 2.4466 +#define M3perFT3 0.028317 +#define LperFT3 28.317 +#define MperFT 0.3048 +#define PSIperFT 0.4333 +#define KPAperPSI 6.895 +#define KWperHP 0.7457 +#define SECperDAY 86400 +#define MSECperDAY 8.64e7 +#define MMperINCH 25.40 + +//--------------------------- +// Token separator characters +//--------------------------- +#define SEPSTR " \t\n\r" + + +#endif //CONSTS_H diff --git a/src/controls.c b/src/controls.c new file mode 100644 index 000000000..b46054006 --- /dev/null +++ b/src/controls.c @@ -0,0 +1,1553 @@ +//----------------------------------------------------------------------------- +// controls.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 06/01/22 (Build 5.2.1) +// Author: L. Rossman +// +// Rule-based controls functions. +// +// Control rules have the format: +// RULE name +// IF +// AND / OR +// etc. +// THEN +// AND +// etc. +// ELSE +// AND +// etc. +// PRIORITY

+// +// consists of: +// value / +// where is +// E.g.: Node 123 Depth > 4.5 +// Node 456 Depth < Node 123 Depth +// +// consists of: +// = setting +// E.g.: Pump abc status = OFF +// Weir xyz setting = 0.5 +// +// Update History +// ============== +// Build 5.1.008: +// - Support added for r.h.s. variables in rule premises. +// - Node volume added as a premise variable. +// Build 5.1.009: +// - Fixed problem with parsing a RHS premise variable. +// Build 5.1.010: +// - Support added for link TIMEOPEN & TIMECLOSED premises. +// Build 5.1.011: +// - Support added for DAYOFYEAR attribute. +// - Modulated controls no longer included in reported control actions. +// Build 5.2.0: +// - Additional attributes added to condition clauses. +// - Support added for named variables in condition clauses. +// - Support added for math expressions in condition clauses. +// Build 5.2.1: +// - A refactoring bug from 5.2.0 causing duplicate actions to be added +// to the list of control actions to take was fixed. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +enum RuleState {r_RULE, r_IF, r_AND, r_OR, r_THEN, r_ELSE, r_PRIORITY, + r_VARIABLE, r_EXPRESSION, r_ERROR}; +enum RuleObject {r_GAGE, r_NODE, r_LINK, r_CONDUIT, r_PUMP, r_ORIFICE, + r_WEIR, r_OUTLET, r_SIMULATION}; +enum RuleAttrib {r_DEPTH, r_MAXDEPTH, r_HEAD, r_VOLUME, r_INFLOW, + r_FLOW, r_FULLFLOW, r_FULLDEPTH, r_STATUS, r_SETTING, + r_LENGTH, r_SLOPE, r_VELOCITY, r_TIMEOPEN, r_TIMECLOSED, + r_TIME, r_DATE, r_CLOCKTIME, r_DAYOFYEAR, r_DAY, r_MONTH}; +enum RuleRelation {EQ, NE, LT, LE, GT, GE}; +enum RuleSetting {r_CURVE, r_TIMESERIES, r_PID, r_NUMERIC}; + +#define MAXVARNAME 32 + +static char* ObjectWords[] = + {"GAGE", "NODE", "LINK", "CONDUIT", "PUMP", "ORIFICE", "WEIR", "OUTLET", + "SIMULATION", NULL}; +static char* AttribWords[] = + {"DEPTH", "MAXDEPTH", "HEAD", "VOLUME", "INFLOW", + "FLOW", "FULLFLOW", "FULLDEPTH", "STATUS", "SETTING", + "LENGTH", "SLOPE", "VELOCITY", "TIMEOPEN", "TIMECLOSED", + "TIME", "DATE", "CLOCKTIME", "DAYOFYEAR", "DAY", "MONTH", NULL}; +static char* RelOpWords[] = {"=", "<>", "<", "<=", ">", ">=", NULL}; +static char* StatusWords[] = {"OFF", "ON", NULL}; +static char* ConduitWords[] = {"CLOSED", "OPEN", NULL}; +static char* SettingTypeWords[] = {"CURVE", "TIMESERIES", "PID", NULL}; +static char* IntensityWord = "INTENSITY"; + +//----------------------------------------------------------------------------- +// Data Structures +//----------------------------------------------------------------------------- +// Rule Premise Variable +struct TVariable +{ + int object; // type of object + int index; // index in object's array + int attribute; // object's attribute +}; + +// Named Variable +struct TNamedVariable +{ + struct TVariable variable; // a rule premise variable + char name[MAXVARNAME+1]; // name used in math expression +}; + +// Rule Premise Function +struct TExpression +{ + MathExpr* expression; // tokenized math expression + char name[MAXVARNAME+1]; // expression name +}; + +// Rule Premise Clause +struct TPremise +{ + int type; // clause type (IF/AND/OR) + int exprIndex; // expression index (-1 if N/A) + struct TVariable lhsVar; // left hand side variable + struct TVariable rhsVar; // right hand side variable + int relation; // relational operator (>, <, =, etc) + double value; // right hand side value + struct TPremise *next; // next premise clause of rule +}; + +// Rule Action Clause +struct TAction +{ + int rule; // index of rule that action belongs to + int link; // index of link being controlled + int attribute; // attribute of link being controlled + int curve; // index of curve for modulated control + int tseries; // index of time series for modulated control + double value; // control setting for link attribute + double kp, ki, kd; // coeffs. for PID modulated control + double e1, e2; // PID set point error from previous time steps + struct TAction *next; // next action clause of rule +}; + +// List of Control Actions +struct TActionList +{ + struct TAction* action; + struct TActionList* next; +}; + +// Control Rule +struct TRule +{ + char* ID; // rule ID + double priority; // priority level + struct TPremise* firstPremise; // pointer to first premise of rule + struct TPremise* lastPremise; // pointer to last premise of rule + struct TAction* thenActions; // linked list of actions if true + struct TAction* elseActions; // linked list of actions if false +}; + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +struct TRule* Rules; // array of control rules +struct TActionList* ActionList; // linked list of control actions +int InputState; // state of rule interpreter +int RuleCount; // total number of rules +double ControlValue; // value of controller variable +double SetPoint; // value of controller setpoint +DateTime CurrentDate; // current date in whole days +DateTime CurrentTime; // current time of day (decimal) + +int VariableCount; +int ExpressionCount; +int CurrentVariable; +int CurrentExpression; +struct TNamedVariable* NamedVariable; // array of named variables +struct TExpression* Expression; // array of math expressions + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// controls_create +// controls_delete +// controls_init +// controls_addToCount +// controls_addVariable +// controls_addExpression +// controls_addRuleClause +// controls_evaluate + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +int addPremise(int r, int type, char* Tok[], int nToks); +int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v); +int getPremiseValue(char* token, int attrib, double* value); +int addAction(int r, char* Tok[], int nToks); + +int evaluatePremise(struct TPremise* p, double tStep); +double getVariableValue(struct TVariable v); +int compareTimes(double lhsValue, int relation, double rhsValue, + double halfStep); +int compareValues(double lhsValue, int relation, double rhsValue); + +void updateActionList(struct TAction* a); +int executeActionList(DateTime currentTime); +void clearActionList(void); +void deleteActionList(void); +void deleteRules(void); + +int findExactMatch(char *s, char *keyword[]); +int setActionSetting(char* tok[], int nToks, int* curve, int* tseries, + int* attrib, double value[]); +void updateActionValue(struct TAction* a, DateTime currentTime, double dt); +double getPIDSetting(struct TAction* a, double dt); + +int getVariableIndex(char* varName); +double getNamedVariableValue(int varIndex); +int getExpressionIndex(char* exprName); +int getGageAttrib(char* token); +double getRainValue(struct TVariable v); + +//============================================================================= + +void controls_init() +// +// Input: none +// Output: none +// Purpose: initializes the control rule system. +// +{ + Rules = NULL; + NamedVariable = NULL; + Expression = NULL; + RuleCount = 0; + VariableCount = 0; + ExpressionCount = 0; +} + +//============================================================================= + +void controls_addToCount(char* s) +// +// Input: s = either VARIABLE or EXPRESSION +// Output: none +// Purpose: updates the number of named variables or math expressions used +// by control rules. +// +{ + if (match(s, w_VARIABLE)) VariableCount++; + else if (match(s, w_EXPRESSION)) ExpressionCount++; +} + +//============================================================================= + +int controls_create(int n) +// +// Input: n = total number of control rules +// Output: returns error code +// Purpose: creates an array of control rules. +// +{ + int r; + ActionList = NULL; + InputState = r_PRIORITY; + RuleCount = n; + if (RuleCount > 0) + { + Rules = (struct TRule *) calloc(RuleCount, sizeof(struct TRule)); + if (Rules == NULL) return ERR_MEMORY; + for ( r=0; r 0) + { + NamedVariable = (struct TNamedVariable *) calloc(VariableCount, + sizeof(struct TNamedVariable)); + if (NamedVariable == NULL) return ERR_MEMORY; + } + if (ExpressionCount > 0) + { + Expression = (struct TExpression *) calloc(ExpressionCount, + sizeof(struct TExpression)); + if (Expression == NULL) return ERR_MEMORY; + } + return 0; +} + +//============================================================================= + +void controls_delete(void) +// +// Input: none +// Output: none +// Purpose: deletes all control rules. +// +{ + int i; + + for (i = 0; i < ExpressionCount; i++) + { + mathexpr_delete(Expression[i].expression); + Expression[i].expression = NULL; + } + FREE(Expression); + FREE(NamedVariable); + + if ( RuleCount == 0 ) return; + deleteActionList(); + deleteRules(); +} + +//============================================================================= + +int controls_addVariable(char* tok[], int nToks) +// +// Input: tok = an array of string tokens +// n = the size of tok[] +// Output: returns error code +// Purpose: adds a named variable to the control rule system from a +// tokenized line of input with formats: +// VARIABLE name = Object id attribute +// VARIABLE name = SIMULATION attribute +// +{ + struct TVariable v1; + int k, err; + + CurrentVariable++; + if (nToks < 5) return ERR_ITEMS; + if (findExactMatch(tok[1], AttribWords) >= 0) + return error_setInpError(ERR_KEYWORD, tok[1]); + if (!match(tok[2], "=")) return error_setInpError(ERR_KEYWORD, tok[2]); + if (!match(tok[3], "SIMULATION") && nToks < 6) return ERR_ITEMS; + k = 3; + err = getPremiseVariable(tok, nToks, &k, &v1); + if (err > 0) return err; + k = CurrentVariable; + NamedVariable[k].variable = v1; + sstrncpy(NamedVariable[k].name, tok[1], MAXVARNAME); + return 0; +} + +//============================================================================= + +int controls_addExpression(char* tok[], int nToks) +// +// Input: tok = an array of string tokens +// n = number of tokens +// Output: returns error code +// Purpose: adds a math expression to the control rule system from a +// a tokenized line of input with format: +// EXPRESSION name = +// +{ + int i, k; + char s[MAXLINE + 1]; + MathExpr* expr; + + CurrentExpression++; + if (nToks < 4) return ERR_ITEMS; + k = CurrentExpression; + Expression[k].expression = NULL; + sstrncpy(Expression[k].name, tok[1], MAXVARNAME); + sstrncpy(s, tok[3], MAXLINE); + for (i = 4; i < nToks; i++) + { + sstrcat(s, " ", MAXLINE); + sstrcat(s, tok[i], MAXLINE); + } + + expr = mathexpr_create(s, getVariableIndex); + if (expr == NULL) + return error_setInpError(ERR_MATH_EXPR, ""); + + Expression[k].expression = expr; + return 0; +} + +//============================================================================= + +int getVariableIndex(char* varName) +// +// Input: varName = string containing a variable name +// Output: returns the index of the named variable or -1 if not found +// Purpose: finds the array index of a named variable. +// +{ + int i; + for (i = 0; i < VariableCount; i++) + { + if (match(varName, NamedVariable[i].name)) return i; + } + return -1; +} + +//============================================================================= + +double getNamedVariableValue(int varIndex) +// +// Input: varIndex = index of a named variable +// Output: returns the current value of the variable +// Purpose: finds the value of a named variable. +// +{ + return getVariableValue(NamedVariable[varIndex].variable); +} + +//============================================================================= + +int getExpressionIndex(char* exprName) +// +// Input: exprName = string containing an expression name +// Output: returns the index of the expression or -1 if not found +// Purpose: finds the array index of a math expression +// +{ + int i; + for (i = 0; i < ExpressionCount; i++) + { + if (match(exprName, Expression[i].name)) return i; + } + return -1; +} + +//============================================================================= + +int controls_addRuleClause(int r, int keyword, char* tok[], int nToks) +// +// Input: r = rule index +// keyword = the clause's keyword code (IF, THEN, etc.) +// tok = an array of string tokens that comprises the clause +// nToks = number of tokens +// Output: returns an error code +// Purpose: addd a new clause to a control rule. +// +{ + switch (keyword) + { + case r_RULE: + if ( Rules[r].ID == NULL ) + Rules[r].ID = project_findID(CONTROL, tok[1]); + InputState = r_RULE; + if ( nToks > 2 ) return ERR_RULE; + return 0; + + case r_IF: + if ( InputState != r_RULE ) return ERR_RULE; + InputState = r_IF; + return addPremise(r, r_AND, tok, nToks); + + case r_AND: + if ( InputState == r_IF ) return addPremise(r, r_AND, tok, nToks); + else if ( InputState == r_THEN || InputState == r_ELSE ) + return addAction(r, tok, nToks); + else return ERR_RULE; + + case r_OR: + if ( InputState != r_IF ) return ERR_RULE; + return addPremise(r, r_OR, tok, nToks); + + case r_THEN: + if ( InputState != r_IF ) return ERR_RULE; + InputState = r_THEN; + return addAction(r, tok, nToks); + + case r_ELSE: + if ( InputState != r_THEN ) return ERR_RULE; + InputState = r_ELSE; + return addAction(r, tok, nToks); + + case r_PRIORITY: + if ( InputState != r_THEN && InputState != r_ELSE ) return ERR_RULE; + InputState = r_PRIORITY; + if ( !getDouble(tok[1], &Rules[r].priority) ) return ERR_NUMBER; + if ( nToks > 2 ) return ERR_RULE; + return 0; + } + return 0; +} + +//============================================================================= + +int controls_evaluate(DateTime currentTime, DateTime elapsedTime, double tStep) +// +// Input: currentTime = current simulation date/time +// elapsedTime = decimal days since start of simulation +// tStep = simulation time step (days) +// Output: returns number of new actions taken +// Purpose: evaluates all control rules at current time of the simulation. +// +{ + int r; // control rule index + int result; // TRUE if rule premises satisfied + struct TPremise* p; // pointer to rule premise clause + struct TAction* a; // pointer to rule action clause + + // --- save date and time to shared variables + CurrentDate = floor(currentTime); + CurrentTime = currentTime - floor(currentTime); + ElapsedTime = elapsedTime; + + // --- evaluate each rule + if ( RuleCount == 0 ) return 0; + clearActionList(); + for (r=0; rtype == r_OR ) + { + if ( result == FALSE ) + result = evaluatePremise(p, tStep); + } + else + { + if ( result == FALSE ) break; + result = evaluatePremise(p, tStep); + } + p = p->next; + } + + // --- if premises true, add THEN clauses to action list + // else add ELSE clauses to action list + if ( result == TRUE ) a = Rules[r].thenActions; + else a = Rules[r].elseActions; + while (a) + { + updateActionValue(a, currentTime, tStep); + updateActionList(a); + a = a->next; + } + } + + // --- execute actions on action list + if ( ActionList ) return executeActionList(currentTime); + else return 0; +} + +//============================================================================= + +int addPremise(int r, int type, char* tok[], int nToks) +// +// Input: r = control rule index +// type = type of premise (IF, AND, OR) +// tok = array of string tokens containing premise statement +// nToks = number of string tokens +// Output: returns an error code +// Purpose: adds a new premise to a control rule. +// +{ + int relation, n, err = 0; + double value = MISSING; + struct TPremise* p; + struct TVariable v1; + struct TVariable v2; + int obj, exprIndex, varIndex = -1; + + // --- initialize LHS variable v1 + if (nToks < 4) return ERR_ITEMS; + v1.attribute = -1; + v1.object = -1; + v1.index = -1; + n = 1; + + // --- check if 2nd token is a math expression + exprIndex = getExpressionIndex(tok[1]); + + // --- if not then check if it's a named variable + if (exprIndex < 0) + { + varIndex = getVariableIndex(tok[n]); + if (varIndex >= 0) + { + v1 = NamedVariable[varIndex].variable; + } + + // otherwise parse object|index|attribute tokens + else + { + err = getPremiseVariable(tok, nToks, &n, &v1); + if ( err > 0 ) return err; + } + } + + // --- get relational operator + n++; + if ( n >= nToks ) return error_setInpError(ERR_ITEMS, ""); + relation = findExactMatch(tok[n], RelOpWords); + if ( relation < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); + + // --- initialize RHS variable v2 + v2.attribute = -1; + v2.object = -1; + v2.index = -1; + n++; + if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); + + // --- check for named RHS variable + varIndex = getVariableIndex(tok[n]); + if (varIndex >= 0) + { + v2 = NamedVariable[varIndex].variable; + } + + // --- check for object|index|attribute variable + else + { + obj = findmatch(tok[n], ObjectWords); + if (obj >= 0) + { + err = getPremiseVariable(tok, nToks, &n, &v2); + if ( err > 0 ) return ERR_RULE; + if (exprIndex < 0 && v1.attribute != v2.attribute) + report_writeWarningMsg(WARN11, Rules[r].ID); + } + + // --- check for a single RHS value + else + { + err = getPremiseValue(tok[n], v1.attribute, &value); + if ( err > 0 ) return err; + } + } + + // --- make sure another clause is not on same line + n++; + if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE; + + // --- create the premise object + p = (struct TPremise *) malloc(sizeof(struct TPremise)); + if ( !p ) return ERR_MEMORY; + p->type = type; + p->exprIndex = exprIndex; + p->lhsVar = v1; + p->rhsVar = v2; + p->relation = relation; + p->value = value; + p->next = NULL; + if ( Rules[r].firstPremise == NULL ) + { + Rules[r].firstPremise = p; + } + else + { + Rules[r].lastPremise->next = p; + } + Rules[r].lastPremise = p; + return 0; +} + +//============================================================================= + +int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v) +// +// Input: tok = array of string tokens +// nToks = number of tokens +// k = index of current token +// Output: returns an error code; updates k to new current token and +// places identity of specified variable in v +// Purpose: parses a variable (e.g., Node 123 Depth) used in a control rule. +// +{ + int n = *k; + int object = -1; + int index = -1; + int obj, attrib; + + // --- get object type + obj = findmatch(tok[n], ObjectWords); + if ( obj < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); + + // --- get object index from its name + n++; + if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); + switch (obj) + { + case r_GAGE: + index = project_findObject(GAGE, tok[n]); + if (index < 0) return error_setInpError(ERR_NAME, tok[n]); + object = r_GAGE; + break; + + case r_NODE: + index = project_findObject(NODE, tok[n]); + if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]); + object = r_NODE; + break; + + case r_LINK: + case r_CONDUIT: + case r_PUMP: + case r_ORIFICE: + case r_WEIR: + case r_OUTLET: + index = project_findObject(LINK, tok[n]); + if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]); + object = r_LINK; + break; + default: n--; + } + n++; + if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); + + // --- get attribute index from its name + if (object == r_GAGE) + attrib = getGageAttrib(tok[n]); + else + attrib = findmatch(tok[n], AttribWords); + if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); + + // --- check that attribute belongs to object type + if (obj == r_GAGE) + { + + } + + else if ( obj == r_NODE ) switch (attrib) + { + case r_DEPTH: + case r_MAXDEPTH: + case r_HEAD: + case r_VOLUME: + case r_INFLOW: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + + // --- check for link TIMEOPEN & TIMECLOSED attributes + else if ( object == r_LINK && index >= 0 && + ( (attrib == r_TIMEOPEN || attrib == r_TIMECLOSED) + )) + { + // nothing to do here + } + + else if ( obj == r_LINK || obj == r_CONDUIT ) switch (attrib) + { + case r_STATUS: + case r_DEPTH: + case r_FULLFLOW: + case r_FULLDEPTH: + case r_FLOW: + case r_LENGTH: + case r_SLOPE: + case r_VELOCITY: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + else if ( obj == r_PUMP ) switch (attrib) + { + case r_FLOW: + case r_SETTING: + case r_STATUS: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + else if ( obj == r_ORIFICE || obj == r_WEIR || + obj == r_OUTLET ) switch (attrib) + { + case r_FLOW: + case r_SETTING: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + else switch (attrib) + { + case r_TIME: + case r_DATE: + case r_CLOCKTIME: + case r_DAY: + case r_MONTH: + case r_DAYOFYEAR: break; + default: return error_setInpError(ERR_KEYWORD, tok[n]); + } + + // --- populate variable structure + v->object = object; + v->index = index; + v->attribute = attrib; + *k = n; + return 0; +} + +//============================================================================= + +int getGageAttrib(char* token) +// +// Input: token = a string token +// Output: returns an attribute code or -1 if an error occurred +// Purpose: determines the atrribute code for a rain gage variable. +// Note: a valid token is INTENSITY for current rainfall intensity +// (attribute code = 0) or nHR_PRECIP for total rain depth +// over past n hours (attribute code = n). +// +{ + int attrib; + + // --- check if token is currrent rainfall intensity + if (match(token, IntensityWord)) + return 0; + + // --- token is past rain depth - read number of past hours + attrib = atoi(token); + + // --- check that number of hours is in allowable range + if (attrib < 1 || attrib > MAXPASTRAIN) + return -1; + return attrib; +} + +//============================================================================= + +int getPremiseValue(char* token, int attrib, double* value) +// +// Input: token = a string token +// attrib = index of a node/link attribute +// Output: value = attribute value; +// returns an error code; +// Purpose: parses the numerical value of a particular node/link attribute +// in the premise clause of a control rule. +// +{ + char strDate[25]; + switch (attrib) + { + case r_STATUS: + *value = findmatch(token, StatusWords); + if ( *value < 0.0 ) *value = findmatch(token, ConduitWords); + if ( *value < 0.0 ) return error_setInpError(ERR_KEYWORD, token); + break; + + case r_TIME: + case r_CLOCKTIME: + case r_TIMEOPEN: + case r_TIMECLOSED: + if ( !datetime_strToTime(token, value) ) + return error_setInpError(ERR_DATETIME, token); + break; + + case r_DATE: + if ( !datetime_strToDate(token, value) ) + return error_setInpError(ERR_DATETIME, token); + break; + + case r_DAY: + if ( !getDouble(token, value) ) + return error_setInpError(ERR_NUMBER, token); + if ( *value < 1.0 || *value > 7.0 ) + return error_setInpError(ERR_DATETIME, token); + break; + + case r_MONTH: + if ( !getDouble(token, value) ) + return error_setInpError(ERR_NUMBER, token); + if ( *value < 1.0 || *value > 12.0 ) + return error_setInpError(ERR_DATETIME, token); + break; + + case r_DAYOFYEAR: + sstrncpy(strDate, token, 6); + sstrcat(strDate, "/1947", 25); + if ( datetime_strToDate(strDate, value) ) + { + *value = datetime_dayOfYear(*value); + } + else if ( !getDouble(token, value) || *value < 1 || *value > 365 ) + return error_setInpError(ERR_DATETIME, token); + break; + + default: if ( !getDouble(token, value) ) + return error_setInpError(ERR_NUMBER, token); + } + return 0; +} + +//============================================================================= + +int addAction(int r, char* tok[], int nToks) +// +// Input: r = control rule index +// tok = array of string tokens containing action statement +// nToks = number of string tokens +// Output: returns an error code +// Purpose: adds a new action to a control rule. +// +{ + int obj, link, attrib; + int curve = -1, tseries = -1; + int n; + int err; + double values[] = {1.0, 0.0, 0.0}; + + struct TAction* a; + + // --- check for proper number of tokens + if ( nToks < 6 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check for valid object type + obj = findmatch(tok[1], ObjectWords); + if ( obj != r_LINK && obj != r_CONDUIT && obj != r_PUMP && + obj != r_ORIFICE && obj != r_WEIR && obj != r_OUTLET ) + return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- check that object name exists and is of correct type + link = project_findObject(LINK, tok[2]); + if ( link < 0 ) return error_setInpError(ERR_NAME, tok[2]); + switch (obj) + { + case r_CONDUIT: + if ( Link[link].type != CONDUIT ) + return error_setInpError(ERR_NAME, tok[2]); + break; + case r_PUMP: + if ( Link[link].type != PUMP ) + return error_setInpError(ERR_NAME, tok[2]); + break; + case r_ORIFICE: + if ( Link[link].type != ORIFICE ) + return error_setInpError(ERR_NAME, tok[2]); + break; + case r_WEIR: + if ( Link[link].type != WEIR ) + return error_setInpError(ERR_NAME, tok[2]); + break; + case r_OUTLET: + if ( Link[link].type != OUTLET ) + return error_setInpError(ERR_NAME, tok[2]); + break; + } + + // --- check for valid attribute name + attrib = findmatch(tok[3], AttribWords); + if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); + + // --- get control action setting + if ( obj == r_CONDUIT ) + { + if ( attrib == r_STATUS ) + { + values[0] = findmatch(tok[5], ConduitWords); + if ( values[0] < 0.0 ) + return error_setInpError(ERR_KEYWORD, tok[5]); + } + else return error_setInpError(ERR_KEYWORD, tok[3]); + } + + else if ( obj == r_PUMP ) + { + if ( attrib == r_STATUS ) + { + values[0] = findmatch(tok[5], StatusWords); + if ( values[0] < 0.0 ) + return error_setInpError(ERR_KEYWORD, tok[5]); + } + else if ( attrib == r_SETTING ) + { + err = setActionSetting(tok, nToks, &curve, &tseries, + &attrib, values); + if ( err > 0 ) return err; + } + else return error_setInpError(ERR_KEYWORD, tok[3]); + } + + else if ( obj == r_ORIFICE || obj == r_WEIR || obj == r_OUTLET ) + { + if ( attrib == r_SETTING ) + { + err = setActionSetting(tok, nToks, &curve, &tseries, + &attrib, values); + if ( err > 0 ) return err; + if ( attrib == r_SETTING + && (values[0] < 0.0 || values[0] > 1.0) ) + return error_setInpError(ERR_NUMBER, tok[5]); + } + else return error_setInpError(ERR_KEYWORD, tok[3]); + } + else return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- check if another clause is on same line + n = 6; + if ( curve >= 0 || tseries >= 0 ) n = 7; + if ( attrib == r_PID ) n = 9; + if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE; + + // --- create the action object + a = (struct TAction *) malloc(sizeof(struct TAction)); + if ( !a ) return ERR_MEMORY; + a->rule = r; + a->link = link; + a->attribute = attrib; + a->curve = curve; + a->tseries = tseries; + a->value = values[0]; + if ( attrib == r_PID ) + { + a->kp = values[0]; + a->ki = values[1]; + a->kd = values[2]; + a->e1 = 0.0; + a->e2 = 0.0; + } + if ( InputState == r_THEN ) + { + a->next = Rules[r].thenActions; + Rules[r].thenActions = a; + } + else + { + a->next = Rules[r].elseActions; + Rules[r].elseActions = a; + } + return 0; +} + +//============================================================================= + +int setActionSetting(char* tok[], int nToks, int* curve, int* tseries, + int* attrib, double values[]) +// +// Input: tok = array of string tokens containing action statement +// nToks = number of string tokens +// Output: curve = index of controller curve +// tseries = index of controller time series +// attrib = r_PID if PID controller used +// values = values of control settings +// returns an error code +// Purpose: identifies how control actions settings are determined. +// +{ + int k, m; + + // --- see if control action is determined by a Curve or Time Series + if (nToks < 6) return error_setInpError(ERR_ITEMS, ""); + k = findmatch(tok[5], SettingTypeWords); + if ( k >= 0 && nToks < 7 ) return error_setInpError(ERR_ITEMS, ""); + switch (k) + { + + // --- control determined by a curve - find curve index + case r_CURVE: + m = project_findObject(CURVE, tok[6]); + if ( m < 0 ) return error_setInpError(ERR_NAME, tok[6]); + *curve = m; + break; + + // --- control determined by a time series - find time series index + case r_TIMESERIES: + m = project_findObject(TSERIES, tok[6]); + if ( m < 0 ) return error_setInpError(ERR_NAME, tok[6]); + *tseries = m; + Tseries[m].refersTo = CONTROL; + break; + + // --- control determined by PID controller + case r_PID: + if (nToks < 9) return error_setInpError(ERR_ITEMS, ""); + for (m=6; m<=8; m++) + { + if ( !getDouble(tok[m], &values[m-6]) ) + return error_setInpError(ERR_NUMBER, tok[m]); + } + *attrib = r_PID; + break; + + // --- direct numerical control is used + default: + if ( !getDouble(tok[5], &values[0]) ) + return error_setInpError(ERR_NUMBER, tok[5]); + } + return 0; +} + +//============================================================================= + +void updateActionValue(struct TAction* a, DateTime currentTime, double dt) +// +// Input: a = an action object +// currentTime = current simulation date/time (days) +// dt = time step (days) +// Output: none +// Purpose: updates value of actions found from Curves or Time Series. +// +{ + if ( a->curve >= 0 ) + { + a->value = table_lookup(&Curve[a->curve], ControlValue); + } + else if ( a->tseries >= 0 ) + { + a->value = table_tseriesLookup(&Tseries[a->tseries], currentTime, TRUE); + } + else if ( a->attribute == r_PID ) + { + a->value = getPIDSetting(a, dt); + } +} + +//============================================================================= + +double getPIDSetting(struct TAction* a, double dt) +// +// Input: a = an action object +// dt = current time step (days) +// Output: returns a new link setting +// Purpose: computes a new setting for a link subject to a PID controller. +// +// Note: a->kp = gain coefficient, +// a->ki = integral time (minutes) +// a->k2 = derivative time (minutes) +// a->e1 = error from previous time step +// a->e2 = error from two time steps ago +{ + double e0, setting; + double p, i, d, update; + double tolerance = 0.0001; + + // --- convert time step from days to minutes + dt *= 1440.0; + + // --- determine relative error in achieving controller set point + e0 = SetPoint - ControlValue; + if ( fabs(e0) > TINY ) + { + if ( SetPoint != 0.0 ) e0 = e0/SetPoint; + else e0 = e0/ControlValue; + } + + // --- reset previous errors to 0 if controller gets stuck + if (fabs(e0 - a->e1) < tolerance) + { + a->e2 = 0.0; + a->e1 = 0.0; + } + + // --- use the recursive form of the PID controller equation to + // determine the new setting for the controlled link + p = (e0 - a->e1); + if ( a->ki == 0.0 ) i = 0.0; + else i = e0 * dt / a->ki; + d = a->kd * (e0 - 2.0*a->e1 + a->e2) / dt; + update = a->kp * (p + i + d); + if ( fabs(update) < tolerance ) update = 0.0; + setting = Link[a->link].targetSetting + update; + + // --- update previous errors + a->e2 = a->e1; + a->e1 = e0; + + // --- check that new setting lies within feasible limits + if ( setting < 0.0 ) setting = 0.0; + if (Link[a->link].type != PUMP && setting > 1.0 ) setting = 1.0; + return setting; +} + +//============================================================================= + +void updateActionList(struct TAction* a) +// +// Input: a = an action object +// Output: none +// Purpose: adds a new action to the list of actions to be taken. +// +{ + struct TActionList* listItem; + struct TAction* a1; + double priority = Rules[a->rule].priority; + + // --- check if link referred to in action is already listed + listItem = ActionList; + while ( listItem ) + { + a1 = listItem->action; + if ( !a1 ) break; + if ( a1->link == a->link ) + { + // --- replace old action if new action has higher priority + if ( priority > Rules[a1->rule].priority ) listItem->action = a; + return; + } + listItem = listItem->next; + } + + // --- action not listed so add it to ActionList //5.2.1 + if ( !listItem ) + { + listItem = (struct TActionList *) malloc(sizeof(struct TActionList)); + listItem->next = ActionList; + ActionList = listItem; + } + listItem->action = a; +} + +//============================================================================= + +int executeActionList(DateTime currentTime) +// +// Input: currentTime = current date/time of the simulation +// Output: returns number of new actions taken +// Purpose: executes all actions required by fired control rules. +// +{ + struct TActionList* listItem; + struct TActionList* nextItem; + struct TAction* a1; + int count = 0; + + listItem = ActionList; + while ( listItem ) + { + a1 = listItem->action; + if ( !a1 ) break; + if ( a1->link >= 0 ) + { + if ( Link[a1->link].targetSetting != a1->value ) + { + Link[a1->link].targetSetting = a1->value; + if ( RptFlags.controls && a1->curve < 0 + && a1->tseries < 0 && a1->attribute != r_PID ) + report_writeControlAction(currentTime, Link[a1->link].ID, + a1->value, Rules[a1->rule].ID); + count++; + } + } + nextItem = listItem->next; + listItem = nextItem; + } + return count; +} + +//============================================================================= + +int evaluatePremise(struct TPremise* p, double tStep) +// +// Input: p = a control rule premise condition +// tStep = current time step (days) +// Output: returns TRUE if the condition is true or FALSE otherwise +// Purpose: evaluates the truth of a control rule premise condition. +// +{ + double lhsValue, rhsValue; + int result = FALSE; + + // --- check if left hand side (lhs) of premise is an expression + if (p->exprIndex >= 0) + lhsValue = mathexpr_eval(Expression[p->exprIndex].expression, + getNamedVariableValue); + + // --- otherwise get value of the lhs variable + else + lhsValue = getVariableValue(p->lhsVar); + + // --- if right hand side (rhs) of premise is a variable then get its value + if ( p->value == MISSING ) rhsValue = getVariableValue(p->rhsVar); + else rhsValue = p->value; + if ( lhsValue == MISSING || rhsValue == MISSING ) return FALSE; + + // --- compare the lhs of the premise to the rhs + switch (p->lhsVar.attribute) + { + case r_TIME: + case r_CLOCKTIME: + return compareTimes(lhsValue, p->relation, rhsValue, tStep/2.0); + case r_TIMEOPEN: + case r_TIMECLOSED: + result = compareTimes(lhsValue, p->relation, rhsValue, tStep/2.0); + ControlValue = lhsValue * 24.0; // convert time from days to hours + return result; + default: + return compareValues(lhsValue, p->relation, rhsValue); + } +} + +//============================================================================= + +double getVariableValue(struct TVariable v) +{ + int i = -1; // a node index + int j = -1; // a link index + + if (v.object == r_GAGE) + return getRainValue(v); + if (v.object == r_NODE) i = v.index; + if (v.object == r_LINK) j = v.index; + + switch ( v.attribute ) + { + case r_TIME: + return ElapsedTime; + + case r_DATE: + return CurrentDate; + + case r_CLOCKTIME: + return CurrentTime; + + case r_DAY: + return datetime_dayOfWeek(CurrentDate); + + case r_MONTH: + return datetime_monthOfYear(CurrentDate); + + case r_DAYOFYEAR: + return datetime_dayOfYear(CurrentDate); + + case r_STATUS: + if ( j < 0 || + (Link[j].type != CONDUIT && Link[j].type != PUMP) ) return MISSING; + else return Link[j].setting; + + case r_SETTING: + if ( j < 0 || (Link[j].type != PUMP && + Link[j].type != ORIFICE && + Link[j].type != WEIR) ) + return MISSING; + else return Link[j].setting; + + case r_FLOW: + if ( j < 0 ) return MISSING; + else return Link[j].direction*Link[j].newFlow*UCF(FLOW); + + case r_FULLFLOW: + case r_FULLDEPTH: + case r_VELOCITY: + case r_LENGTH: + case r_SLOPE: + if ( j < 0 ) return MISSING; + else if (Link[j].type != CONDUIT) return MISSING; + switch (v.attribute) + { + case r_FULLFLOW: return Link[j].qFull * UCF(FLOW); + case r_FULLDEPTH: return Link[j].xsect.yFull * UCF(LENGTH); + case r_VELOCITY: + return link_getVelocity(j, Link[j].newFlow, Link[j].newDepth) + * UCF(LENGTH); + case r_LENGTH: return Conduit[Link[j].subIndex].length * UCF(LENGTH); + case r_SLOPE: return Conduit[Link[j].subIndex].slope; + default: return MISSING; + } + case r_DEPTH: + if ( j >= 0 ) return Link[j].newDepth*UCF(LENGTH); + else if ( i >= 0 ) + return Node[i].newDepth*UCF(LENGTH); + else return MISSING; + + case r_MAXDEPTH: + if (i >= 0) return Node[i].fullDepth*UCF(LENGTH); + else return MISSING; + + case r_HEAD: + if ( i < 0 ) return MISSING; + return (Node[i].newDepth + Node[i].invertElev) * UCF(LENGTH); + + case r_VOLUME: + if ( i < 0 ) return MISSING; + return (Node[i].newVolume * UCF(VOLUME)); + + case r_INFLOW: + if ( i < 0 ) return MISSING; + else return Node[i].newLatFlow*UCF(FLOW); + + case r_TIMEOPEN: + if ( j < 0 ) return MISSING; + if ( Link[j].setting <= 0.0 ) return MISSING; + return CurrentDate + CurrentTime - Link[j].timeLastSet; + + case r_TIMECLOSED: + if ( j < 0 ) return MISSING; + if ( Link[j].setting > 0.0 ) return MISSING; + return CurrentDate + CurrentTime - Link[j].timeLastSet; + + default: return MISSING; + } +} + +//============================================================================= + +double getRainValue(struct TVariable v) +// +// Input: v = a rule premise variable for a rain gage +// Output: returns current or past rainfall amount +// Purpose: retrieves either the current rainfall intensity or the past +// rainfall total for a rain gage. +// +{ + if (v.index < 0) return MISSING; + else if (Gage[v.index].isUsed == FALSE) return 0.0; + else if (v.attribute == 0) + return Gage[v.index].rainfall; + else return gage_getPastRain(v.index, v.attribute); +} + +//============================================================================= + +int compareTimes(double lhsValue, int relation, double rhsValue, double halfStep) +// +// Input: lhsValue = date/time value on left hand side of relation +// relation = relational operator code (see RuleRelation enumeration) +// rhsValue = date/time value on right hand side of relation +// halfStep = 1/2 the current time step (days) +// Output: returns TRUE if time relation is satisfied +// Purpose: evaluates the truth of a relation between two date/times. +// +{ + if ( relation == EQ ) + { + if ( lhsValue >= rhsValue - halfStep + && lhsValue < rhsValue + halfStep ) return TRUE; + return FALSE; + } + else if ( relation == NE ) + { + if ( lhsValue < rhsValue - halfStep + || lhsValue >= rhsValue + halfStep ) return TRUE; + return FALSE; + } + else return compareValues(lhsValue, relation, rhsValue); +} + +//============================================================================= + +int compareValues(double lhsValue, int relation, double rhsValue) +// Input: lhsValue = value on left hand side of relation +// relation = relational operator code (see RuleRelation enumeration) +// rhsValue = value on right hand side of relation +// Output: returns TRUE if relation is satisfied +// Purpose: evaluates the truth of a relation between two values. +{ + SetPoint = rhsValue; + ControlValue = lhsValue; + switch (relation) + { + case EQ: if ( lhsValue == rhsValue ) return TRUE; break; + case NE: if ( lhsValue != rhsValue ) return TRUE; break; + case LT: if ( lhsValue < rhsValue ) return TRUE; break; + case LE: if ( lhsValue <= rhsValue ) return TRUE; break; + case GT: if ( lhsValue > rhsValue ) return TRUE; break; + case GE: if ( lhsValue >= rhsValue ) return TRUE; break; + } + return FALSE; +} + +//============================================================================= + +void clearActionList(void) +// +// Input: none +// Output: none +// Purpose: clears the list of actions to be executed. +// +{ + struct TActionList* listItem; + listItem = ActionList; + while ( listItem ) + { + listItem->action = NULL; + listItem = listItem->next; + } +} + +//============================================================================= + +void deleteActionList(void) +// +// Input: none +// Output: none +// Purpose: frees the memory used to hold the list of actions to be executed. +// +{ + struct TActionList* listItem; + struct TActionList* nextItem; + listItem = ActionList; + while ( listItem ) + { + nextItem = listItem->next; + free(listItem); + listItem = nextItem; + } + ActionList = NULL; +} + +//============================================================================= + +void deleteRules(void) +// +// Input: none +// Output: none +// Purpose: frees the memory used for all of the control rules. +// +{ + struct TPremise* p; + struct TPremise* pnext; + struct TAction* a; + struct TAction* anext; + int r; + for (r=0; rnext; + free(p); + p = pnext; + } + a = Rules[r].thenActions; + while (a ) + { + anext = a->next; + free(a); + a = anext; + } + a = Rules[r].elseActions; + while (a ) + { + anext = a->next; + free(a); + a = anext; + } + } + FREE(Rules); + RuleCount = 0; +} + +//============================================================================= + +int findExactMatch(char *s, char *keyword[]) +// +// Input: s = character string +// keyword = array of keyword strings +// Output: returns index of keyword which matches s or -1 if no match found +// Purpose: finds exact match between string and array of keyword strings. +// +{ + int i = 0; + while (keyword[i] != NULL) + { + if ( strcomp(s, keyword[i]) ) return(i); + i++; + } + return(-1); +} + +//============================================================================= diff --git a/src/culvert.c b/src/culvert.c new file mode 100644 index 000000000..5f62877b7 --- /dev/null +++ b/src/culvert.c @@ -0,0 +1,411 @@ +//----------------------------------------------------------------------------- +// culvert.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Culvert equations for SWMM5 +// +// Computes flow reduction in a culvert-type conduit due to +// inlet control using equations from the FHWA HEC-5 circular. +// +// Update History +// ============== +// Build 5.1.013: +// - C parameter corrected for Arch, Corrugated Metal, Mitered culvert. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "findroot.h" +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +enum CulvertParam {FORM, K, M, C, Y}; +static const int MAX_CULVERT_CODE = 57; +static const double Params[58][5] = { + +// FORM K M C Y +//------------------------------------ + {0.0, 0.0, 0.0, 0.0, 0.00}, + + //Circular concrete + {1.0, 0.0098, 2.00, 0.0398, 0.67}, //Square edge w/headwall + {1.0, 0.0018, 2.00, 0.0292, 0.74}, //Groove end w/headwall + {1.0, 0.0045, 2.00, 0.0317, 0.69}, //Groove end projecting + + //Circular Corrugated Metal Pipe + {1.0, 0.0078, 2.00, 0.0379, 0.69}, //Headwall + {1.0, 0.0210, 1.33, 0.0463, 0.75}, //Mitered to slope + {1.0, 0.0340, 1.50, 0.0553, 0.54}, //Projecting + + //Circular Pipe, Beveled Ring Entrance + {1.0, 0.0018, 2.50, 0.0300, 0.74}, //Beveled ring, 45 deg bevels + {1.0, 0.0018, 2.50, 0.0243, 0.83}, //Beveled ring, 33.7 deg bevels + + //Rectangular Box with Flared Wingwalls + {1.0, 0.026, 1.0, 0.0347, 0.81}, //30-75 deg. wingwall flares + {1.0, 0.061, 0.75, 0.0400, 0.80}, //90 or 15 deg. wingwall flares + {1.0, 0.061, 0.75, 0.0423, 0.82}, //0 deg. wingwall flares (striaght sides) + + //Rectanglar Box with Flared Wingwalls & Top Edge Bevel + {2.0, 0.510, 0.667, 0.0309, 0.80}, //45 deg. flare; 0.43D top edge bevel + {2.0, 0.486, 0.667, 0.0249, 0.83}, //18-33.7 deg flare; 0.083D top edge bevel + + //Rectangular Box; 90-deg Headwall; Chamfered or Beveled Inlet Edges + {2.0, 0.515, 0.667, 0.0375, 0.79}, //chamfered 3/4-in + {2.0, 0.495, 0.667, 0.0314, 0.82}, //beveled 1/2-in/ft at 45 deg (1:1) + {2.0, 0.486, 0.667, 0.0252, 0.865}, //beveled 1-in/ft at 33.7 deg (1:1.5) + + //Rectangular Box; Skewed Headwall; Chamfered or Beveled Inlet Edges + {2.0, 0.545, 0.667, 0.04505,0.73}, //3/4" chamfered edge, 45 deg skewed headwall + {2.0, 0.533, 0.667, 0.0425, 0.705}, //3/4" chamfered edge, 30 deg skewed headwall + {2.0, 0.522, 0.667, 0.0402, 0.68}, //3/4" chamfered edge, 15 deg skewed headwall + {2.0, 0.498, 0.667, 0.0327, 0.75}, //45 deg beveled edge, 10-45 deg skewed headwall + + //Rectangular box, Non-offset Flared Wingwalls; 3/4" Chamfer at Top of Inlet + {2.0, 0.497, 0.667, 0.0339, 0.803}, //45 deg (1:1) wingwall flare + {2.0, 0.493, 0.667, 0.0361, 0.806}, //18.4 deg (3:1) wingwall flare + {2.0, 0.495, 0.667, 0.0386, 0.71}, //18.4 deg (3:1) wingwall flare, 30 deg inlet skew + + //Rectangular box, Offset Flared Wingwalls, Beveled Edge at Inlet Top + {2.0, 0.497, 0.667, 0.0302, 0.835}, //45 deg (1:1) flare, 0.042D top edge bevel + {2.0, 0.495, 0.667, 0.0252, 0.881}, //33.7 deg (1.5:1) flare, 0.083D top edge bevel + {2.0, 0.493, 0.667, 0.0227, 0.887}, //18.4 deg (3:1) flare, 0.083D top edge bevel + + // Corrugated Metal Box + {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall + {1.0, 0.0145, 1.75, 0.0419, 0.64}, //Thick wall projecting + {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Thin wall projecting + + // Horizontal Ellipse Concrete + {1.0, 0.0100, 2.00, 0.0398, 0.67}, //Square edge w/headwall + {1.0, 0.0018, 2.50, 0.0292, 0.74}, //Grooved end w/headwall + {1.0, 0.0045, 2.00, 0.0317, 0.69}, //Grooved end projecting + + // Vertical Ellipse Concrete + {1.0, 0.0100, 2.00, 0.0398, 0.67}, //Square edge w/headwall + {1.0, 0.0018, 2.50, 0.0292, 0.74}, //Grooved end w/headwall + {1.0, 0.0095, 2.00, 0.0317, 0.69}, //Grooved end projecting + + // Pipe Arch, 18" Corner Radius, Corrugated Metal + {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall + {1.0, 0.0300, 1.00, 0.0463, 0.75}, //Mitered to slope + {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Projecting + + // Pipe Arch, 18" Corner Radius, Corrugated Metal + {1.0, 0.0300, 1.50, 0.0496, 0.57}, //Projecting + {1.0, 0.0088, 2.00, 0.0368, 0.68}, //No bevels + {1.0, 0.0030, 2.00, 0.0269, 0.77}, //33.7 deg bevels + + // Pipe Arch, 31" Corner Radius, Corrugated Metal + {1.0, 0.0300, 1.50, 0.0496, 0.57}, //Projecting + {1.0, 0.0088, 2.00, 0.0368, 0.68}, //No bevels + {1.0, 0.0030, 2.00, 0.0269, 0.77}, //33.7 deg. bevels + + // Arch, Corrugated Metal + {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall + {1.0, 0.0300, 1.00, 0.0473, 0.75}, //Mitered to slope + {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Thin wall projecting + + // Circular Culvert + {2.0, 0.534, 0.555, 0.0196, 0.90}, //Smooth tapered inlet throat + {2.0, 0.519, 0.640, 0.0210, 0.90}, //Rough tapered inlet throat + + // Elliptical Inlet Face + {2.0, 0.536, 0.622, 0.0368, 0.83}, //Tapered inlet, beveled edges + {2.0, 0.5035,0.719, 0.0478, 0.80}, //Tapered inlet, square edges + {2.0, 0.547, 0.800, 0.0598, 0.75}, //Tapered inlet, thin edge projecting + + // Rectangular + {2.0, 0.475, 0.667, 0.0179, 0.97}, //Tapered inlet throat + + // Rectangular Concrete + {2.0, 0.560, 0.667, 0.0446, 0.85}, //Side tapered, less favorable edges + {2.0, 0.560, 0.667, 0.0378, 0.87}, //Side tapered, more favorable edges + + // Rectangular Concrete + {2.0, 0.500, 0.667, 0.0446, 0.65}, //Slope tapered, less favorable edges + {2.0, 0.500, 0.667, 0.0378, 0.71} //Slope tapered, more favorable edges + + }; + +//----------------------------------------------------------------------------- +// Culvert data structure +//----------------------------------------------------------------------------- +typedef struct +{ + double yFull; // full depth of culvert (ft) + double scf; // slope correction factor + double dQdH; // Derivative of flow w.r.t. head + double qc; // Unsubmerged critical flow + double kk; + double mm; // Coeffs. for unsubmerged flow + double ad; + double hPlus; // Intermediate terms + TXsect* xsect; // Pointer to culvert cross section +} TCulvert; + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// double culvert_getInflow + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static double getUnsubmergedFlow(int code, double h, TCulvert* culvert); +static double getSubmergedFlow(int code, double h, TCulvert* culvert); +static double getTransitionFlow(int code, double h, double h1, double h2, + TCulvert* culvert); +static double getForm1Flow(double h, TCulvert* culvert); +static double form1Eqn(double yc, void* p); +/* +static void report_CulvertControl(int j, double q0, double q, int condition, + double yRatio); //for debugging only +*/ + +//============================================================================= + +double culvert_getInflow(int j, double q0, double h) +// +// Input: j = link index +// q0 = unmodified flow rate (cfs) +// h = upstream head (ft) +// Output: returns modified flow rate through culvert (cfs) +// Purpose: uses FHWA HEC-5 equations to find flow through inlet +// controlled culverts +// +{ + int code, //culvert type code number + k, //conduit index + condition; //flow condition + double y, //current depth (ft) + y1, //unsubmerged depth limit (ft) + y2, //submerged depth limit (ft) + q; //inlet-controlled flow (cfs) + TCulvert culvert; //intermediate results + + // --- check that we have a culvert conduit + if ( Link[j].type != CONDUIT ) return q0; + culvert.xsect = &Link[j].xsect; + code = culvert.xsect->culvertCode; + if ( code <= 0 || code > MAX_CULVERT_CODE ) return q0; + + // --- compute often-used variables + k = Link[j].subIndex; + culvert.yFull = culvert.xsect->yFull; + culvert.ad = culvert.xsect->aFull * sqrt(culvert.yFull); + + // --- slope correction factor (-7 for mitered inlets, 0.5 for others) + switch (code) + { + case 5: + case 37: + case 46: culvert.scf = -7.0 * Conduit[k].slope; break; + default: culvert.scf = 0.5 * Conduit[k].slope; + } + + // --- find head relative to culvert's upstream invert + // (can be greater than yFull when inlet is submerged) + y = h - (Node[Link[j].node1].invertElev + Link[j].offset1); + + // --- check for submerged flow (based on FHWA criteria of Q/AD > 4) + y2 = culvert.yFull * (16.0 * Params[code][C] + Params[code][Y] - culvert.scf); + if ( y >= y2 ) + { + q = getSubmergedFlow(code, y, &culvert); + condition = 2; + } + else + { + // --- check for unsubmerged flow (based on arbitrary limit of 0.95 full) + y1 = 0.95 * culvert.yFull; + if ( y <= y1 ) + { + q = getUnsubmergedFlow(code, y, &culvert); + condition = 1; + } + // --- flow is in transition zone + else + { + q = getTransitionFlow(code, y, y1, y2, &culvert); + condition = 0; + } + } + + // --- check if inlet controls and replace conduit's value of dq/dh + if ( q < q0 ) + { + // --- for debugging only + //if ( RptFlags.controls ) report_CulvertControl(j, q0, q, condition, + // y / culvert.yFull); + + Link[j].inletControl = TRUE; + Link[j].dqdh = culvert.dQdH; + return q; + } + else return q0; +} + +//============================================================================= + +double getUnsubmergedFlow(int code, double h, TCulvert* culvert) +// +// Input: code = culvert type code number +// h = inlet water depth above culvert invert +// culvert = pointer to a culvert data structure +// Output: returns flow rate; +// computes value of variable Dqdh +// Purpose: computes flow rate and its derivative for unsubmerged +// culvert inlet. +// +{ + double arg; + double q; + + // --- assign shared variables + culvert->kk = Params[code][K]; + culvert->mm = Params[code][M]; + arg = h / culvert->yFull / culvert->kk; + + // --- evaluate correct equation form + if ( Params[code][FORM] == 1.0) + { + q = getForm1Flow(h, culvert); + } + else q = culvert->ad * pow(arg, 1.0/culvert->mm); + culvert->dQdH = q / h / culvert->mm; + return q; +} + +//============================================================================= + +double getSubmergedFlow(int code, double h, TCulvert* culvert) +// +// Input: code = culvert type code number +// h = inlet head (ft) +// culvert = pointer to a culvert data structure +// Output: returns flow rate; +// computes value of Dqdh +// Purpose: computes flow rate and its derivative for submerged +// culvert inlet. +// +{ + double cc = Params[code][C]; + double yy = Params[code][Y]; + double arg = (h/culvert->yFull - yy + culvert->scf) / cc ; + double q; + + if ( arg <= 0.0 ) + { + culvert->dQdH = 0.0; + return BIG; + } + q = sqrt(arg) * culvert->ad; + culvert->dQdH = 0.5 * q / arg / culvert->yFull / cc; + return q; +} + +//============================================================================= + +double getTransitionFlow(int code, double h, double h1, double h2, TCulvert* culvert) +// +// Input: code = culvert type code number +// h = inlet water depth above culvert invert (ft) +// h1 = head limit for unsubmerged condition (ft) +// h2 = head limit for submerged condition (ft) +// culvert = pointer to a culvert data structure +// Output: returns flow rate )cfs); +// computes value of Dqdh (cfs/ft) +// Purpose: computes flow rate and its derivative for inlet-controlled flow +// when inlet water depth lies in the transition range between +// submerged and unsubmerged conditions. +// +{ + double q1 = getUnsubmergedFlow(code, h1, culvert); + double q2 = getSubmergedFlow(code, h2, culvert); + double q = q1 + (q2 - q1) * (h - h1) / (h2 - h1); + culvert->dQdH = (q2 - q1) / (h2 - h1); + return q; +} + +//============================================================================= + +double getForm1Flow(double h, TCulvert* culvert) +// +// Input: h = inlet water depth above culvert invert +// culvert = pointer to a culvert data structure +// Output: returns inlet controlled flow rate +// Purpose: computes inlet-controlled flow rate for unsubmerged culvert +// using FHWA Equation Form1. +// +// See pages 195-196 of FHWA HEC-5 (2001) for details. +// +{ + // --- save re-used terms in culvert structure + culvert->hPlus = h / culvert->yFull + culvert->scf; + + // --- use Ridder's method to solve Equation Form 1 for critical depth + // between a range of 0.01h and h + findroot_Ridder(0.01*h, h, 0.001, form1Eqn, culvert); + + // --- return the flow value used in evaluating Equation Form 1 + return culvert->qc; +} + +//============================================================================= + +double form1Eqn(double yc, void* p) +// +// Input: yc = critical depth +// p = pointer to a TCulvert object +// Output: returns residual error +// Purpose: evaluates the error in satisfying FHWA culvert Equation Form1: +// +// h/yFull + 0.5*s = yc/yFull + yh/2/yFull + K[ac/aFull*sqrt(g*yh/yFull)]^M +// +// for a given value of critical depth yc where: +// h = inlet depth above culvert invert +// s = culvert slope +// yFull = full depth of culvert +// yh = hydraulic depth at critical depth +// ac = flow area at critical depth +// g = accel. of gravity +// K and M = coefficients +// +{ + double ac, wc, yh; + TCulvert* culvert = (TCulvert *)p; + + ac = xsect_getAofY(culvert->xsect, yc); + wc = xsect_getWofY(culvert->xsect, yc); + yh = ac/wc; + + culvert->qc = ac * sqrt(GRAVITY * yh); + return culvert->hPlus - yc/culvert->yFull - yh/2.0/culvert->yFull - + culvert->kk * pow(culvert->qc/culvert->ad, culvert->mm); +} + +//============================================================================= +/* +void report_CulvertControl(int j, double q0, double q, int condition, double yRatio) +// +// Used for debugging only +// +{ + static char* conditionTxt[] = {"transition", "unsubmerged", "submerged"}; + char theDate[12]; + char theTime[9]; + DateTime aDate = getDateTime(NewRoutingTime); + datetime_dateToStr(aDate, theDate); + datetime_timeToStr(aDate, theTime); + fprintf(Frpt.file, + "\n %11s: %8s Culvert %s flow reduced from %.3f to %.3f cfs for %s flow (%.2f).", + theDate, theTime, Link[j].ID, q0, q, conditionTxt[condition], yRatio); +} +*/ diff --git a/src/datetime.c b/src/datetime.c new file mode 100644 index 000000000..d96ef7f3b --- /dev/null +++ b/src/datetime.c @@ -0,0 +1,528 @@ +//----------------------------------------------------------------------------- +// datetime.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// DateTime functions. +// +// Update History +// ============== +// Build 5.1.011: +// - decodeTime() no longer rounds up. +// - New getTimeStamp function added. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include +#include "datetime.h" + +// Macro to convert charcter x to upper case +#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const char* MonthTxt[] = + {"JAN", "FEB", "MAR", "APR", + "MAY", "JUN", "JUL", "AUG", + "SEP", "OCT", "NOV", "DEC"}; +static const int DaysPerMonth[2][12] = // days per month + {{31, 28, 31, 30, 31, 30, // normal years + 31, 31, 30, 31, 30, 31}, + {31, 29, 31, 30, 31, 30, // leap years + 31, 31, 30, 31, 30, 31}}; +static const int DateDelta = 693594; // days since 01/01/00 +static const double SecsPerDay = 86400.; // seconds per day + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +static int DateFormat; + + +//============================================================================= + +void divMod(int n, int d, int* result, int* remainder) + +// Input: n = numerator +// d = denominator +// Output: result = integer part of n/d +// remainder = remainder of n/d +// Purpose: finds integer part and remainder of n/d. + +{ + if (d == 0) + { + *result = 0; + *remainder = 0; + } + else + { + *result = n/d; + *remainder = n - d*(*result); + } +} + +//============================================================================= + +int isLeapYear(int year) + +// Input: year = a year +// Output: returns 1 if year is a leap year, 0 if not +// Purpose: determines if year is a leap year. + +{ + if ((year % 4 == 0) + && ((year % 100 != 0) + || (year % 400 == 0))) return 1; + else return 0; +} + +//============================================================================= + +int datetime_findMonth(char* month) + +// Input: month = month of year as character string +// Output: returns: month of year as a number (1-12) +// Purpose: finds number (1-12) of month. + +{ + int i; + for (i = 0; i < 12; i++) + { + if (UCHAR(month[0]) == MonthTxt[i][0] + && UCHAR(month[1]) == MonthTxt[i][1] + && UCHAR(month[2]) == MonthTxt[i][2]) return i+1; + } + return 0; +} + +//============================================================================= + +DateTime datetime_encodeDate(int year, int month, int day) + +// Input: year = a year +// month = a month (1 to 12) +// day = a day of month +// Output: returns encoded value of year-month-day +// Purpose: encodes year-month-day to a DateTime value. + +{ + int i, j; + i = isLeapYear(year); + if ((year >= 1) + && (year <= 9999) + && (month >= 1) + && (month <= 12) + && (day >= 1) + && (day <= DaysPerMonth[i][month-1])) + { + for (j = 0; j < month-1; j++) day += DaysPerMonth[i][j]; + i = year - 1; + i = i*365 + i/4 - i/100 + i/400 + day - DateDelta; + return i; + } + else return -DateDelta; +} + +//============================================================================= + +DateTime datetime_encodeTime(int hour, int minute, int second) + +// Input: hour = hour of day (0-24) +// minute = minute of hour (0-60) +// second = seconds of minute (0-60) +// Output: returns time encoded as fractional part of a day +// Purpose: encodes hour:minute:second to a DateTime value + +{ + int s; + if ((hour >= 0) + && (minute >= 0) + && (second >= 0)) + { + s = (hour * 3600 + minute * 60 + second); + return (double)s/SecsPerDay; + } + else return 0.0; +} + +//============================================================================= + +void datetime_decodeDate(DateTime date, int* year, int* month, int* day) + +// Input: date = encoded date/time value +// Output: year = 4-digit year +// month = month of year (1-12) +// day = day of month +// Purpose: decodes DateTime value to year-month-day. + +{ + int D1, D4, D100, D400; + int y, m, d, i, k, t; + + D1 = 365; //365 + D4 = D1 * 4 + 1; //1461 + D100 = D4 * 25 - 1; //36524 + D400 = D100 * 4 + 1; //146097 + + t = (int)(floor (date)) + DateDelta; + if (t <= 0) + { + *year = 0; + *month = 1; + *day = 1; + } + else + { + t--; + y = 1; + while (t >= D400) + { + t -= D400; + y += 400; + } + divMod(t, D100, &i, &d); + if (i == 4) + { + i--; + d += D100; + } + y += i*100; + divMod(d, D4, &i, &d); + y += i*4; + divMod(d, D1, &i, &d); + if (i == 4) + { + i--; + d += D1; + } + y += i; + k = isLeapYear(y); + m = 1; + for (;;) + { + i = DaysPerMonth[k][m-1]; + if (d < i) break; + d -= i; + m++; + } + *year = y; + *month = m; + *day = d + 1; + } +} + +//============================================================================= + +void datetime_decodeTime(DateTime time, int* h, int* m, int* s) + +// Input: time = decimal fraction of a day +// Output: h = hour of day (0-23) +// m = minute of hour (0-59) +// s = second of minute (0-59) +// Purpose: decodes DateTime value to hour:minute:second. + +{ + int secs; + int mins; + double fracDay = (time - floor(time)) * SecsPerDay; + secs = (int)(floor(fracDay + 0.5)); + if ( secs >= 86400 ) secs = 86399; + divMod(secs, 60, &mins, s); + divMod(mins, 60, h, m); + if ( *h > 23 ) *h = 0; +} + +//============================================================================= + +void datetime_dateToStr(DateTime date, char* s) + +// Input: date = encoded date/time value +// Output: s = formatted date string +// Purpose: represents DateTime date value as a formatted string. + +{ + int y, m, d; + datetime_decodeDate(date, &y, &m, &d); + switch (DateFormat) + { + case Y_M_D: + snprintf(s, DATE_STR_SIZE, "%4d-%3s-%02d", y, MonthTxt[m-1], d); + break; + + case M_D_Y: + //sprintf(dateStr, "%3s-%02d-%4d", MonthTxt[m-1], d, y); + snprintf(s, DATE_STR_SIZE, "%02d/%02d/%04d", m, d, y); + break; + + default: + snprintf(s, DATE_STR_SIZE, "%02d-%3s-%4d", d, MonthTxt[m-1], y); + } +} + +void datetime_timeToStr(DateTime time, char* s) + +// Input: time = decimal fraction of a day +// Output: s = time in hr:min:sec format +// Purpose: represents DateTime time value as a formatted string. + +{ + int hr, min, sec; + datetime_decodeTime(time, &hr, &min, &sec); + snprintf(s, TIME_STR_SIZE, "%02d:%02d:%02d", hr, min, sec); +} + +//============================================================================= + +int datetime_strToDate(char* s, DateTime* d) + +// Input: s = date as string +// Output: d = encoded date; +// returns 1 if conversion successful, 0 if not +// Purpose: converts string date s to DateTime value. +// +{ + int yr = 0, mon = 0, day = 0, n; + char month[4]; + char sep1, sep2; + *d = -DateDelta; + if (strchr(s, '-') || strchr(s, '/')) + { + switch (DateFormat) + { + case Y_M_D: + n = sscanf(s, "%d%c%d%c%d", &yr, &sep1, &mon, &sep2, &day); + if ( n < 3 ) + { + mon = 0; + n = sscanf(s, "%d%c%3s%c%d", &yr, &sep1, month, &sep2, &day); + if ( n < 3 ) return 0; + } + break; + + case D_M_Y: + n = sscanf(s, "%d%c%d%c%d", &day, &sep1, &mon, &sep2, &yr); + if ( n < 3 ) + { + mon = 0; + n = sscanf(s, "%d%c%3s%c%d", &day, &sep1, month, &sep2, &yr); + if ( n < 3 ) return 0; + } + break; + + default: // M_D_Y + n = sscanf(s, "%d%c%d%c%d", &mon, &sep1, &day, &sep2, &yr); + if ( n < 3 ) + { + mon = 0; + n = sscanf(s, "%3s%c%d%c%d", month, &sep1, &day, &sep2, &yr); + if ( n < 3 ) return 0; + } + } + if (mon == 0) mon = datetime_findMonth(month); + *d = datetime_encodeDate(yr, mon, day); + } + if (*d == -DateDelta) return 0; + else return 1; +} + +//============================================================================= + +int datetime_strToTime(char* s, DateTime* t) + +// Input: s = time as string +// Output: t = encoded time, +// returns 1 if conversion successful, 0 if not +// Purpose: converts a string time to a DateTime value. +// Note: accepts time as hr:min:sec or as decimal hours. + +{ + int n, hr, min = 0, sec = 0; + char *endptr; + + // Attempt to read time as decimal hours + *t = strtod(s, &endptr); + if ( *endptr == 0 ) + { + *t /= 24.0; + return 1; + } + + // Read time in hr:min:sec format + *t = 0.0; + n = sscanf(s, "%d:%d:%d", &hr, &min, &sec); + if ( n == 0 ) return 0; + *t = datetime_encodeTime(hr, min, sec); + if ( (hr >= 0) && (min >= 0) && (sec >= 0) ) return 1; + else return 0; +} + +//============================================================================= + +void datetime_setDateFormat(int fmt) + +// Input: fmt = date format code +// Output: none +// Purpose: sets date format + +{ + if ( fmt >= Y_M_D && fmt <= M_D_Y) DateFormat = fmt; +} + +//============================================================================= + +DateTime datetime_addSeconds(DateTime date1, double seconds) + +// Input: date1 = an encoded date/time value +// seconds = number of seconds to add to date1 +// Output: returns updated value of date1 +// Purpose: adds a given number of seconds to a date/time. + +{ + double d = floor(date1); + int h, m, s; + datetime_decodeTime(date1, &h, &m, &s); + return d + (3600.0*h + 60.0*m + s + seconds)/SecsPerDay; +} + +//============================================================================= + +DateTime datetime_addDays(DateTime date1, DateTime date2) + +// Input: date1 = an encoded date/time value +// date2 = decimal days to be added to date1 +// Output: returns date1 + date2 +// Purpose: adds a given number of decimal days to a date/time. + +{ + double d1 = floor(date1); + double d2 = floor(date2); + int h1, m1, s1; + int h2, m2, s2; + datetime_decodeTime(date1, &h1, &m1, &s1); + datetime_decodeTime(date2, &h2, &m2, &s2); + return d1 + d2 + datetime_encodeTime(h1+h2, m1+m2, s1+s2); +} + +//============================================================================= + +long datetime_timeDiff(DateTime date1, DateTime date2) + +// Input: date1 = an encoded date/time value +// date2 = an encoded date/time value +// Output: returns date1 - date2 in seconds +// Purpose: finds number of seconds between two dates. + +{ + double d1 = floor(date1); + double d2 = floor(date2); + int h, m, s; + long s1, s2, secs; + datetime_decodeTime(date1, &h, &m, &s); + s1 = 3600*h + 60*m + s; + datetime_decodeTime(date2, &h, &m, &s); + s2 = 3600*h + 60*m + s; + secs = (int)(floor((d1 - d2)*SecsPerDay + 0.5)); + secs += (s1 - s2); + return secs; +} + +//============================================================================= + +int datetime_monthOfYear(DateTime date) + +// Input: date = an encoded date/time value +// Output: returns index of month of year (1..12) +// Purpose: finds month of year (Jan = 1 ...) for a given date. + +{ + int year, month, day; + datetime_decodeDate(date, &year, &month, &day); + return month; +} + +//============================================================================= + +int datetime_dayOfYear(DateTime date) + +// Input: date = an encoded date/time value +// Output: returns day of year (1..365) +// Purpose: finds day of year (Jan 1 = 1) for a given date. + +{ + int year, month, day; + DateTime startOfYear; + datetime_decodeDate(date, &year, &month, &day); + startOfYear = datetime_encodeDate(year, 1, 1); + return (int)(floor(date - startOfYear)) + 1; +} + +//============================================================================= + +int datetime_dayOfWeek(DateTime date) + +// Input: date = an encoded date/time value +// Output: returns index of day of week (1..7) +// Purpose: finds day of week (Sun = 1, ... Sat = 7) for a given date. + +{ + int t = (int)(floor(date)) + DateDelta; + return (t % 7) + 1; +} + +//============================================================================= + +int datetime_hourOfDay(DateTime date) + +// Input: date = an encoded date/time value +// Output: returns hour of day (0..23) +// Purpose: finds hour of day (0 = 12 AM, ..., 23 = 11 PM) for a given date. + +{ + int hour, min, sec; + datetime_decodeTime(date, &hour, &min, &sec); + return hour; +} + +//============================================================================= + +int datetime_daysPerMonth(int year, int month) + +// Input: year = year in which month falls +// month = month of year (1..12) +// Output: returns number of days in the month +// Purpose: finds number of days in a given month of a specified year. + +{ + if ( month < 1 || month > 12 ) return 0; + return DaysPerMonth[isLeapYear(year)][month-1]; +} + +//============================================================================= + +void datetime_getTimeStamp(int fmt, DateTime aDate, int stampSize, char* timeStamp) + +// Input: fmt = desired date format code +// aDate = a date/time value in decimal days +// stampSize = the number of bytes allocated for the time stamp +// Output: returns a time stamp string (e.g., Year-Month-Day Hr:Min:Sec) +// Purpose: Expresses a decimal day date by a time stamp. +{ + char dateStr[DATE_STR_SIZE]; + char timeStr[TIME_STR_SIZE]; + int oldDateFormat = DateFormat; + + if ( stampSize < TIME_STAMP_SIZE ) return; + datetime_setDateFormat(fmt); + datetime_dateToStr(aDate, dateStr); + DateFormat = oldDateFormat; + datetime_timeToStr(aDate, timeStr); + snprintf(timeStamp, stampSize, "%s %s", dateStr, timeStr); +} diff --git a/src/datetime.h b/src/datetime.h new file mode 100644 index 000000000..98b87b48e --- /dev/null +++ b/src/datetime.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// datetime.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// The DateTime type is used to store date and time values. It is +// equivalent to a double floating point type. +// +// The integral part of a DateTime value is the number of days that have +// passed since 12/31/1899. The fractional part of a DateTime value is the +// fraction of a 24 hour day that has elapsed. +// +// Update History +// ============== +// Build 5.1.011: +// - New getTimeStamp function added. +//----------------------------------------------------------------------------- + +#ifndef DATETIME_H +#define DATETIME_H + + +typedef double DateTime; + +#define Y_M_D 0 +#define M_D_Y 1 +#define D_M_Y 2 +#define NO_DATE -693594 // 1/1/0001 +#define DATE_STR_SIZE 12 +#define TIME_STR_SIZE 9 +#define TIME_STAMP_SIZE 21 + +// Functions for encoding a date or time value to a DateTime value +DateTime datetime_encodeDate(int year, int month, int day); +DateTime datetime_encodeTime(int hour, int minute, int second); + +// Functions for decoding a DateTime value to a date and time +void datetime_decodeDate(DateTime date, int* y, int* m, int* d); +void datetime_decodeTime(DateTime time, int* h, int* m, int* s); + +// Function for finding day of week for a date (1 = Sunday) +// month of year, days per month, and hour of day +int datetime_monthOfYear(DateTime date); +int datetime_dayOfYear(DateTime date); +int datetime_dayOfWeek(DateTime date); +int datetime_hourOfDay(DateTime date); +int datetime_daysPerMonth(int year, int month); + +// Functions for converting a DateTime value to a string +void datetime_dateToStr(DateTime date, char* s); +void datetime_timeToStr(DateTime time, char* s); +void datetime_getTimeStamp(int fmt, DateTime aDate, int stampSize, + char* timeStamp); + +// Functions for converting a string date or time to a DateTime value +int datetime_findMonth(char* s); +int datetime_strToDate(char* s, DateTime* d); +int datetime_strToTime(char* s, DateTime* t); + +// Function for setting date format +void datetime_setDateFormat(int fmt); + +// Functions for adding and subtracting dates +DateTime datetime_addSeconds(DateTime date1, double seconds); +DateTime datetime_addDays(DateTime date1, DateTime date2); +long datetime_timeDiff(DateTime date1, DateTime date2); + + +#endif //DATETIME_H diff --git a/src/dwflow.c b/src/dwflow.c new file mode 100644 index 000000000..d6484fe57 --- /dev/null +++ b/src/dwflow.c @@ -0,0 +1,684 @@ +//----------------------------------------------------------------------------- +// dwflow.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 08/01/22 (Build 5.2.1) +// Author: L. Rossman +// M. Tryby (EPA) +// R. Dickinson (CDM) +// +// Solves the momentum equation for flow in a conduit under dynamic wave +// flow routing. +// +// Update History +// ============== +// Build 5.1.008: +// - Bug in finding if conduit was upstrm/dnstrm full was fixed. +// Build 5.1.012: +// - Modified uniform loss rate term of conduit momentum equation. +// Build 5.1.013: +// - Preissmann slot surcharge option implemented. +// - Changed sign of uniform loss rate term (dq6) in flow updating equation. +// Build 5.1.014: +// - Conduit evap. and seepage loss initialized to 0 in dwflow_findConduitFlow. +// - Most current flow (qLast) used instead of previous time period flow +// (qOld) in call to link_getLossRate. +// Build 5.2.1: +// - Implements the new option to skip checking for normal flow limitations. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "headers.h" + +static const double MAXVELOCITY = 50.; // max. allowable velocity (ft/sec) + +static int getFlowClass(int link, double q, double h1, double h2, + double y1, double y2, double* criticalDepth, double* normalDepth, + double* fasnh); +static void findSurfArea(int link, double q, double length, double* h1, + double* h2, double* y1, double* y2); +static double findLocalLosses(int link, double a1, double a2, double aMid, + double q); + +static double getWidth(TXsect* xsect, double y); +static double getSlotWidth(TXsect* xsect, double y); +static double getArea(TXsect* xsect, double y, double wSlot); +static double getHydRad(TXsect* xsect, double y); + +static double checkNormalFlow(int j, double q, double y1, double y2, + double a1, double r1); + +//============================================================================= + +void dwflow_findConduitFlow(int j, int steps, double omega, double dt) +// +// Input: j = link index +// steps = number of iteration steps taken +// omega = under-relaxation parameter +// dt = time step (sec) +// Output: returns new flow value (cfs) +// Purpose: updates flow in conduit link by solving finite difference +// form of continuity and momentum equations. +// +{ + int k; // index of conduit + int n1, n2; // indexes of end nodes + double z1, z2; // upstream/downstream invert elev. (ft) + double h1, h2; // upstream/dounstream flow heads (ft) + double y1, y2; // upstream/downstream flow depths (ft) + double a1, a2; // upstream/downstream flow areas (ft2) + double r1; // upstream hyd. radius (ft) + double yMid, rMid, aMid; // mid-stream or avg. values of y, r, & a + double aWtd, rWtd; // upstream weighted area & hyd. radius + double qLast; // flow from previous iteration (cfs) + double qOld; // flow from previous time step (cfs) + double aOld; // area from previous time step (ft2) + double v; // velocity (ft/sec) + double rho; // upstream weighting factor + double sigma; // inertial damping factor + double length; // effective conduit length (ft) + double wSlot; // Preissmann slot width (ft) + double dq1, dq2, dq3, dq4, dq5, // terms in momentum eqn. + dq6; // term for evap and infil losses + double denom; // denominator of flow update formula + double q; // new flow value (cfs) + double barrels; // number of barrels in conduit + TXsect* xsect = &Link[j].xsect; // ptr. to conduit's cross section data + char isFull = FALSE; // TRUE if conduit flowing full + char isClosed = FALSE; // TRUE if conduit closed + + + + // --- adjust isClosed status by any control action + if ( Link[j].setting == 0 ) isClosed = TRUE; + + // --- get flow from last time step & previous iteration + k = Link[j].subIndex; + barrels = Conduit[k].barrels; + qOld = Link[j].oldFlow / barrels; + qLast = Conduit[k].q1; + Conduit[k].evapLossRate = 0.0; + Conduit[k].seepLossRate = 0.0; + + // --- get most current heads at upstream and downstream ends of conduit + n1 = Link[j].node1; + n2 = Link[j].node2; + z1 = Node[n1].invertElev + Link[j].offset1; + z2 = Node[n2].invertElev + Link[j].offset2; + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + h1 = MAX(h1, z1); + h2 = MAX(h2, z2); + + // --- get unadjusted upstream and downstream flow depths in conduit + // (flow depth = head in conduit - elev. of conduit invert) + y1 = h1 - z1; + y2 = h2 - z2; + y1 = MAX(y1, FUDGE); + y2 = MAX(y2, FUDGE); + + // --- flow depths can't exceed full depth of conduit if slot not used + if ( SurchargeMethod != SLOT ) + { + y1 = MIN(y1, xsect->yFull); + y2 = MIN(y2, xsect->yFull); + } + + // -- get area from solution at previous time step + aOld = Conduit[k].a2; + aOld = MAX(aOld, FUDGE); + + // --- use Courant-modified length instead of conduit's actual length + length = Conduit[k].modLength; + + // --- find surface area contributions to upstream and downstream nodes + // based on previous iteration's flow estimate + findSurfArea(j, qLast, length, &h1, &h2, &y1, &y2); + + // --- compute area at each end of conduit & hyd. radius at upstream end + wSlot = getSlotWidth(xsect, y1); + a1 = getArea(xsect, y1, wSlot); + r1 = getHydRad(xsect, y1); + wSlot = getSlotWidth(xsect, y2); + a2 = getArea(xsect, y2, wSlot); + + // --- compute area & hyd. radius at midpoint + yMid = 0.5 * (y1 + y2); + wSlot = getSlotWidth(xsect, yMid); + aMid = getArea(xsect, yMid, wSlot); + rMid = getHydRad(xsect, yMid); + + // --- alternate approach not currently used, but might produce better + // Bernoulli energy balance for steady flows + //aMid = (a1+a2)/2.0; + //rMid = (r1+getHydRad(xsect,y2))/2.0; + + // --- check if conduit is flowing full + if ( y1 >= xsect->yFull && + y2 >= xsect->yFull) isFull = TRUE; + + // --- set new flow to zero if conduit is dry or if flap gate is closed + if ( Link[j].flowClass == DRY || + Link[j].flowClass == UP_DRY || + Link[j].flowClass == DN_DRY || + isClosed || + aMid <= FUDGE ) + { + Conduit[k].a1 = 0.5 * (a1 + a2); + Conduit[k].q1 = 0.0;; + Conduit[k].q2 = 0.0; + Link[j].dqdh = GRAVITY * dt * aMid / length * barrels; + Link[j].froude = 0.0; + Link[j].newDepth = MIN(yMid, Link[j].xsect.yFull); + Link[j].newVolume = Conduit[k].a1 * link_getLength(j) * barrels; + Link[j].newFlow = 0.0; + return; + } + + // --- compute velocity from last flow estimate + v = qLast / aMid; + if ( fabs(v) > MAXVELOCITY ) v = MAXVELOCITY * SGN(qLast); + + // --- compute Froude No. + Link[j].froude = link_getFroude(j, v, yMid); + if ( Link[j].flowClass == SUBCRITICAL && + Link[j].froude > 1.0 ) Link[j].flowClass = SUPCRITICAL; + + // --- find inertial damping factor (sigma) + if ( Link[j].froude <= 0.5 ) sigma = 1.0; + else if ( Link[j].froude >= 1.0 ) sigma = 0.0; + else sigma = 2.0 * (1.0 - Link[j].froude); + + // --- get upstream-weighted area & hyd. radius based on damping factor + // (modified version of R. Dickinson's slope weighting) + rho = 1.0; + if ( !isFull && qLast > 0.0 && h1 >= h2 ) rho = sigma; + aWtd = a1 + (aMid - a1) * rho; + rWtd = r1 + (rMid - r1) * rho; + + // --- determine how much inertial damping to apply + if ( InertDamping == NO_DAMPING ) sigma = 1.0; + else if ( InertDamping == FULL_DAMPING ) sigma = 0.0; + + // --- use full inertial damping if closed conduit is surcharged + if ( isFull && !xsect_isOpen(xsect->type) ) sigma = 0.0; + + // --- compute terms of momentum eqn.: + // --- 1. friction slope term + if ( xsect->type == FORCE_MAIN && isFull ) + dq1 = dt * forcemain_getFricSlope(j, fabs(v), rMid); + else dq1 = dt * Conduit[k].roughFactor / pow(rWtd, 1.33333) * fabs(v); + + // --- 2. energy slope term + dq2 = dt * GRAVITY * aWtd * (h2 - h1) / length; + + // --- 3 & 4. inertial terms + dq3 = 0.0; + dq4 = 0.0; + if ( sigma > 0.0 ) + { + dq3 = 2.0 * v * (aMid - aOld) * sigma; + dq4 = dt * v * v * (a2 - a1) / length * sigma; + } + + // --- 5. local losses term + dq5 = 0.0; + if ( Conduit[k].hasLosses ) + { + dq5 = findLocalLosses(j, a1, a2, aMid, qLast) / 2.0 / length * dt; + } + + // --- 6. term for evap and seepage losses per unit length + dq6 = link_getLossRate(j, qLast) * 2.5 * dt * v / link_getLength(j); + + // --- combine terms to find new conduit flow + denom = 1.0 + dq1 + dq5; + q = (qOld - dq2 + dq3 + dq4 + dq6) / denom; + + // --- compute derivative of flow w.r.t. head + Link[j].dqdh = 1.0 / denom * GRAVITY * dt * aWtd / length * barrels; + + // --- check if any flow limitation applies + Link[j].inletControl = FALSE; + Link[j].normalFlow = FALSE; + if ( q > 0.0 ) + { + // --- check for inlet controlled culvert flow + if ( xsect->culvertCode > 0 && !isFull ) + q = culvert_getInflow(j, q, h1); + + // --- check for normal flow limitation based on surface slope & Fr + else if (NormalFlowLtd != NEITHER && y1 < Link[j].xsect.yFull && + ( Link[j].flowClass == SUBCRITICAL || + Link[j].flowClass == SUPCRITICAL )) + q = checkNormalFlow(j, q, y1, y2, a1, r1); + } + + // --- apply under-relaxation weighting between new & old flows; + // --- do not allow change in flow direction without first being zero + if ( steps > 0 ) + { + q = (1.0 - omega) * qLast + omega * q; + if ( q * qLast < 0.0 ) q = 0.001 * SGN(q); + } + + // --- check if user-supplied flow limit applies + if ( Link[j].qLimit > 0.0 ) + { + if ( fabs(q) > Link[j].qLimit ) q = SGN(q) * Link[j].qLimit; + } + + // --- check for reverse flow with closed flap gate + if ( link_setFlapGate(j, n1, n2, q) ) q = 0.0; + + // --- do not allow flow out of a dry node + // (as suggested by R. Dickinson) + if( q > FUDGE && Node[n1].newDepth <= FUDGE ) q = FUDGE; + if( q < -FUDGE && Node[n2].newDepth <= FUDGE ) q = -FUDGE; + + // --- save new values of area, flow, depth, & volume + Conduit[k].a1 = aMid; + Conduit[k].q1 = q; + Conduit[k].q2 = q; + Link[j].newDepth = MIN(yMid, xsect->yFull); + aMid = (a1 + a2) / 2.0; +// aMid = MIN(aMid, xsect->aFull); //Slot can have aMid > aFull + Conduit[k].fullState = link_getFullState(a1, a2, xsect->aFull); + Link[j].newVolume = aMid * link_getLength(j) * barrels; + Link[j].newFlow = q * barrels; +} + +//============================================================================= + +int getFlowClass(int j, double q, double h1, double h2, double y1, double y2, + double *yC, double *yN, double* fasnh) +// +// Input: j = conduit link index +// q = current conduit flow (cfs) +// h1 = head at upstream end of conduit (ft) +// h2 = head at downstream end of conduit (ft) +// y1 = upstream flow depth in conduit (ft) +// y2 = downstream flow depth in conduit (ft) +// yC = critical flow depth (ft) +// yN = normal flow depth (ft) +// fasnh = fraction between norm. & crit. depth +// Output: returns flow classification code +// Purpose: determines flow class for a conduit based on depths at each end. +// +{ + int n1, n2; // indexes of upstrm/downstrm nodes + int flowClass; // flow classification code + double ycMin, ycMax; // min/max critical depths (ft) + double z1, z2; // offsets of conduit inverts (ft) + + // --- get upstream & downstream node indexes + n1 = Link[j].node1; + n2 = Link[j].node2; + + // --- get upstream & downstream conduit invert offsets + z1 = Link[j].offset1; + z2 = Link[j].offset2; + + // --- base offset of an outfall conduit on outfall's depth + if ( Node[n1].type == OUTFALL ) z1 = MAX(0.0, (z1 - Node[n1].newDepth)); + if ( Node[n2].type == OUTFALL ) z2 = MAX(0.0, (z2 - Node[n2].newDepth)); + + // --- default class is SUBCRITICAL + flowClass = SUBCRITICAL; + *fasnh = 1.0; + + // --- case where both ends of conduit are wet + if ( y1 > FUDGE && y2 > FUDGE ) + { + if ( q < 0.0 ) + { + // --- upstream end at critical depth if flow depth is + // below conduit's critical depth and an upstream + // conduit offset exists + if ( z1 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + ycMin = MIN(*yN, *yC); + if ( y1 < ycMin ) flowClass = UP_CRITICAL; + } + } + + // --- case of normal direction flow + else + { + // --- downstream end at smaller of critical and normal depth + // if downstream flow depth below this and a downstream + // conduit offset exists + if ( z2 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + ycMin = MIN(*yN, *yC); + ycMax = MAX(*yN, *yC); + if ( y2 < ycMin ) flowClass = DN_CRITICAL; + else if ( y2 < ycMax ) + { + if ( ycMax - ycMin < FUDGE ) *fasnh = 0.0; + else *fasnh = (ycMax - y2) / (ycMax - ycMin); + } + } + } + } + + // --- case where no flow at either end of conduit + else if ( y1 <= FUDGE && y2 <= FUDGE ) flowClass = DRY; + + // --- case where downstream end of pipe is wet, upstream dry + else if ( y2 > FUDGE ) + { + // --- flow classification is UP_DRY if downstream head < + // invert of upstream end of conduit + if ( h2 < Node[n1].invertElev + Link[j].offset1 ) flowClass = UP_DRY; + + // --- otherwise, the downstream head will be >= upstream + // conduit invert creating a flow reversal and upstream end + // should be at critical depth, providing that an upstream + // offset exists (otherwise subcritical condition is maintained) + else if ( z1 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + flowClass = UP_CRITICAL; + } + } + + // --- case where upstream end of pipe is wet, downstream dry + else + { + // --- flow classification is DN_DRY if upstream head < + // invert of downstream end of conduit + if ( h1 < Node[n2].invertElev + Link[j].offset2 ) flowClass = DN_DRY; + + // --- otherwise flow at downstream end should be at critical depth + // providing that a downstream offset exists (otherwise + // subcritical condition is maintained) + else if ( z2 > 0.0 ) + { + *yN = link_getYnorm(j, fabs(q)); + *yC = link_getYcrit(j, fabs(q)); + flowClass = DN_CRITICAL; + } + } + return flowClass; +} + +//============================================================================= + +void findSurfArea(int j, double q, double length, double* h1, double* h2, + double* y1, double* y2) +// +// Input: j = conduit link index +// q = current conduit flow (cfs) +// length = conduit length (ft) +// h1 = head at upstream end of conduit (ft) +// h2 = head at downstream end of conduit (ft) +// y1 = upstream flow depth (ft) +// y2 = downstream flow depth (ft) +// Output: updated values of h1, h2, y1, & y2; +// Purpose: assigns surface area of conduit to its up and downstream nodes. +// +{ + int n1, n2; // indexes of upstrm/downstrm nodes + double flowDepth1; // flow depth at upstrm end (ft) + double flowDepth2; // flow depth at downstrm end (ft) + double flowDepthMid; // flow depth at midpt. (ft) + double width1; // top width at upstrm end (ft) + double width2; // top width at downstrm end (ft) + double widthMid; // top width at midpt. (ft) + double surfArea1 = 0.0; // surface area at upstream node (ft2) + double surfArea2 = 0.0; // surface area st downstrm node (ft2) + double criticalDepth; // critical flow depth (ft) + double normalDepth; // normal flow depth (ft) + double fullDepth; // full depth (ft) + double fasnh = 1.0; // fraction between norm. & crit. depth + TXsect* xsect = &Link[j].xsect; // pointer to cross-section data + + // --- get node indexes & current flow depths + n1 = Link[j].node1; + n2 = Link[j].node2; + flowDepth1 = *y1; + flowDepth2 = *y2; + + normalDepth = (flowDepth1 + flowDepth2) / 2.0; + criticalDepth = normalDepth; + + // --- find conduit's flow classification + fullDepth = xsect->yFull; + if (flowDepth1 >= fullDepth && flowDepth2 >= fullDepth) + { + Link[j].flowClass = SUBCRITICAL; + } + else Link[j].flowClass = getFlowClass(j, q, *h1, *h2, *y1, *y2, + &criticalDepth, &normalDepth, &fasnh); + + // --- add conduit's surface area to its end nodes depending on flow class + switch ( Link[j].flowClass ) + { + case SUBCRITICAL: + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width1 = getWidth(xsect, flowDepth1); + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + surfArea1 = (width1 + widthMid) * length / 4.; + surfArea2 = (widthMid + width2) * length / 4. * fasnh; + break; + + case UP_CRITICAL: + flowDepth1 = criticalDepth; + if ( normalDepth < criticalDepth ) flowDepth1 = normalDepth; + flowDepth1 = MAX(flowDepth1, FUDGE); + *h1 = Node[n1].invertElev + Link[j].offset1 + flowDepth1; + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + surfArea2 = (widthMid + width2) * length * 0.5; + break; + + case DN_CRITICAL: + flowDepth2 = criticalDepth; + if ( normalDepth < criticalDepth ) flowDepth2 = normalDepth; + flowDepth2 = MAX(flowDepth2, FUDGE); + *h2 = Node[n2].invertElev + Link[j].offset2 + flowDepth2; + width1 = getWidth(xsect, flowDepth1); + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + widthMid = getWidth(xsect, flowDepthMid); + surfArea1 = (width1 + widthMid) * length * 0.5; + break; + + case UP_DRY: + flowDepth1 = FUDGE; + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width1 = getWidth(xsect, flowDepth1); + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + + // --- assign avg. surface area of downstream half of conduit + // to the downstream node + surfArea2 = (widthMid + width2) * length / 4.; + + // --- if there is no free-fall at upstream end, assign the + // upstream node the avg. surface area of the upstream half + if ( Link[j].offset1 <= 0.0 ) + { + surfArea1 = (width1 + widthMid) * length / 4.; + } + break; + + case DN_DRY: + flowDepth2 = FUDGE; + flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); + if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; + width1 = getWidth(xsect, flowDepth1); + width2 = getWidth(xsect, flowDepth2); + widthMid = getWidth(xsect, flowDepthMid); + + // --- assign avg. surface area of upstream half of conduit + // to the upstream node + surfArea1 = (widthMid + width1) * length / 4.; + + // --- if there is no free-fall at downstream end, assign the + // downstream node the avg. surface area of the downstream half + if ( Link[j].offset2 <= 0.0 ) + { + surfArea2 = (width2 + widthMid) * length / 4.; + } + break; + + case DRY: + surfArea1 = FUDGE * length / 2.0; + surfArea2 = surfArea1; + break; + } + Link[j].surfArea1 = surfArea1; + Link[j].surfArea2 = surfArea2; + *y1 = flowDepth1; + *y2 = flowDepth2; +} + +//============================================================================= + +double findLocalLosses(int j, double a1, double a2, double aMid, double q) +// +// Input: j = link index +// a1 = upstream area (ft2) +// a2 = downstream area (ft2) +// aMid = midpoint area (ft2) +// q = flow rate (cfs) +// Output: returns local losses (ft/sec) +// Purpose: computes local losses term of momentum equation. +// +{ + double losses = 0.0; + q = fabs(q); + if ( a1 > FUDGE ) losses += Link[j].cLossInlet * (q/a1); + if ( a2 > FUDGE ) losses += Link[j].cLossOutlet * (q/a2); + if ( aMid > FUDGE ) losses += Link[j].cLossAvg * (q/aMid); + return losses; +} + +//============================================================================= + +double getSlotWidth(TXsect* xsect, double y) +{ + double yNorm = y / xsect->yFull; + + // --- return 0.0 if slot surcharge method not used + if (SurchargeMethod != SLOT || xsect_isOpen(xsect->type) || + yNorm < CrownCutoff) return 0.0; + + // --- for depth > 1.78 * pipe depth, slot width = 1% of max. width + if (yNorm > 1.78) return 0.01 * xsect->wMax; + + // --- otherwise use the Sjoberg formula + return xsect->wMax * 0.5423 * exp(-pow(yNorm, 2.4)); +} + +//============================================================================= + +double getWidth(TXsect* xsect, double y) +// +// Input: xsect = ptr. to conduit cross section +// y = flow depth (ft) +// Output: returns top width (ft) +// Purpose: computes top width of flow surface in conduit. +// +{ + double wSlot = getSlotWidth(xsect, y); + if (wSlot > 0.0) return wSlot; + if (y / xsect->yFull >= CrownCutoff && !xsect_isOpen(xsect->type)) + y = CrownCutoff * xsect->yFull; + return xsect_getWofY(xsect, y); +} + +//============================================================================= + +double getArea(TXsect* xsect, double y, double wSlot) +// +// Input: xsect = ptr. to conduit cross section +// y = flow depth (ft) +// Output: returns flow area (ft2) +// Purpose: computes area of flow cross-section in a conduit. +// +{ + if ( y >= xsect->yFull ) return xsect->aFull + (y - xsect->yFull) * wSlot; + return xsect_getAofY(xsect, y); +} + +//============================================================================= + +double getHydRad(TXsect* xsect, double y) +// +// Input: xsect = ptr. to conduit cross section +// y = flow depth (ft) +// Output: returns hydraulic radius (ft) +// Purpose: computes hydraulic radius of flow cross-section in a conduit. +// +{ + if (y >= xsect->yFull) return xsect->rFull; + return xsect_getRofY(xsect, y); +} + +//============================================================================= + +double checkNormalFlow(int j, double q, double y1, double y2, double a1, + double r1) +// +// Input: j = link index +// q = link flow found from dynamic wave equations (cfs) +// y1 = flow depth at upstream end (ft) +// y2 = flow depth at downstream end (ft) +// a1 = flow area at upstream end (ft2) +// r1 = hyd. radius at upstream end (ft) +// Output: returns modifed flow in link (cfs) +// Purpose: checks if flow in link should be replaced by normal flow. +// +{ + int check = FALSE; + int k = Link[j].subIndex; + int n1 = Link[j].node1; + int n2 = Link[j].node2; + int hasOutfall = (Node[n1].type == OUTFALL || Node[n2].type == OUTFALL); + double qNorm; + double f1; + + // --- check if water surface slope < conduit slope + if ( NormalFlowLtd == SLOPE || NormalFlowLtd == BOTH || hasOutfall ) + { + if ( y1 < y2) check = TRUE; + } + + // --- check if Fr >= 1.0 at upstream end of conduit + if ( !check && (NormalFlowLtd == FROUDE || NormalFlowLtd == BOTH) && + !hasOutfall ) + { + if ( y1 > FUDGE && y2 > FUDGE ) + { + f1 = link_getFroude(j, q/a1, y1); + if ( f1 >= 1.0 ) check = TRUE; + } + } + + // --- check if normal flow < dynamic flow + if ( check ) + { + qNorm = Conduit[k].beta * a1 * pow(r1, 2./3.); + if ( qNorm < q ) + { + Link[j].normalFlow = TRUE; + return qNorm; + } + } + return q; +} diff --git a/src/dynwave.c b/src/dynwave.c new file mode 100644 index 000000000..ac302e849 --- /dev/null +++ b/src/dynwave.c @@ -0,0 +1,908 @@ +//----------------------------------------------------------------------------- +// dynwave.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// M. Tryby (EPA) +// R. Dickinson (CDM) +// +// Dynamic wave flow routing functions. +// +// This module solves the dynamic wave flow routing equations using +// Picard Iterations (i.e., a method of successive approximations) +// to solve the explicit form of the continuity and momentum equations +// for conduits. +// +// Update History +// ============== +// Build 5.1.002: +// - Only non-ponded nodal surface area is saved for use in +// surcharge algorithm. +// Build 5.1.007: +// - Node losses added to node outflow variable instead of treated +// as a separate item when computing change in node flow volume. +// Build 5.1.008: +// - Module-specific constants moved here from project.c. +// - Support added for user-specified minimum variable time step. +// - Node crown elevations found here instead of in flowrout.c module. +// - OpenMP use to parallelize findLinkFlows() & findNodeDepths(). +// - Bug in finding complete list of capacity limited links fixed. +// Build 5.1.011: +// - Added test for failed memory allocation. +// - Fixed illegal array index bug for Ideal Pumps. +// Build 5.1.013: +// - Include omp.h protected against lack of compiler support for OpenMP. +// - SurchargeMethod option used to decide how node surcharging is handled. +// - Storage nodes allowed to pressurize if their surcharge depth > 0. +// - Minimum flow needed to compute a Courant time step modified. +// Build 5.1.014: +// - updateNodeFlows() modified to subtract conduit evap. and seepage losses +// from downstream node inflow instead of upstream node outflow. +// Build 5.1.015: +// - Roll back the 5.1.014 change for conduit losses in updateNodeFlows(). +// Build 5.2.0: +// - Support added for reporting most frequent non-converging links. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double MINTIMESTEP = 0.001; // min. time step (sec) +static const double OMEGA = 0.5; // under-relaxation parameter +static const double DEFAULT_SURFAREA = 12.566; // Min. nodal surface area (~4 ft diam.) +static const double DEFAULT_HEADTOL = 0.005; // Default head tolerance (ft) +static const double EXTRAN_CROWN_CUTOFF = 0.96; // crown cutoff for EXTRAN +static const double SLOT_CROWN_CUTOFF = 0.985257; // crown cutoff for SLOT +static const int DEFAULT_MAXTRIALS = 8; // Max. trials per time step + + +//----------------------------------------------------------------------------- +// Data Structures +//----------------------------------------------------------------------------- +typedef struct +{ + char converged; // TRUE if iterations for a node done + double newSurfArea; // current surface area (ft2) + double oldSurfArea; // previous surface area (ft2) + double sumdqdh; // sum of dqdh from adjoining links + double dYdT; // change in depth w.r.t. time (ft/sec) +} TXnode; + +//----------------------------------------------------------------------------- +// Shared Variables +//----------------------------------------------------------------------------- +static double VariableStep; // size of variable time step (sec) +static TXnode* Xnode; // extended nodal information + +static double Omega; // actual under-relaxation parameter +static int Steps; // number of Picard iterations + +//----------------------------------------------------------------------------- +// Function declarations +//----------------------------------------------------------------------------- +static void initRoutingStep(void); +static void initNodeStates(void); +static void findBypassedLinks(); +static void findLimitedLinks(); + +static void findLinkFlows(double dt); +static int isTrueConduit(int link); +static void findNonConduitFlow(int link, double dt); +static void findNonConduitSurfArea(int link); +static double getModPumpFlow(int link, double q, double dt); +static void updateNodeFlows(int link); +static void updateConvergenceStats(); + +static int findNodeDepths(double dt); +static void setNodeDepth(int node, double dt); +static double getFloodedDepth(int node, int canPond, double dV, double yNew, + double yMax, double dt); + +static double getVariableStep(double maxStep); +static double getLinkStep(double tMin, int *minLink); +static double getNodeStep(double tMin, int *minNode); + +//============================================================================= + +void dynwave_init() +// +// Input: none +// Output: none +// Purpose: initializes dynamic wave routing method. +// +{ + int i, j; + double z; + + VariableStep = 0.0; + Xnode = (TXnode *) calloc(Nobjects[NODE], sizeof(TXnode)); + if ( Xnode == NULL ) + { + report_writeErrorMsg(ERR_MEMORY, + " Not enough memory for dynamic wave routing."); + return; + } + + // --- initialize node surface areas & crown elev. + for (i = 0; i < Nobjects[NODE]; i++ ) + { + Xnode[i].newSurfArea = 0.0; + Xnode[i].oldSurfArea = 0.0; + Node[i].crownElev = Node[i].invertElev; + } + + // --- initialize links & update node crown elevations + for (i = 0; i < Nobjects[LINK]; i++) + { + j = Link[i].node1; + z = Node[j].invertElev + Link[i].offset1 + Link[i].xsect.yFull; + Node[j].crownElev = MAX(Node[j].crownElev, z); + j = Link[i].node2; + z = Node[j].invertElev + Link[i].offset2 + Link[i].xsect.yFull; + Node[j].crownElev = MAX(Node[j].crownElev, z); + Link[i].flowClass = DRY; + Link[i].dqdh = 0.0; + } + + // --- set crown cutoff for finding top width of closed conduits + if ( SurchargeMethod == SLOT ) CrownCutoff = SLOT_CROWN_CUTOFF; + else CrownCutoff = EXTRAN_CROWN_CUTOFF; +} + +//============================================================================= + +void dynwave_close() +// +// Input: none +// Output: none +// Purpose: frees memory allocated for dynamic wave routing method. +// +{ + FREE(Xnode); +} + +//============================================================================= + +void dynwave_validate() +// +// Input: none +// Output: none +// Purpose: adjusts dynamic wave routing options. +// +{ + if ( MinRouteStep > RouteStep ) MinRouteStep = RouteStep; + if ( MinRouteStep < MINTIMESTEP ) MinRouteStep = MINTIMESTEP; + if ( MinSurfArea == 0.0 ) MinSurfArea = DEFAULT_SURFAREA; + else MinSurfArea /= UCF(LENGTH) * UCF(LENGTH); + if ( HeadTol == 0.0 ) HeadTol = DEFAULT_HEADTOL; + else HeadTol /= UCF(LENGTH); + if ( MaxTrials == 0 ) MaxTrials = DEFAULT_MAXTRIALS; +} + +//============================================================================= + +double dynwave_getRoutingStep(double fixedStep) +// +// Input: fixedStep = user-supplied fixed time step (sec) +// Output: returns routing time step (sec) +// Purpose: computes variable routing time step if applicable. +// +{ + // --- use user-supplied fixed step if variable step option turned off + // or if its smaller than the min. allowable variable time step + if ( CourantFactor == 0.0 ) return fixedStep; + if ( fixedStep < MINTIMESTEP ) return fixedStep; + + // --- at start of simulation (when current variable step is zero) + // use the minimum allowable time step + if ( VariableStep == 0.0 ) + { + VariableStep = MinRouteStep; + } + + // --- otherwise compute variable step based on current flow solution + else VariableStep = getVariableStep(fixedStep); + + // --- adjust step to be a multiple of a millisecond + VariableStep = floor(1000.0 * VariableStep) / 1000.0; + return VariableStep; +} + +//============================================================================= + +int dynwave_execute(double tStep) +// +// Input: links = array of topo sorted links indexes +// tStep = time step (sec) +// Output: returns number of iterations used +// Purpose: routes flows through drainage network over current time step. +// +{ + int converged; + + // --- initialize + if ( ErrorCode ) return 0; + Steps = 0; + converged = FALSE; + Omega = OMEGA; + initRoutingStep(); + + // --- keep iterating until convergence + while ( Steps < MaxTrials ) + { + // --- execute a routing step & check for nodal convergence + initNodeStates(); + findLinkFlows(tStep); + converged = findNodeDepths(tStep); + Steps++; + if ( Steps > 1 ) + { + if ( converged ) break; + + // --- check if link calculations can be skipped in next step + findBypassedLinks(); + } + } + if ( !converged ) updateConvergenceStats(); + + // --- identify any capacity-limited conduits + findLimitedLinks(); + return Steps; +} + +//============================================================================= + +void updateConvergenceStats() +{ + int i; + NonConvergeCount++; + for (i = 0; i < Nobjects[NODE]; i++) + stats_updateConvergenceStats(i, Xnode[i].converged); +} + +//============================================================================= + +void initRoutingStep() +{ + int i; + for (i = 0; i < Nobjects[NODE]; i++) + { + Xnode[i].converged = FALSE; + Xnode[i].dYdT = 0.0; + } + for (i = 0; i < Nobjects[LINK]; i++) + { + Link[i].bypassed = FALSE; + Link[i].surfArea1 = 0.0; + Link[i].surfArea2 = 0.0; + } + + // --- a2 preserves conduit area from solution at last time step + for ( i = 0; i < Nlinks[CONDUIT]; i++) Conduit[i].a2 = Conduit[i].a1; +} + +//============================================================================= + +void initNodeStates() +// +// Input: none +// Output: none +// Purpose: initializes node's surface area, inflow & outflow +// +{ + int i; + + for (i = 0; i < Nobjects[NODE]; i++) + { + // --- initialize nodal surface area + if ( AllowPonding ) + { + Xnode[i].newSurfArea = node_getPondedArea(i, Node[i].newDepth); + } + else + { + Xnode[i].newSurfArea = node_getSurfArea(i, Node[i].newDepth); + } + + // --- initialize nodal inflow & outflow + Node[i].inflow = 0.0; + Node[i].outflow = Node[i].losses; + if ( Node[i].newLatFlow >= 0.0 ) + { + Node[i].inflow += Node[i].newLatFlow; + } + else + { + Node[i].outflow -= Node[i].newLatFlow; + } + Xnode[i].sumdqdh = 0.0; + } +} + +//============================================================================= + +void findBypassedLinks() +{ + int i; + for (i = 0; i < Nobjects[LINK]; i++) + { + if ( Xnode[Link[i].node1].converged && + Xnode[Link[i].node2].converged ) + Link[i].bypassed = TRUE; + else Link[i].bypassed = FALSE; + } +} + +//============================================================================= + +void findLimitedLinks() +// +// Input: none +// Output: none +// Purpose: determines if a conduit link is capacity limited. +// +{ + int j, n1, n2, k; + double h1, h2; + + for (j = 0; j < Nobjects[LINK]; j++) + { + // ---- check only non-dummy conduit links + if ( !isTrueConduit(j) ) continue; + + // --- check that upstream end is full + k = Link[j].subIndex; + Conduit[k].capacityLimited = FALSE; + if ( Conduit[k].a1 >= Link[j].xsect.aFull ) + { + // --- check if HGL slope > conduit slope + n1 = Link[j].node1; + n2 = Link[j].node2; + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + if ( (h1 - h2) > fabs(Conduit[k].slope) * Conduit[k].length ) + Conduit[k].capacityLimited = TRUE; + } + } +} + +//============================================================================= + +void findLinkFlows(double dt) +{ + int i; + + // --- find new flow in each non-dummy conduit +#pragma omp parallel num_threads(NumThreads) +{ + #pragma omp for + for ( i = 0; i < Nobjects[LINK]; i++) + { + if ( isTrueConduit(i) && !Link[i].bypassed ) + dwflow_findConduitFlow(i, Steps, Omega, dt); + } +} + + // --- update inflow/outflows for nodes attached to non-dummy conduits + for ( i = 0; i < Nobjects[LINK]; i++) + { + if ( isTrueConduit(i) ) updateNodeFlows(i); + } + + // --- find new flows for all dummy conduits, pumps & regulators + for ( i = 0; i < Nobjects[LINK]; i++) + { + if ( !isTrueConduit(i) ) + { + if ( !Link[i].bypassed ) findNonConduitFlow(i, dt); + updateNodeFlows(i); + } + } +} + +//============================================================================= + +int isTrueConduit(int j) +{ + return ( Link[j].type == CONDUIT && Link[j].xsect.type != DUMMY ); +} + +//============================================================================= + +void findNonConduitFlow(int i, double dt) +// +// Input: i = link index +// dt = time step (sec) +// Output: none +// Purpose: finds new flow in a non-conduit-type link +// +{ + double qLast; // previous link flow (cfs) + double qNew; // new link flow (cfs) + + // --- get link flow from last iteration + qLast = Link[i].newFlow; + Link[i].dqdh = 0.0; + + // --- get new inflow to link from its upstream node + // (link_getInflow returns 0 if flap gate closed or pump is offline) + qNew = link_getInflow(i); + if ( Link[i].type == PUMP ) qNew = getModPumpFlow(i, qNew, dt); + + // --- find surface area at each end of link + findNonConduitSurfArea(i); + + // --- apply under-relaxation with flow from previous iteration; + // --- do not allow flow to change direction without first being 0 + if ( Steps > 0 && Link[i].type != PUMP ) + { + qNew = (1.0 - Omega) * qLast + Omega * qNew; + if ( qNew * qLast < 0.0 ) qNew = 0.001 * SGN(qNew); + } + Link[i].newFlow = qNew; +} + +//============================================================================= + +double getModPumpFlow(int i, double q, double dt) +// +// Input: i = link index +// q = pump flow from pump curve (cfs) +// dt = time step (sec) +// Output: returns modified pump flow rate (cfs) +// Purpose: modifies pump curve pumping rate depending on amount of water +// available at pump's inlet node. +// +{ + int j = Link[i].node1; // pump's inlet node index + int k = Link[i].subIndex; // pump's index + double newNetInflow; // inflow - outflow rate (cfs) + double netFlowVolume; // inflow - outflow volume (ft3) + double y; // node depth (ft) + + if ( q == 0.0 ) return q; + + // --- case where inlet node is a storage node: + // prevent node volume from going negative + if ( Node[j].type == STORAGE ) return node_getMaxOutflow(j, q, dt); + + // --- case where inlet is a non-storage node + switch ( Pump[k].type ) + { + // --- for Type1 pump, a volume is computed for inlet node, + // so make sure it doesn't go negative + case TYPE1_PUMP: + return node_getMaxOutflow(j, q, dt); + + // --- for other types of pumps, if pumping rate would make depth + // at upstream node negative, then set pumping rate = inflow + case TYPE2_PUMP: + case TYPE4_PUMP: + case TYPE3_PUMP: + newNetInflow = Node[j].inflow - Node[j].outflow - q; + netFlowVolume = 0.5 * (Node[j].oldNetInflow + newNetInflow ) * dt; + y = Node[j].oldDepth + netFlowVolume / Xnode[j].newSurfArea; + if ( y <= 0.0 ) return Node[j].inflow; + } + return q; +} + +//============================================================================= + +void findNonConduitSurfArea(int i) +// +// Input: i = link index +// Output: none +// Purpose: finds the surface area contributed by a non-conduit +// link to its upstream and downstream nodes. +// +{ + if ( Link[i].type == ORIFICE ) + { + Link[i].surfArea1 = Orifice[Link[i].subIndex].surfArea / 2.; + } + + // --- no surface area for weirs to maintain SWMM 4 compatibility + else Link[i].surfArea1 = 0.0; + + Link[i].surfArea2 = Link[i].surfArea1; + if ( Link[i].flowClass == UP_CRITICAL || + Node[Link[i].node1].type == STORAGE ) Link[i].surfArea1 = 0.0; + if ( Link[i].flowClass == DN_CRITICAL || + Node[Link[i].node2].type == STORAGE ) Link[i].surfArea2 = 0.0; +} + +//============================================================================= + +void updateNodeFlows(int i) +// +// Input: i = link index +// q = link flow rate (cfs) +// Output: none +// Purpose: updates cumulative inflow & outflow at link's end nodes. +// +{ + int k; + int barrels = 1; + int n1 = Link[i].node1; + int n2 = Link[i].node2; + double q = Link[i].newFlow; + double uniformLossRate = 0.0; + + // --- compute any uniform seepage loss from a conduit + if ( Link[i].type == CONDUIT ) + { + k = Link[i].subIndex; + uniformLossRate = Conduit[k].evapLossRate + Conduit[k].seepLossRate; + barrels = Conduit[k].barrels; + uniformLossRate *= barrels; + } + + // --- update total inflow & outflow at upstream/downstream nodes + if ( q >= 0.0 ) + { + Node[n1].outflow += q + uniformLossRate; + Node[n2].inflow += q; + } + else + { + Node[n1].inflow -= q; + Node[n2].outflow -= q - uniformLossRate; + } + + // --- add surf. area contributions to upstream/downstream nodes + Xnode[Link[i].node1].newSurfArea += Link[i].surfArea1 * barrels; + Xnode[Link[i].node2].newSurfArea += Link[i].surfArea2 * barrels; + + // --- update summed value of dqdh at each end node + Xnode[Link[i].node1].sumdqdh += Link[i].dqdh; + if ( Link[i].type == PUMP ) + { + k = Link[i].subIndex; + if ( Pump[k].type != TYPE4_PUMP ) + { + Xnode[n2].sumdqdh += Link[i].dqdh; + } + } + else Xnode[n2].sumdqdh += Link[i].dqdh; +} + +//============================================================================= + +int findNodeDepths(double dt) +// +// Input: dt = time step (sec) +// Output: returns TRUE if depth change at all non-Outfall nodes is +// within the convergence tolerance and FALSE otherwise +// Purpose: finds new depth at all nodes and checks if convergence achieved. +// +{ + int i; + double yOld; // previous node depth (ft) + + // --- compute outfall depths based on flow in connecting link + for ( i = 0; i < Nobjects[LINK]; i++ ) link_setOutfallDepth(i); + + // --- compute new depth for all non-outfall nodes and determine if + // depth change from previous iteration is below tolerance +#pragma omp parallel num_threads(NumThreads) +{ + #pragma omp for private(yOld) + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + if ( Node[i].type == OUTFALL ) continue; + yOld = Node[i].newDepth; + setNodeDepth(i, dt); + Xnode[i].converged = TRUE; + if ( fabs(yOld - Node[i].newDepth) > HeadTol ) + { + Xnode[i].converged = FALSE; + } + } +} + + // --- return FALSE if any non-Outfall node failed to converge + for (i = 0; i < Nobjects[NODE]; i++) + { + if ( Node[i].type == OUTFALL ) continue; + if (Xnode[i].converged == FALSE) return FALSE; + } + return TRUE; +} + +//============================================================================= + +void setNodeDepth(int i, double dt) +// +// Input: i = node index +// dt = time step (sec) +// Output: none +// Purpose: sets depth at non-outfall node after current time step. +// +{ + int canPond; // TRUE if node can pond overflows + int isPonded; // TRUE if node is currently ponded + int isSurcharged = FALSE; // TRUE if node is surcharged + double dQ; // inflow minus outflow at node (cfs) + double dV; // change in node volume (ft3) + double dy; // change in node depth (ft) + double yMax; // max. depth at node (ft) + double yOld; // node depth at previous time step (ft) + double yLast; // previous node depth (ft) + double yNew; // new node depth (ft) + double yCrown; // depth to node crown (ft) + double surfArea; // node surface area (ft2) + double denom; // denominator term + double corr; // correction factor + double f; // relative surcharge depth + + // --- see if node can pond water above it + canPond = (AllowPonding && Node[i].pondedArea > 0.0); + isPonded = (canPond && Node[i].newDepth > Node[i].fullDepth); + + // --- initialize values + yCrown = Node[i].crownElev - Node[i].invertElev; + yOld = Node[i].oldDepth; + yLast = Node[i].newDepth; + Node[i].overflow = 0.0; + surfArea = Xnode[i].newSurfArea; + surfArea = MAX(surfArea, MinSurfArea); + + // --- determine average net flow volume into node over the time step + dQ = Node[i].inflow - Node[i].outflow; + dV = 0.5 * (Node[i].oldNetInflow + dQ) * dt; + + + // --- determine if node is EXTRAN surcharged + if (SurchargeMethod == EXTRAN) + { + // --- ponded nodes don't surcharge + if (isPonded) isSurcharged = FALSE; + + // --- closed storage units that are full are in surcharge + else if (Node[i].type == STORAGE) + { + isSurcharged = (Node[i].surDepth > 0.0 && + yLast > Node[i].fullDepth); + } + + // --- surcharge occurs when node depth exceeds top of its highest link + else isSurcharged = (yCrown > 0.0 && yLast > yCrown); + } + + // --- if node not surcharged, base depth change on surface area + if (!isSurcharged) + { + dy = dV / surfArea; + yNew = yOld + dy; + + // --- save non-ponded surface area for use in surcharge algorithm + if ( !isPonded ) Xnode[i].oldSurfArea = surfArea; + + // --- apply under-relaxation to new depth estimate + if ( Steps > 0 ) + { + yNew = (1.0 - Omega) * yLast + Omega * yNew; + } + + // --- don't allow a ponded node to drop much below full depth + if ( isPonded && yNew < Node[i].fullDepth ) + yNew = Node[i].fullDepth - FUDGE; + } + + // --- if node surcharged, base depth change on dqdh + // NOTE: depth change is w.r.t depth from previous + // iteration; also, do not apply under-relaxation. + else + { + // --- apply correction factor for upstream terminal nodes + corr = 1.0; + if ( Node[i].degree < 0 ) corr = 0.6; + + // --- allow surface area from last non-surcharged condition + // to influence dqdh if depth close to crown depth + denom = Xnode[i].sumdqdh; + if ( yLast < 1.25 * yCrown ) + { + f = (yLast - yCrown) / yCrown; + denom += (Xnode[i].oldSurfArea/dt - + Xnode[i].sumdqdh) * exp(-15.0 * f); + } + + // --- compute new estimate of node depth + if ( denom == 0.0 ) dy = 0.0; + else dy = corr * dQ / denom; + yNew = yLast + dy; + if ( yNew < yCrown ) yNew = yCrown - FUDGE; + + // --- don't allow a newly ponded node to rise much above full depth + if ( canPond && yNew > Node[i].fullDepth ) + yNew = Node[i].fullDepth + FUDGE; + } + + // --- depth cannot be negative + if ( yNew < 0 ) yNew = 0.0; + + // --- determine max. non-flooded depth + yMax = Node[i].fullDepth; + if ( canPond == FALSE ) yMax += Node[i].surDepth; + + // --- find flooded depth & volume + if ( yNew > yMax ) + { + yNew = getFloodedDepth(i, canPond, dV, yNew, yMax, dt); + } + else Node[i].newVolume = node_getVolume(i, yNew); + + // --- compute change in depth w.r.t. time + Xnode[i].dYdT = fabs(yNew - yOld) / dt; + + // --- save new depth for node + Node[i].newDepth = yNew; +} + +//============================================================================= + +double getFloodedDepth(int i, int canPond, double dV, double yNew, + double yMax, double dt) +// +// Input: i = node index +// canPond = TRUE if water can pond over node +// isPonded = TRUE if water is currently ponded +// dV = change in volume over time step (ft3) +// yNew = current depth at node (ft) +// yMax = max. depth at node before ponding (ft) +// dt = time step (sec) +// Output: returns depth at node when flooded (ft) +// Purpose: computes depth, volume and overflow for a flooded node. +// +{ + if ( canPond == FALSE ) + { + Node[i].overflow = dV / dt; + Node[i].newVolume = Node[i].fullVolume; + yNew = yMax; + } + else + { + Node[i].newVolume = MAX((Node[i].oldVolume+dV), Node[i].fullVolume); + Node[i].overflow = (Node[i].newVolume - + MAX(Node[i].oldVolume, Node[i].fullVolume)) / dt; + } + if ( Node[i].overflow < FUDGE ) Node[i].overflow = 0.0; + return yNew; + +} + +//============================================================================= + +double getVariableStep(double maxStep) +// +// Input: maxStep = user-supplied max. time step (sec) +// Output: returns time step (sec) +// Purpose: finds time step that satisfies stability criterion but +// is no greater than the user-supplied max. time step. +// +{ + int minLink = -1; // index of link w/ min. time step + int minNode = -1; // index of node w/ min. time step + double tMin; // allowable time step (sec) + double tMinLink; // allowable time step for links (sec) + double tMinNode; // allowable time step for nodes (sec) + + // --- find stable time step for links & then nodes + tMin = maxStep; + tMinLink = getLinkStep(tMin, &minLink); + tMinNode = getNodeStep(tMinLink, &minNode); + + // --- use smaller of the link and node time step + tMin = tMinLink; + if ( tMinNode < tMin ) + { + tMin = tMinNode ; + minLink = -1; + } + + // --- update count of times the minimum node or link was critical + stats_updateCriticalTimeCount(minNode, minLink); + + // --- don't let time step go below an absolute minimum + if ( tMin < MinRouteStep ) tMin = MinRouteStep; + return tMin; +} + +//============================================================================= + +double getLinkStep(double tMin, int *minLink) +// +// Input: tMin = critical time step found so far (sec) +// Output: minLink = index of link with critical time step; +// returns critical time step (sec) +// Purpose: finds critical time step for conduits based on Courant criterion. +// +{ + int i; // link index + int k; // conduit index + double q; // conduit flow (cfs) + double t; // time step (sec) + double tLink = tMin; // critical link time step (sec) + + // --- examine each conduit link + for ( i = 0; i < Nobjects[LINK]; i++ ) + { + if ( Link[i].type == CONDUIT ) + { + // --- skip conduits with negligible flow, area or Fr + k = Link[i].subIndex; + q = fabs(Link[i].newFlow) / Conduit[k].barrels; + if ( q <= FUDGE + || Conduit[k].a1 <= FUDGE + || Link[i].froude <= 0.01 + ) continue; + + // --- compute time step to satisfy Courant condition + t = Link[i].newVolume / Conduit[k].barrels / q; + t = t * Conduit[k].modLength / link_getLength(i); + t = t * Link[i].froude / (1.0 + Link[i].froude) * CourantFactor; + + // --- update critical link time step + if ( t < tLink ) + { + tLink = t; + *minLink = i; + } + } + } + return tLink; +} + +//============================================================================= + +double getNodeStep(double tMin, int *minNode) +// +// Input: tMin = critical time step found so far (sec) +// Output: minNode = index of node with critical time step; +// returns critical time step (sec) +// Purpose: finds critical time step for nodes based on max. allowable +// projected change in depth. +// +{ + int i; // node index + double maxDepth; // max. depth allowed at node (ft) + double dYdT; // change in depth per unit time (ft/sec) + double t1; // time needed to reach depth limit (sec) + double tNode = tMin; // critical node time step (sec) + + // --- find smallest time so that estimated change in nodal depth + // does not exceed safety factor * maxdepth + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + // --- see if node can be skipped + if ( Node[i].type == OUTFALL ) continue; + if ( Node[i].newDepth <= FUDGE) continue; + if ( Node[i].newDepth + FUDGE >= + Node[i].crownElev - Node[i].invertElev ) continue; + + // --- define max. allowable depth change using crown elevation + maxDepth = (Node[i].crownElev - Node[i].invertElev) * 0.25; + if ( maxDepth < FUDGE ) continue; + dYdT = Xnode[i].dYdT; + if (dYdT < FUDGE ) continue; + + // --- compute time to reach max. depth & compare with critical time + t1 = maxDepth / dYdT; + if ( t1 < tNode ) + { + tNode = t1; + *minNode = i; + } + } + return tNode; +} diff --git a/src/enums.h b/src/enums.h new file mode 100644 index 000000000..c260e4150 --- /dev/null +++ b/src/enums.h @@ -0,0 +1,500 @@ +//----------------------------------------------------------------------------- +// enums.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 06/01/22 (Build 5.2.1) +// Author: L. Rossman +// +// Enumerated constants +// +// Update History +// ============== +// Build 5.1.004: +// - IGNORE_RDII for the ignore RDII option added. +// Build 5.1.007: +// - s_GWF for [GWF] input file section added. +// - s_ADJUST for [ADJUSTMENTS] input file section added. +// Build 5.1.008: +// - Enumerations for fullness state of a conduit added. +// - NUM_THREADS added for number of parallel threads option. +// - Runoff flow categories added to represent mass balance components. +// Build 5.1.010: +// - New ROADWAY_WEIR type of weir added. +// - Potential evapotranspiration (PET) added as a system output variable. +// Build 5.1.011: +// - s_EVENT added to InputSectionType enumeration. +// Build 5.1.013: +// - SURCHARGE_METHOD and RULE_STEP options added. +// - WEIR_CURVE added as a curve type. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for variable speed pumps. +// - Support added for analytical storage shapes. +// Build 5.2.1: +// - Adds a NEITHER option to the NormalFlowType enumeration. +//----------------------------------------------------------------------------- + +#ifndef ENUMS_H +#define ENUMS_H + + +//------------------------------------- +// Names of major object types +//------------------------------------- + enum ObjectType { + GAGE, // rain gage + SUBCATCH, // subcatchment + NODE, // conveyance system node + LINK, // conveyance system link + POLLUT, // pollutant + LANDUSE, // land use category + TIMEPATTERN, // dry weather flow time pattern + CURVE, // generic table of values + TSERIES, // generic time series of values + CONTROL, // conveyance system control rules + TRANSECT, // irregular channel cross-section + AQUIFER, // groundwater aquifer + UNITHYD, // RDII unit hydrograph + SNOWMELT, // snowmelt parameter set + SHAPE, // custom conduit shape + LID, // LID treatment units + STREET, // street cross section + INLET, // street inlet design + MAX_OBJ_TYPES}; + +//------------------------------------- +// Names of Node sub-types +//------------------------------------- + #define MAX_NODE_TYPES 5 + enum NodeType { + JUNCTION, + OUTFALL, + STORAGE, + DIVIDER}; + +//------------------------------------- +// Names of Link sub-types +//------------------------------------- + #define MAX_LINK_TYPES 5 + enum LinkType { + CONDUIT, + PUMP, + ORIFICE, + WEIR, + OUTLET}; + +//------------------------------------- +// File types +//------------------------------------- + enum FileType { + RAINFALL_FILE, // rainfall file + RUNOFF_FILE, // runoff file + HOTSTART_FILE, // hotstart file + RDII_FILE, // RDII file + INFLOWS_FILE, // inflows interface file + OUTFLOWS_FILE}; // outflows interface file + +//------------------------------------- +// File usage types +//------------------------------------- + enum FileUsageType { + NO_FILE, // no file usage + SCRATCH_FILE, // use temporary scratch file + USE_FILE, // use previously saved file + SAVE_FILE}; // save file currently in use + +//------------------------------------- +// Rain gage data types +//------------------------------------- + enum GageDataType { + RAIN_TSERIES, // rainfall from user-supplied time series + RAIN_FILE}; // rainfall from external file + +//------------------------------------- +// Cross section shape types +//------------------------------------- + enum XsectType { + DUMMY, // 0 + CIRCULAR, // 1 closed + FILLED_CIRCULAR, // 2 closed + RECT_CLOSED, // 3 closed + RECT_OPEN, // 4 + TRAPEZOIDAL, // 5 + TRIANGULAR, // 6 + PARABOLIC, // 7 + POWERFUNC, // 8 + RECT_TRIANG, // 9 + RECT_ROUND, // 10 + MOD_BASKET, // 11 + HORIZ_ELLIPSE, // 12 closed + VERT_ELLIPSE, // 13 closed + ARCH, // 14 closed + EGGSHAPED, // 15 closed + HORSESHOE, // 16 closed + GOTHIC, // 17 closed + CATENARY, // 18 closed + SEMIELLIPTICAL, // 19 closed + BASKETHANDLE, // 20 closed + SEMICIRCULAR, // 21 closed + IRREGULAR, // 22 + CUSTOM, // 23 closed + FORCE_MAIN, // 24 closed + STREET_XSECT}; // 25 + +//------------------------------------- +// Measurement units types +//------------------------------------- + enum UnitsType { + US, // US units + SI}; // SI (metric) units + + enum FlowUnitsType { + CFS, // cubic feet per second + GPM, // gallons per minute + MGD, // million gallons per day + CMS, // cubic meters per second + LPS, // liters per second + MLD}; // million liters per day + + enum ConcUnitsType { + MG, // Milligrams / L + UG, // Micrograms / L + COUNT}; // Counts / L + +//-------------------------------------- +// Quantities requiring unit conversions +//-------------------------------------- + enum ConversionType { + RAINFALL, + RAINDEPTH, + EVAPRATE, + LENGTH, + LANDAREA, + VOLUME, + WINDSPEED, + TEMPERATURE, + MASS, + GWFLOW, + FLOW}; // Flow must always be listed last + +//------------------------------------- +// Computed subcatchment quantities +//------------------------------------- + #define MAX_SUBCATCH_RESULTS 9 + enum SubcatchResultType { + SUBCATCH_RAINFALL, // rainfall intensity + SUBCATCH_SNOWDEPTH, // snow depth + SUBCATCH_EVAP, // evap loss + SUBCATCH_INFIL, // infil loss + SUBCATCH_RUNOFF, // runoff flow rate + SUBCATCH_GW_FLOW, // groundwater flow rate to node + SUBCATCH_GW_ELEV, // elevation of saturated gw table + SUBCATCH_SOIL_MOIST, // soil moisture + SUBCATCH_WASHOFF}; // pollutant washoff concentration + +//------------------------------------- +// Computed node quantities +//------------------------------------- + #define MAX_NODE_RESULTS 7 + enum NodeResultType { + NODE_DEPTH, // water depth above invert + NODE_HEAD, // hydraulic head + NODE_VOLUME, // volume stored & ponded + NODE_LATFLOW, // lateral inflow rate + NODE_INFLOW, // total inflow rate + NODE_OVERFLOW, // overflow rate + NODE_QUAL}; // concentration of each pollutant + +//------------------------------------- +// Computed link quantities +//------------------------------------- + #define MAX_LINK_RESULTS 6 + enum LinkResultType { + LINK_FLOW, // flow rate + LINK_DEPTH, // flow depth + LINK_VELOCITY, // flow velocity + LINK_VOLUME, // link volume + LINK_CAPACITY, // ratio of area to full area + LINK_QUAL}; // concentration of each pollutant + +//------------------------------------- +// System-wide quantities +//------------------------------------- +#define MAX_SYS_RESULTS 15 +enum SysFlowType { + SYS_TEMPERATURE, // air temperature + SYS_RAINFALL, // rainfall intensity + SYS_SNOWDEPTH, // snow depth + SYS_INFIL, // infil + SYS_RUNOFF, // runoff flow + SYS_DWFLOW, // dry weather inflow + SYS_GWFLOW, // ground water inflow + SYS_IIFLOW, // RDII inflow + SYS_EXFLOW, // external inflow + SYS_INFLOW, // total lateral inflow + SYS_FLOODING, // flooding outflow + SYS_OUTFLOW, // outfall outflow + SYS_STORAGE, // storage volume + SYS_EVAP, // evaporation + SYS_PET}; // potential ET + +//------------------------------------- +// Conduit flow classifications +//------------------------------------- + enum FlowClassType { + DRY, // dry conduit + UP_DRY, // upstream end is dry + DN_DRY, // downstream end is dry + SUBCRITICAL, // sub-critical flow + SUPCRITICAL, // super-critical flow + UP_CRITICAL, // free-fall at upstream end + DN_CRITICAL, // free-fall at downstream end + MAX_FLOW_CLASSES, // number of distinct flow classes + UP_FULL, // upstream end is full + DN_FULL, // downstream end is full + ALL_FULL}; // completely full + +//------------------------ +// Runoff flow categories +//------------------------ +enum RunoffFlowType { + RUNOFF_RAINFALL, // rainfall + RUNOFF_EVAP, // evaporation + RUNOFF_INFIL, // infiltration + RUNOFF_RUNOFF, // runoff + RUNOFF_DRAINS, // LID drain flow + RUNOFF_RUNON}; // runon from outfalls + +//------------------------------------- +// Surface pollutant loading categories +//------------------------------------- + enum LoadingType { + BUILDUP_LOAD, // pollutant buildup load + DEPOSITION_LOAD, // rainfall deposition load + SWEEPING_LOAD, // load removed by sweeping + BMP_REMOVAL_LOAD, // load removed by BMPs + INFIL_LOAD, // runon load removed by infiltration + RUNOFF_LOAD, // load removed by runoff + FINAL_LOAD}; // load remaining on surface + +//------------------------------------- +// Input data options +//------------------------------------- + enum RainfallType { + RAINFALL_INTENSITY, // rainfall expressed as intensity + RAINFALL_VOLUME, // rainfall expressed as volume + CUMULATIVE_RAINFALL}; // rainfall expressed as cumulative volume + + enum TempType { + NO_TEMP, // no temperature data supplied + TSERIES_TEMP, // temperatures come from time series + FILE_TEMP}; // temperatures come from file + +enum WindType { + MONTHLY_WIND, // wind speed varies by month + FILE_WIND}; // wind speed comes from file + + enum EvapType { + CONSTANT_EVAP, // constant evaporation rate + MONTHLY_EVAP, // evaporation rate varies by month + TIMESERIES_EVAP, // evaporation supplied by time series + TEMPERATURE_EVAP, // evaporation from daily temperature + FILE_EVAP, // evaporation comes from file + RECOVERY, // soil recovery pattern + DRYONLY}; // evap. allowed only in dry periods + + enum NormalizerType { + PER_AREA, // buildup is per unit of area + PER_CURB}; // buildup is per unit of curb length + + enum BuildupType { + NO_BUILDUP, // no buildup + POWER_BUILDUP, // power function buildup equation + EXPON_BUILDUP, // exponential function buildup equation + SATUR_BUILDUP, // saturation function buildup equation + EXTERNAL_BUILDUP}; // external time series buildup + + enum WashoffType { + NO_WASHOFF, // no washoff + EXPON_WASHOFF, // exponential washoff equation + RATING_WASHOFF, // rating curve washoff equation + EMC_WASHOFF}; // event mean concentration washoff + +enum SubAreaType { + IMPERV0, // impervious w/o depression storage + IMPERV1, // impervious w/ depression storage + PERV}; // pervious + + enum RunoffRoutingType { + TO_OUTLET, // perv & imperv runoff goes to outlet + TO_IMPERV, // perv runoff goes to imperv area + TO_PERV}; // imperv runoff goes to perv subarea + + enum RouteModelType { + NO_ROUTING, // no routing + SF, // steady flow model + KW, // kinematic wave model + EKW, // extended kin. wave model + DW}; // dynamic wave model + + enum ForceMainType { + H_W, // Hazen-Williams eqn. + D_W}; // Darcy-Weisbach eqn. + + enum OffsetType { + DEPTH_OFFSET, // offset measured as depth + ELEV_OFFSET}; // offset measured as elevation + + enum KinWaveMethodType { + NORMAL, // normal method + MODIFIED}; // modified method + +enum CompatibilityType { + SWMM5, // SWMM 5 weighting for area & hyd. radius + SWMM3, // SWMM 3 weighting + SWMM4}; // SWMM 4 weighting + + enum NormalFlowType { + SLOPE, // based on slope only + FROUDE, // based on Fr only + BOTH, // based on slope & Fr + NEITHER}; + + enum InertialDampingType { + NO_DAMPING, // no inertial damping + PARTIAL_DAMPING, // partial damping + FULL_DAMPING}; // full damping + + enum SurchargeMethodType { + EXTRAN, // original EXTRAN method + SLOT}; // Preissmann slot method + + enum InflowType { + EXTERNAL_INFLOW, // user-supplied external inflow + DRY_WEATHER_INFLOW, // user-supplied dry weather inflow + WET_WEATHER_INFLOW, // computed runoff inflow + GROUNDWATER_INFLOW, // computed groundwater inflow + RDII_INFLOW, // computed I&I inflow + FLOW_INFLOW, // inflow parameter is flow + CONCEN_INFLOW, // inflow parameter is pollutant concen. + MASS_INFLOW}; // inflow parameter is pollutant mass + + enum PatternType { + MONTHLY_PATTERN, // DWF multipliers for each month + DAILY_PATTERN, // DWF multipliers for each day of week + HOURLY_PATTERN, // DWF multipliers for each hour of day + WEEKEND_PATTERN}; // hourly multipliers for week end days + + enum OutfallType { + FREE_OUTFALL, // critical depth outfall condition + NORMAL_OUTFALL, // normal flow depth outfall condition + FIXED_OUTFALL, // fixed depth outfall condition + TIDAL_OUTFALL, // variable tidal stage outfall condition + TIMESERIES_OUTFALL}; // variable time series outfall depth + + enum StorageType { + TABULAR, // area v. depth from table + FUNCTIONAL, // area v. depth from power function + CYLINDRICAL, // area v. depth from elliptical cylinder + CONICAL, // area v. depth from elliptical cone + PARABOLOID, // area v. depth from elliptical paraboloid + PYRAMIDAL}; // area v. depth from rectangular pyramid + + enum ReactorType { + CSTR, // completely mixed reactor + PLUG}; // plug flow reactor + + enum TreatmentType { + REMOVAL, // treatment stated as a removal + CONCEN}; // treatment stated as effluent concen. + + enum DividerType { + CUTOFF_DIVIDER, // diverted flow is excess of cutoff flow + TABULAR_DIVIDER, // table of diverted flow v. inflow + WEIR_DIVIDER, // diverted flow proportional to excess flow + OVERFLOW_DIVIDER}; // diverted flow is flow > full conduit flow + + enum PumpCurveType { + TYPE1_PUMP, // flow varies stepwise with wet well volume + TYPE2_PUMP, // flow varies stepwise with inlet depth + TYPE3_PUMP, // flow varies with head delivered + TYPE4_PUMP, // flow varies with inlet depth + TYPE5_PUMP, // variable speed version of TYPE3 pump + IDEAL_PUMP}; // outflow equals inflow + + enum OrificeType { + SIDE_ORIFICE, // side orifice + BOTTOM_ORIFICE}; // bottom orifice + + enum WeirType { + TRANSVERSE_WEIR, // transverse weir + SIDEFLOW_WEIR, // side flow weir + VNOTCH_WEIR, // V-notch (triangular) weir + TRAPEZOIDAL_WEIR, // trapezoidal weir + ROADWAY_WEIR}; // FHWA HDS-5 roadway weir + + enum CurveType { + STORAGE_CURVE, // surf. area v. depth for storage node + DIVERSION_CURVE, // diverted flow v. inflow for divider node + TIDAL_CURVE, // water elev. v. hour of day for outfall + RATING_CURVE, // flow rate v. head for outlet link + CONTROL_CURVE, // control setting v. controller variable + SHAPE_CURVE, // width v. depth for custom x-section + WEIR_CURVE, // discharge coeff. v. head for weir + PUMP1_CURVE, // flow v. wet well volume for pump + PUMP2_CURVE, // flow v. depth for pump (discrete) + PUMP3_CURVE, // flow v. head for pump (continuous) + PUMP4_CURVE, // flow v. depth for pump (continuous) + PUMP5_CURVE}; // variable speed version of TYPE3 pump + + enum NodeInletType { + NO_INLET, + BYPASS, + CAPTURE + }; + + enum InputSectionType { + s_TITLE, s_OPTION, s_FILE, s_RAINGAGE, + s_TEMP, s_EVAP, s_SUBCATCH, s_SUBAREA, + s_INFIL, s_AQUIFER, s_GROUNDWATER, s_SNOWMELT, + s_JUNCTION, s_OUTFALL, s_STORAGE, s_DIVIDER, + s_CONDUIT, s_PUMP, s_ORIFICE, s_WEIR, + s_OUTLET, s_XSECTION, s_TRANSECT, s_LOSSES, + s_CONTROL, s_POLLUTANT, s_LANDUSE, s_BUILDUP, + s_WASHOFF, s_COVERAGE, s_INFLOW, s_DWF, + s_PATTERN, s_RDII, s_UNITHYD, s_LOADING, + s_TREATMENT, s_CURVE, s_TIMESERIES, s_REPORT, + s_COORDINATE, s_VERTICES, s_POLYGON, s_LABEL, + s_SYMBOL, s_BACKDROP, s_TAG, s_PROFILE, + s_MAP, s_LID_CONTROL, s_LID_USAGE, s_GWF, + s_ADJUST, s_EVENT, s_STREET, s_INLET_USAGE, + s_INLET}; + + enum InputOptionType { + FLOW_UNITS, INFIL_MODEL, ROUTE_MODEL, + START_DATE, START_TIME, END_DATE, + END_TIME, REPORT_START_DATE, REPORT_START_TIME, + SWEEP_START, SWEEP_END, START_DRY_DAYS, + WET_STEP, DRY_STEP, ROUTE_STEP, RULE_STEP, + REPORT_STEP, ALLOW_PONDING, INERT_DAMPING, + SLOPE_WEIGHTING, VARIABLE_STEP, NORMAL_FLOW_LTD, + LENGTHENING_STEP, MIN_SURFAREA, COMPATIBILITY, + SKIP_STEADY_STATE, TEMPDIR, IGNORE_RAINFALL, + FORCE_MAIN_EQN, LINK_OFFSETS, MIN_SLOPE, + IGNORE_SNOWMELT, IGNORE_GWATER, IGNORE_ROUTING, + IGNORE_QUALITY, MAX_TRIALS, HEAD_TOL, + SYS_FLOW_TOL, LAT_FLOW_TOL, IGNORE_RDII, + MIN_ROUTE_STEP, NUM_THREADS, SURCHARGE_METHOD}; + +enum NoYesType { + NO, + YES}; + +enum NoneAllType { + NONE, + ALL, + SOME}; + + +#endif //ENUMS_H diff --git a/src/error.c b/src/error.c new file mode 100644 index 000000000..626950b6f --- /dev/null +++ b/src/error.c @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// error.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Error messages +// +// Update History +// ============== +// Build 5.1.008: +// - Text of Error 217 for control rules modified. +// Build 5.1.010: +// - Text of Error 318 for rainfall data files modified. +// Build 5.1.015: +// - Added new Error 140 for storage nodes. +// Build 5.2.0: +// - Re-designed error message system. +// - Added new Error 235 for invalid infiltration parameters. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "error.h" + +char ErrString[256]; + +char* error_getMsg(int errCode, char* msg) +{ + switch (errCode) + { + +#define ERR(code,string) case code: strcpy(msg, string); break; +#include "error.txt" +#undef ERR + + default: + strcpy(msg, ""); + } + return (msg); +}; + +int error_setInpError(int errcode, char* s) +{ + strcpy(ErrString, s); + return errcode; +} diff --git a/src/error.h b/src/error.h new file mode 100644 index 000000000..fedf0847e --- /dev/null +++ b/src/error.h @@ -0,0 +1,182 @@ +//----------------------------------------------------------------------------- +// error.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Error codes +// +//----------------------------------------------------------------------------- + +#ifndef ERROR_H +#define ERROR_H + +enum ErrorType { + +// ... Runtime Errors + ERR_NONE = 0, + ERR_MEMORY = 101, + ERR_KINWAVE = 103, + ERR_ODE_SOLVER = 105, + ERR_TIMESTEP = 107, + +// ... Subcatchment/Aquifer Errors + ERR_SUBCATCH_OUTLET = 108, + ERR_AQUIFER_PARAMS = 109, + ERR_GROUND_ELEV = 110, + +// ... Conduit/Pump Errors + ERR_LENGTH = 111, + ERR_ELEV_DROP = 112, + ERR_ROUGHNESS = 113, + ERR_BARRELS = 114, + ERR_SLOPE = 115, + ERR_NO_XSECT = 117, + ERR_XSECT = 119, + ERR_NO_CURVE = 121, + ERR_PUMP_LIMITS = 122, + +// ... Topology Errors + ERR_LOOP = 131, + ERR_MULTI_OUTLET = 133, + ERR_DUMMY_LINK = 134, + +// ... Node Errors + ERR_DIVIDER = 135, + ERR_DIVIDER_LINK = 136, + ERR_WEIR_DIVIDER = 137, + ERR_NODE_DEPTH = 138, + ERR_REGULATOR = 139, + ERR_STORAGE_VOLUME = 140, + ERR_OUTFALL = 141, + ERR_REGULATOR_SHAPE = 143, + ERR_NO_OUTLETS = 145, + +// ... RDII Errors + ERR_UNITHYD_TIMES = 151, + ERR_UNITHYD_RATIOS = 153, + ERR_RDII_AREA = 155, + +// ... Rain Gage Errors + ERR_RAIN_FILE_CONFLICT = 156, + ERR_RAIN_GAGE_FORMAT = 157, + ERR_RAIN_GAGE_TSERIES = 158, + ERR_RAIN_GAGE_INTERVAL = 159, + +// ... Treatment Function Error + ERR_CYCLIC_TREATMENT = 161, + +// ... Curve/Time Series Errors + ERR_CURVE_SEQUENCE = 171, + ERR_TIMESERIES_SEQUENCE = 173, + +// ... Snowmelt Errors + ERR_SNOWMELT_PARAMS = 181, + ERR_SNOWPACK_PARAMS = 182, + +// ... LID Errors + ERR_LID_TYPE = 183, + ERR_LID_LAYER = 184, + ERR_LID_PARAMS = 185, + ERR_LID_AREAS = 187, + ERR_LID_CAPTURE_AREA = 188, + +// ... Simulation Date/Time Errors + ERR_START_DATE = 191, + ERR_REPORT_DATE = 193, + ERR_REPORT_STEP = 195, + +// ... Input Parser Errors + ERR_INPUT = 200, + ERR_LINE_LENGTH = 201, + ERR_ITEMS = 203, + ERR_KEYWORD = 205, + ERR_DUP_NAME = 207, + ERR_NAME = 209, + ERR_NUMBER = 211, + ERR_DATETIME = 213, + ERR_RULE = 217, + ERR_TRANSECT_UNKNOWN = 219, + ERR_TRANSECT_SEQUENCE = 221, + ERR_TRANSECT_TOO_FEW = 223, + ERR_TRANSECT_TOO_MANY = 225, + ERR_TRANSECT_MANNING = 227, + ERR_TRANSECT_OVERBANK = 229, + ERR_TRANSECT_NO_DEPTH = 231, + ERR_MATH_EXPR = 233, + ERR_INFIL_PARAMS = 235, + +// ... File Name/Opening Errors + ERR_FILE_NAME = 301, + ERR_INP_FILE = 303, + ERR_RPT_FILE = 305, + ERR_OUT_FILE = 307, + ERR_OUT_SIZE = 308, + ERR_OUT_WRITE = 309, + ERR_OUT_READ = 311, + +// ... Rain File Errors + ERR_RAIN_FILE_SCRATCH = 313, + ERR_RAIN_FILE_OPEN = 315, + ERR_RAIN_FILE_DATA = 317, + ERR_RAIN_FILE_SEQUENCE = 318, + ERR_RAIN_FILE_FORMAT = 319, + ERR_RAIN_IFACE_FORMAT = 320, + ERR_RAIN_FILE_GAGE = 321, + +// ... Runoff File Errors + ERR_RUNOFF_FILE_OPEN = 323, + ERR_RUNOFF_FILE_FORMAT = 325, + ERR_RUNOFF_FILE_END = 327, + ERR_RUNOFF_FILE_READ = 329, + +// ... Hotstart File Errors + ERR_HOTSTART_FILE_OPEN = 331, + ERR_HOTSTART_FILE_FORMAT = 333, + ERR_HOTSTART_FILE_READ = 335, + +// ... Climate File Errors + ERR_NO_CLIMATE_FILE = 336, + ERR_CLIMATE_FILE_OPEN = 337, + ERR_CLIMATE_FILE_READ = 338, + ERR_CLIMATE_END_OF_FILE = 339, + +// ... RDII File Errors + ERR_RDII_FILE_SCRATCH = 341, + ERR_RDII_FILE_OPEN = 343, + ERR_RDII_FILE_FORMAT = 345, + +// ... Routing File Errors + ERR_ROUTING_FILE_OPEN = 351, + ERR_ROUTING_FILE_FORMAT = 353, + ERR_ROUTING_FILE_NOMATCH = 355, + ERR_ROUTING_FILE_NAMES = 357, + +// ... Time Series File Errors + ERR_TABLE_FILE_OPEN = 361, + ERR_TABLE_FILE_READ = 363, + +// ... Runtime Errors + ERR_SYSTEM = 500, + +// ... API Errors + ERR_API_NOT_OPEN = 501, + ERR_API_NOT_STARTED = 502, + ERR_API_NOT_ENDED = 503, + ERR_API_OBJECT_TYPE = 504, + ERR_API_OBJECT_INDEX = 505, + ERR_API_OBJECT_NAME = 506, + ERR_API_PROPERTY_TYPE = 507, + ERR_API_PROPERTY_VALUE = 508, + ERR_API_TIME_PERIOD = 509, + +// ... Additional Errors + MAXERRMSG = 1000 +}; + +char* error_getMsg(int i, char* msg); +int error_setInpError(int errcode, char* s); + +#endif //ERROR_H diff --git a/src/error.txt b/src/error.txt new file mode 100644 index 000000000..30d45a783 --- /dev/null +++ b/src/error.txt @@ -0,0 +1,135 @@ +// SWMM 5.2 Error Messages + +ERR(101,"\n ERROR 101: memory allocation error.") +ERR(103,"\n ERROR 103: cannot solve KW equations for Link %s.") +ERR(105,"\n ERROR 105: cannot open ODE solver.") +ERR(107,"\n ERROR 107: cannot compute a valid time step.") + +ERR(108,"\n ERROR 108: ambiguous outlet ID name for Subcatchment %s.") +ERR(109,"\n ERROR 109: invalid parameter values for Aquifer %s.") +ERR(110,"\n ERROR 110: ground elevation is below water table for Subcatchment %s.") + +ERR(111,"\n ERROR 111: invalid length for Conduit %s.") +ERR(112,"\n ERROR 112: elevation drop exceeds length for Conduit %s.") +ERR(113,"\n ERROR 113: invalid roughness for Conduit %s.") +ERR(114,"\n ERROR 114: invalid number of barrels for Conduit %s.") +ERR(115,"\n ERROR 115: adverse slope for Conduit %s.") +ERR(117,"\n ERROR 117: no cross section defined for Link %s.") +ERR(119,"\n ERROR 119: invalid cross section for Link %s.") +ERR(121,"\n ERROR 121: missing or invalid pump curve assigned to Pump %s.") +ERR(122,"\n ERROR 122: startup depth not higher than shutoff depth for Pump %s.") + +ERR(131,"\n ERROR 131: the following links form cyclic loops in the drainage system:") +ERR(133,"\n ERROR 133: Node %s has more than one outlet link.") +ERR(134,"\n ERROR 134: Node %s has illegal DUMMY link connections.") + +ERR(135,"\n ERROR 135: Divider %s does not have two outlet links.") +ERR(136,"\n ERROR 136: Divider %s has invalid diversion link.") +ERR(137,"\n ERROR 137: Weir Divider %s has invalid parameters.") +ERR(138,"\n ERROR 138: Node %s has initial depth greater than maximum depth.") +ERR(139,"\n ERROR 139: Regulator %s is the outlet of a non-storage node.") +ERR(140,"\n ERROR 140: Storage node %s has negative volume at full depth.") +ERR(141,"\n ERROR 141: Outfall %s has more than 1 inlet link or an outlet link.") +ERR(143,"\n ERROR 143: Regulator %s has invalid cross-section shape.") +ERR(145,"\n ERROR 145: Drainage system has no acceptable outlet nodes.") + +ERR(151,"\n ERROR 151: a Unit Hydrograph in set %s has invalid time base.") +ERR(153,"\n ERROR 153: a Unit Hydrograph in set %s has invalid response ratios.") +ERR(155,"\n ERROR 155: invalid sewer area for RDII at node %s.") + +ERR(156,"\n ERROR 156: ambiguous station ID for Rain Gage %s.") +ERR(157,"\n ERROR 157: inconsistent rainfall format for Rain Gage %s.") +ERR(158,"\n ERROR 158: time series for Rain Gage %s is also used by another object.") +ERR(159,"\n ERROR 159: recording interval greater than time series interval for Rain Gage %s.") + +ERR(161,"\n ERROR 161: cyclic dependency in treatment functions at node %s.") + +ERR(171,"\n ERROR 171: Curve %s has invalid or out of sequence data.") +ERR(173,"\n ERROR 173: Time Series %s has its data out of sequence.") + +ERR(181,"\n ERROR 181: invalid Snow Melt Climatology parameters.") +ERR(182,"\n ERROR 182: invalid parameters for Snow Pack %s.") + +ERR(183,"\n ERROR 183: no type specified for LID %s.") +ERR(184,"\n ERROR 184: missing layer for LID %s.") +ERR(185,"\n ERROR 185: invalid parameter value for LID %s.") +ERR(187,"\n ERROR 187: LID area exceeds total area for Subcatchment %s.") +ERR(188,"\n ERROR 188: LID capture area exceeds total impervious area for Subcatchment %s.") + +ERR(191,"\n ERROR 191: simulation start date comes after ending date.") +ERR(193,"\n ERROR 193: report start date comes after ending date.") +ERR(195,"\n ERROR 195: reporting time step or duration is less than routing time step.") + +ERR(200,"\n ERROR 200: one or more errors in input file.") +ERR(201,"\n ERROR 201: too many characters in input line ") +ERR(203,"\n ERROR 203: too few items ") +ERR(205,"\n ERROR 205: invalid keyword %s ") +ERR(207,"\n ERROR 207: duplicate ID name %s ") +ERR(209,"\n ERROR 209: undefined object %s ") +ERR(211,"\n ERROR 211: invalid number %s ") +ERR(213,"\n ERROR 213: invalid date/time %s ") +ERR(217,"\n ERROR 217: control rule clause invalid or out of sequence ") +ERR(219,"\n ERROR 219: data provided for unidentified transect ") +ERR(221,"\n ERROR 221: transect station out of sequence ") +ERR(223,"\n ERROR 223: Transect %s has too few stations.") +ERR(225,"\n ERROR 225: Transect %s has too many stations.") +ERR(227,"\n ERROR 227: Transect %s has no Manning's N.") +ERR(229,"\n ERROR 229: Transect %s has invalid overbank locations.") +ERR(231,"\n ERROR 231: Transect %s has no depth.") +ERR(233,"\n ERROR 233: invalid math expression ") +ERR(235,"\n ERROR 235: invalid infiltration parameters ") + +ERR(301,"\n ERROR 301: files share same names.") +ERR(303,"\n ERROR 303: cannot open input file.") +ERR(305,"\n ERROR 305: cannot open report file.") +ERR(307,"\n ERROR 307: cannot open binary results file.") +ERR(308,"\n ERROR 308: amount of output produced will exceed maximum file size.") + +ERR(309,"\n ERROR 309: error writing to binary results file.") +ERR(311,"\n ERROR 311: error reading from binary results file.") + +ERR(313,"\n ERROR 313: cannot open scratch rainfall interface file.") +ERR(315,"\n ERROR 315: cannot open rainfall interface file %s.") +ERR(317,"\n ERROR 317: cannot open rainfall data file %s.") +ERR(318,"\n ERROR 318: the following line is out of sequence in rainfall data file %s.") +ERR(319,"\n ERROR 319: unknown format for rainfall data file %s.") +ERR(320,"\n ERROR 320: invalid format for rainfall interface file.") +ERR(321,"\n ERROR 321: no data in rainfall interface file for gage %s.") + +ERR(323,"\n ERROR 323: cannot open runoff interface file %s.") +ERR(325,"\n ERROR 325: incompatible data found in runoff interface file.") +ERR(327,"\n ERROR 327: attempting to read beyond end of runoff interface file.") +ERR(329,"\n ERROR 329: error in reading from runoff interface file.") + +ERR(331,"\n ERROR 331: cannot open hot start interface file %s.") +ERR(333,"\n ERROR 333: incompatible data found in hot start interface file.") +ERR(335,"\n ERROR 335: error in reading from hot start interface file.") + +ERR(336,"\n ERROR 336: no climate file specified for evaporation and/or wind speed.") +ERR(337,"\n ERROR 337: cannot open climate file %s.") +ERR(338,"\n ERROR 338: error in reading from climate file %s.") +ERR(339,"\n ERROR 339: attempt to read beyond end of climate file %s.") + +ERR(341,"\n ERROR 341: cannot open scratch RDII interface file.") +ERR(343,"\n ERROR 343: cannot open RDII interface file %s.") +ERR(345,"\n ERROR 345: invalid format for RDII interface file.") + +ERR(351,"\n ERROR 351: cannot open routing interface file %s.") +ERR(353,"\n ERROR 353: invalid format for routing interface file %s.") +ERR(355,"\n ERROR 355: mis-matched names in routing interface file %s.") +ERR(357,"\n ERROR 357: inflows and outflows interface files have same name.") + +ERR(361,"\n ERROR 361: could not open external file used for Time Series %s.") +ERR(363,"\n ERROR 363: invalid data in external file used for Time Series %s.") + +// API Error Keys +ERR(500,"\n ERROR 500: System exception thrown.") +ERR(501,"\n API Error 501: project not opened.") +ERR(502,"\n API Error 502: simulation not started.") +ERR(503,"\n API Error 503: simulation not ended.") +ERR(504,"\n API Error 504: invalid object type.") +ERR(505,"\n API Error 505: invalid object index.") +ERR(506,"\n API Error 506: invalid object name.") +ERR(507,"\n API Error 507: invalid property type.") +ERR(508,"\n API Error 508: invalid property value.") +ERR(509,"\n API Error 509: invalid time period.") diff --git a/src/exfil.c b/src/exfil.c new file mode 100644 index 000000000..f1cb2adf6 --- /dev/null +++ b/src/exfil.c @@ -0,0 +1,252 @@ +//----------------------------------------------------------------------------- +// exfil.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Storage unit exfiltration functions. +// +// Update History +// ============== +// Build 5.1.008: +// - Monthly conductivity adjustment applied to exfiltration rate. +// Build 5.1.010: +// - New modified Green-Ampt infiltration option used. +// Build 5.1.011: +// - Fixed units conversion error for storage units with surface area curves. +// Build 5.2.0: +// - Support added for analytical storage shapes. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" +#include "infil.h" +#include "exfil.h" + +static int createStorageExfil(int k, double x[]); + +//============================================================================= + +int exfil_readStorageParams(int k, char* tok[], int ntoks, int n) +// +// Input: k = storage unit index +// tok[] = array of string tokens +// ntoks = number of tokens +// n = last token processed +// Output: returns an error code +// Purpose: reads a storage unit's exfiltration parameters from a +// tokenized line of input. +// +{ + int i; + double x[3]; //suction head, Ksat, IMDmax + + // --- read Ksat if it's the only remaining token + if ( ntoks == n+1 ) + { + if ( ! getDouble(tok[n], &x[1]) ) + return error_setInpError(ERR_NUMBER, tok[n]); + x[0] = 0.0; + x[2] = 0.0; + } + + // --- otherwise read Green-Ampt infiltration parameters from input tokens + else if ( ntoks < n + 3 ) return error_setInpError(ERR_ITEMS, ""); + else for (i = 0; i < 3; i++) + { + if ( ! getDouble(tok[n+i], &x[i]) ) + return error_setInpError(ERR_NUMBER, tok[n+i]); + } + + // --- no exfiltration if Ksat is 0 + if ( x[1] == 0.0 ) return 0; + + // --- create an exfiltration object + return createStorageExfil(k, x); +} + +//============================================================================= + +void exfil_initState(int k) +// +// Input: k = storage unit index +// Output: none +// Purpose: initializes the state of a storage unit's exfiltration object. +// +{ + int i; + double a, alast, d; + TTable* aCurve; + TExfil* exfil = Storage[k].exfil; + + // --- initialize exfiltration object + if ( exfil != NULL ) + { + // --- initialize the Green-Ampt infil. parameters + grnampt_initState(exfil->btmExfil); + grnampt_initState(exfil->bankExfil); + + switch (Storage[k].shape) + { + // --- shape given by a Storage Curve + case TABULAR: + i = Storage[k].aCurve; + exfil->btmArea = 0.0; + exfil->bankMinDepth = 0.0; + exfil->bankMaxDepth = 0.0; + exfil->bankMaxArea = 0.0; + if ( i >= 0 ) + { + // --- get bottom area + aCurve = &Curve[i]; + Storage[k].exfil->btmArea = table_lookupEx(aCurve, 0.0); + + // --- find min/max bank depths and max. bank area + table_getFirstEntry(aCurve, &d, &a); + alast = a; + while ( table_getNextEntry(aCurve, &d, &a) ) + { + if ( a < alast ) break; + else if ( a > alast ) + { + exfil->bankMaxArea = a; + exfil->bankMaxDepth = d; + } + else if ( exfil->bankMaxArea == 0.0 ) + exfil->bankMinDepth = d; + else break; + alast = a; + } + + // --- convert from user units to internal units + exfil->btmArea /= UCF(LENGTH) * UCF(LENGTH); + exfil->bankMaxArea /= UCF(LENGTH) * UCF(LENGTH); + exfil->bankMinDepth /= UCF(LENGTH); + exfil->bankMaxDepth /= UCF(LENGTH); + } + break; + + // --- functional storage shape curve + case FUNCTIONAL: + exfil->btmArea = Storage[k].a0; + if ( Storage[k].a2 == 0.0 ) + exfil->btmArea +=Storage[k].a1; + exfil->bankMinDepth = 0.0; + exfil->bankMaxDepth = BIG; + exfil->bankMaxArea = BIG; + break; + + // --- cylindrical, conical & prismatic shapes + case CYLINDRICAL: + case CONICAL: + case PYRAMIDAL: + exfil->btmArea = Storage[k].a0; + exfil->bankMinDepth = 0.0; + exfil->bankMaxDepth = BIG; + exfil->bankMaxArea = BIG; + break; + } + } +} + +//============================================================================= + +double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area) +// +// Input: exfil = ptr. to a storage exfiltration object +// tStep = time step (sec) +// depth = water depth (ft) +// area = surface area (ft2) +// Output: returns exfiltration rate out of storage unit (cfs) +// Purpose: computes rate of water exfiltrated from a storage node into +// the soil beneath it. +// +{ + double exfilRate = 0.0; + + // --- find infiltration through bottom of unit + if ( exfil->btmExfil->IMDmax == 0.0 ) + { + exfilRate = exfil->btmExfil->Ks * Adjust.hydconFactor; + } + else exfilRate = grnampt_getInfil(exfil->btmExfil, tStep, 0.0, depth, + MOD_GREEN_AMPT); + exfilRate *= exfil->btmArea; + + // --- find infiltration through sloped banks + if ( depth > exfil->bankMinDepth ) + { + // --- get area of banks + area = MIN(area, exfil->bankMaxArea) - exfil->btmArea; + if ( area > 0.0 ) + { + // --- if infil. rate not a function of depth + if ( exfil->btmExfil->IMDmax == 0.0 ) + { + exfilRate += area * exfil->btmExfil->Ks * Adjust.hydconFactor; + } + + // --- infil. rate depends on depth above bank + else + { + // --- case where water depth is above the point where the + // storage curve no longer has increasing area with depth + if ( depth > exfil->bankMaxDepth ) + { + depth = depth - exfil->bankMaxDepth + + (exfil->bankMaxDepth - exfil->bankMinDepth) / 2.0; + } + + // --- case where water depth is below top of bank + else depth = (depth - exfil->bankMinDepth) / 2.0; + + // --- use Green-Ampt function for bank infiltration + exfilRate += area * grnampt_getInfil(exfil->bankExfil, + tStep, 0.0, depth, MOD_GREEN_AMPT); + } + } + } + return exfilRate; +} + +//============================================================================= + +int createStorageExfil(int k, double x[]) +// +// Input: k = index of storage unit node +// x = array of Green-Ampt infiltration parameters +// Output: returns an error code. +// Purpose: creates an exfiltration object for a storage node. +// +// Note: the exfiltration object is freed in project.c. +// +{ + TExfil* exfil; + + // --- create an exfiltration object for the storage node + exfil = Storage[k].exfil; + if ( exfil == NULL ) + { + exfil = (TExfil *) malloc(sizeof(TExfil)); + if ( exfil == NULL ) return error_setInpError(ERR_MEMORY, ""); + Storage[k].exfil = exfil; + + // --- create Green-Ampt infiltration objects for the bottom & banks + exfil->btmExfil = NULL; + exfil->bankExfil = NULL; + exfil->btmExfil = (TGrnAmpt *) malloc(sizeof(TGrnAmpt)); + if ( exfil->btmExfil == NULL ) return error_setInpError(ERR_MEMORY, ""); + exfil->bankExfil = (TGrnAmpt *) malloc(sizeof(TGrnAmpt)); + if ( exfil->bankExfil == NULL ) return error_setInpError(ERR_MEMORY, ""); + } + + // --- initialize the Green-Ampt parameters + if ( !grnampt_setParams(exfil->btmExfil, x) ) + return error_setInpError(ERR_NUMBER, ""); + grnampt_setParams(exfil->bankExfil, x); + return 0; +} diff --git a/src/exfil.h b/src/exfil.h new file mode 100644 index 000000000..8da4da302 --- /dev/null +++ b/src/exfil.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// exfil.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Public interface for exfiltration functions. +//----------------------------------------------------------------------------- + +#ifndef EXFIL_H +#define EXFIL_H + +//---------------------------- +// EXFILTRATION OBJECT +//---------------------------- +typedef struct +{ + TGrnAmpt* btmExfil; + TGrnAmpt* bankExfil; + double btmArea; + double bankMinDepth; + double bankMaxDepth; + double bankMaxArea; +} TExfil; + +//----------------------------------------------------------------------------- +// Exfiltration Methods +//----------------------------------------------------------------------------- +int exfil_readStorageParams(int k, char* tok[], int ntoks, int n); +void exfil_initState(int k); +double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area); + +#endif diff --git a/src/findroot.c b/src/findroot.c new file mode 100644 index 000000000..d6056f658 --- /dev/null +++ b/src/findroot.c @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------------- +// findroot.c +// +// Finds solution of func(x) = 0 using either the Newton-Raphson +// method or Ridder's Method. +// Based on code from Numerical Recipes in C (Cambridge University +// Press, 1992). +// +// Date: 11/19/13 +// Author: L. Rossman +//----------------------------------------------------------------------------- + +#include +#include "findroot.h" + +#define SIGN(a,b) ((b) >= 0.0 ? fabs(a) : -fabs(a)) +#define MAXIT 60 + + +int findroot_Newton(double x1, double x2, double* rts, double xacc, + void (*func) (double x, double* f, double* df, void* p), + void* p) +// +// Using a combination of Newton-Raphson and bisection, find the root of a +// function func bracketed between x1 and x2. The root, returned in rts, +// will be refined until its accuracy is known within +/-xacc. func is a +// user-supplied routine, that returns both the function value and the first +// derivative of the function. p is a pointer to any auxilary data structure +// that func may require. It can be NULL if not needed. The function returns +// the number of function evaluations used or 0 if the maximum allowed +// iterations were exceeded. +// +// NOTES: +// 1. The calling program must insure that the signs of func(x1) and func(x2) +// are not the same, otherwise x1 and x2 do not bracket the root. +// 2. If func(x1) > func(x2) then the order of x1 and x2 should be +// switched in the call to Newton. +// +{ + int j, n = 0; + double df, dx, dxold, f, x; + double temp, xhi, xlo; + + // Initialize the "stepsize before last" and the last step. + x = *rts; + xlo = x1; + xhi = x2; + dxold = fabs(x2-x1); + dx = dxold; + func(x, &f, &df, p); + n++; + + // Loop over allowed iterations. + for (j=1; j<=MAXIT; j++) + { + // Bisect if Newton out of range or not decreasing fast enough. + if ( ( ( (x-xhi)*df-f)*((x-xlo)*df-f) >= 0.0 + || (fabs(2.0*f) > fabs(dxold*df) ) ) ) + { + dxold = dx; + dx = 0.5*(xhi-xlo); + x = xlo + dx; + if ( xlo == x ) break; + } + + // Newton step acceptable. Take it. + else + { + dxold = dx; + dx = f/df; + temp = x; + x -= dx; + if ( temp == x ) break; + } + + // Convergence criterion. + if ( fabs(dx) < xacc ) break; + + // Evaluate function. Maintain bracket on the root. + func(x, &f, &df, p); + n++; + if ( f < 0.0 ) xlo = x; + else xhi = x; + } + *rts = x; + if ( n <= MAXIT) return n; + else return 0; +}; + + +double findroot_Ridder(double x1, double x2, double xacc, + double (*func)(double, void* p), void* p) +{ + int j; + double ans, fhi, flo, fm, fnew, s, xhi, xlo, xm, xnew; + + flo = func(x1, p); + fhi = func(x2, p); + if ( flo == 0.0 ) return x1; + if ( fhi == 0.0 ) return x2; + ans = 0.5*(x1+x2); + if ( (flo > 0.0 && fhi < 0.0) || (flo < 0.0 && fhi > 0.0) ) + { + xlo = x1; + xhi = x2; + for (j=1; j<=MAXIT; j++) { + xm = 0.5*(xlo + xhi); + fm = func(xm, p); + s = sqrt( fm*fm - flo*fhi ); + if (s == 0.0) return ans; + xnew = xm + (xm-xlo)*( (flo >= fhi ? 1.0 : -1.0)*fm/s ); + if ( fabs(xnew - ans) <= xacc ) break; + ans = xnew; + fnew = func(ans, p); + if ( SIGN(fm, fnew) != fm) + { + xlo = xm; + flo = fm; + xhi = ans; + fhi = fnew; + } + else if ( SIGN(flo, fnew) != flo ) + { + xhi = ans; + fhi = fnew; + } + else if ( SIGN(fhi, fnew) != fhi) + { + xlo = ans; + flo = fnew; + } + else return ans; + if ( fabs(xhi - xlo) <= xacc ) return ans; + } + return ans; + } + return -1.e20; +} diff --git a/src/findroot.h b/src/findroot.h new file mode 100644 index 000000000..3b54c6456 --- /dev/null +++ b/src/findroot.h @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// findroot.h +// +// Header file for root finding method contained in findroot.c +// +// Last modified on 11/19/13. +//----------------------------------------------------------------------------- + +#ifndef FINDROOT_H +#define FINDROOT_H + +int findroot_Newton(double x1, double x2, double* rts, double xacc, + void (*func) (double x, double* f, double* df, void* p), + void* p); +double findroot_Ridder(double x1, double x2, double xacc, + double (*func)(double, void* p), void* p); + +#endif //FINDROOT_H diff --git a/src/flowrout.c b/src/flowrout.c new file mode 100644 index 000000000..f296c3fb0 --- /dev/null +++ b/src/flowrout.c @@ -0,0 +1,800 @@ +//----------------------------------------------------------------------------- +// flowrout.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 05/02/22 (Build 5.2.1) +// Author: L. Rossman +// M. Tryby (EPA) +// +// Flow routing functions. +// +// Update History +// ============== +// Build 5.1.007: +// - updateStorageState() modified in response to node outflow being +// initialized with current evap & seepage losses in routing_execute(). +// Build 5.1.008: +// - Determination of node crown elevations moved to dynwave.c. +// - Support added for new way of recording conduit's fullness state. +// Build 5.1.012: +// - Overflow computed in updateStorageState() must be non-negative. +// - Terminal storage nodes now updated corectly. +// Build 5.1.014: +// - Arguments to function link_getLossRate changed. +// Build 5.2.0: +// - Correction made to updating state of terminal storage nodes. +// Build 5.2.1: +// - For storage routing, after convergence the reported depth is now +// based on the last volume found rather than the next trial depth. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double OMEGA = 0.55; // under-relaxation parameter +static const int MAXITER = 10; // max. iterations for storage updating +static const double STOPTOL = 0.005; // storage updating stopping tolerance + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// flowrout_init (called by routing_open) +// flowrout_close (called by routing_close) +// flowrout_getRoutingStep (called routing_getRoutingStep) +// flowrout_execute (called routing_execute) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static void initLinkDepths(void); +static void initNodeDepths(void); +static void initNodes(void); +static void initLinks(int routingModel); +static void validateTreeLayout(void); +static void validateGeneralLayout(void); +static void updateStorageState(int i, int j, int links[], double dt); +static double getStorageOutflow(int node, int j, int links[], double dt); +static double getLinkInflow(int link, double dt); +static void setNewNodeState(int node, double dt); +static void setNewLinkState(int link); +static void updateNodeDepth(int node, double y); +static int steadyflow_execute(int link, double* qin, double* qout, + double tStep); + + +//============================================================================= + +void flowrout_init(int routingModel) +// +// Input: routingModel = routing model code +// Output: none +// Purpose: initializes flow routing system. +// +{ + // --- initialize for dynamic wave routing + if ( routingModel == DW ) + { + // --- check for valid conveyance network layout + validateGeneralLayout(); + dynwave_init(); + + // --- initialize node & link depths if not using a hotstart file + if ( Fhotstart1.mode == NO_FILE ) + { + initNodeDepths(); + initLinkDepths(); + } + } + + // --- validate network layout for kinematic wave routing + else validateTreeLayout(); + + // --- initialize node & link volumes + initNodes(); + initLinks(routingModel); +} + +//============================================================================= + +void flowrout_close(int routingModel) +// +// Input: routingModel = routing method code +// Output: none +// Purpose: closes down routing method used. +// +{ + if ( routingModel == DW ) dynwave_close(); +} + +//============================================================================= + +double flowrout_getRoutingStep(int routingModel, double fixedStep) +// +// Input: routingModel = type of routing method used +// fixedStep = user-assigned max. routing step (sec) +// Output: returns adjusted value of routing time step (sec) +// Purpose: finds variable time step for dynamic wave routing. +// +{ + if ( routingModel == DW ) + { + return dynwave_getRoutingStep(fixedStep); + } + return fixedStep; +} + +//============================================================================= + +int flowrout_execute(int links[], int routingModel, double tStep) +// +// Input: links = array of link indexes in topo-sorted order (per routing model) +// routingModel = type of routing method used +// tStep = routing time step (sec) +// Output: returns number of computational steps taken +// Purpose: routes flow through conveyance network over current time step. +// +{ + int i, j; + int n1; // upstream node of link + double qin; // link inflow (cfs) + double qout; // link outflow (cfs) + double steps; // computational step count + + // --- set overflows to drain any ponded water + if ( ErrorCode ) return 0; + for (j = 0; j < Nobjects[NODE]; j++) + { + Node[j].updated = FALSE; + Node[j].overflow = 0.0; + if ( Node[j].type != STORAGE + && Node[j].newVolume > Node[j].fullVolume ) + { + Node[j].overflow = (Node[j].newVolume - Node[j].fullVolume)/tStep; + } + } + + // --- execute dynamic wave routing if called for + if ( routingModel == DW ) + { + return dynwave_execute(tStep); + } + + // --- otherwise examine each link, moving from upstream to downstream + steps = 0.0; + for (i = 0; i < Nobjects[LINK]; i++) + { + // --- see if upstream node is a storage unit whose state needs updating + j = links[i]; + n1 = Link[j].node1; + if ( Node[n1].type == STORAGE ) updateStorageState(n1, i, links, tStep); + + // --- retrieve inflow at upstream end of link + qin = getLinkInflow(j, tStep); + + // --- route flow through link + if ( routingModel == SF ) + steps += steadyflow_execute(j, &qin, &qout, tStep); + else + steps += kinwave_execute(j, &qin, &qout, tStep); + Link[j].newFlow = qout; + + // adjust outflow at upstream node and inflow at downstream node + Node[ Link[j].node1 ].outflow += qin; + Node[ Link[j].node2 ].inflow += qout; + } + if ( Nobjects[LINK] > 0 ) steps /= Nobjects[LINK]; + + // --- update state of each non-updated node and link + for ( j=0; j 2 ) + { + report_writeErrorMsg(ERR_DIVIDER, Node[j].ID); + } + break; + + // --- outfalls cannot have any outlet links + case OUTFALL: + if ( Node[j].degree > 0 ) + { + report_writeErrorMsg(ERR_OUTFALL, Node[j].ID); + } + break; + + // --- storage nodes can have multiple outlets + case STORAGE: break; + + // --- all other nodes allowed only one outlet link + default: + if ( Node[j].degree > 1 ) + { + report_writeErrorMsg(ERR_MULTI_OUTLET, Node[j].ID); + } + } + } + + // --- check links + for (j=0; j 1 ) + { + report_writeErrorMsg(ERR_DUMMY_LINK, Node[i].ID); + } + } + } + + // --- check each node to see if it qualifies as an outlet node + // (meaning that degree = 0) + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + // --- if node is of type Outfall, check that it has only 1 + // connecting link (which can either be an outflow or inflow link) + if ( Node[i].type == OUTFALL ) + { + if ( Node[i].degree + (int)Node[i].inflow > 1 ) + { + report_writeErrorMsg(ERR_OUTFALL, Node[i].ID); + } + else outletCount++; + } + } + if ( outletCount == 0 ) report_writeErrorMsg(ERR_NO_OUTLETS, ""); + + // --- reset node inflows back to zero + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + if ( Node[i].inflow == 0.0 ) Node[i].degree = -Node[i].degree; + Node[i].inflow = 0.0; + } +} + +//============================================================================= + +void initNodeDepths(void) +// +// Input: none +// Output: none +// Purpose: sets initial depth at nodes for Dynamic Wave flow routing. +// +{ + int i; // link or node index + int n; // node index + double y; // node water depth (ft) + + // --- use Node[].inflow as a temporary accumulator for depth in + // connecting links and Node[].outflow as a temporary counter + // for the number of connecting links + for (i = 0; i < Nobjects[NODE]; i++) + { + Node[i].inflow = 0.0; + Node[i].outflow = 0.0; + } + + // --- total up flow depths in all connecting links into nodes + for (i = 0; i < Nobjects[LINK]; i++) + { + if ( Link[i].newDepth > FUDGE ) y = Link[i].newDepth + Link[i].offset1; + else y = 0.0; + n = Link[i].node1; + Node[n].inflow += y; + Node[n].outflow += 1.0; + n = Link[i].node2; + Node[n].inflow += y; + Node[n].outflow += 1.0; + } + + // --- if no user-supplied depth then set initial depth at non-storage/ + // non-outfall nodes to average of depths in connecting links + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + if ( Node[i].type == OUTFALL ) continue; + if ( Node[i].type == STORAGE ) continue; + if ( Node[i].initDepth > 0.0 ) continue; + if ( Node[i].outflow > 0.0 ) + { + Node[i].newDepth = Node[i].inflow / Node[i].outflow; + } + } + + // --- compute initial depths at all outfall nodes + for ( i = 0; i < Nobjects[LINK]; i++ ) link_setOutfallDepth(i); +} + +//============================================================================= + +void initLinkDepths() +// +// Input: none +// Output: none +// Purpose: sets initial flow depths in conduits under Dyn. Wave routing. +// +{ + int i; // link index + double y, y1, y2; // depths (ft) + + // --- examine each link + for (i = 0; i < Nobjects[LINK]; i++) + { + // --- examine each conduit + if ( Link[i].type == CONDUIT ) + { + // --- skip conduits with user-assigned initial flows + // (their depths have already been set to normal depth) + if ( Link[i].q0 != 0.0 ) continue; + + // --- set depth to average of depths at end nodes + y1 = Node[Link[i].node1].newDepth - Link[i].offset1; + y1 = MAX(y1, 0.0); + y1 = MIN(y1, Link[i].xsect.yFull); + y2 = Node[Link[i].node2].newDepth - Link[i].offset2; + y2 = MAX(y2, 0.0); + y2 = MIN(y2, Link[i].xsect.yFull); + y = 0.5 * (y1 + y2); + y = MAX(y, FUDGE); + Link[i].newDepth = y; + } + } +} + +//============================================================================= + +void initNodes() +// +// Input: none +// Output: none +// Purpose: sets initial inflow/outflow and volume for each node +// +{ + int i; + + for ( i = 0; i < Nobjects[NODE]; i++ ) + { + // --- initialize node inflow and outflow + Node[i].inflow = Node[i].newLatFlow; + Node[i].outflow = 0.0; + + // --- initialize node volume + Node[i].newVolume = 0.0; + if ( AllowPonding && + Node[i].pondedArea > 0.0 && + Node[i].newDepth > Node[i].fullDepth ) + { + Node[i].newVolume = Node[i].fullVolume + + (Node[i].newDepth - Node[i].fullDepth) * + Node[i].pondedArea; + } + else Node[i].newVolume = node_getVolume(i, Node[i].newDepth); + } + + // --- update nodal inflow/outflow at ends of each link + // (needed for Steady Flow & Kin. Wave routing) + for ( i = 0; i < Nobjects[LINK]; i++ ) + { + if ( Link[i].newFlow >= 0.0 ) + { + Node[Link[i].node1].outflow += Link[i].newFlow; + Node[Link[i].node2].inflow += Link[i].newFlow; + } + else + { + Node[Link[i].node1].inflow -= Link[i].newFlow; + Node[Link[i].node2].outflow -= Link[i].newFlow; + } + } +} + +//============================================================================= + +void initLinks(int routingModel) +// +// Input: none +// Output: none +// Purpose: sets initial upstream/downstream conditions in links. +// +{ + int i; // link index + int k; // conduit or pump index + + // --- examine each link + for ( i = 0; i < Nobjects[LINK]; i++ ) + { + if ( routingModel == SF) Link[i].newFlow = 0.0; + + // --- otherwise if link is a conduit + else if ( Link[i].type == CONDUIT ) + { + // --- assign initial flow to both ends of conduit + k = Link[i].subIndex; + Conduit[k].q1 = Link[i].newFlow / Conduit[k].barrels; + Conduit[k].q2 = Conduit[k].q1; + + // --- find areas based on initial flow depth + Conduit[k].a1 = xsect_getAofY(&Link[i].xsect, Link[i].newDepth); + Conduit[k].a2 = Conduit[k].a1; + + // --- compute initial volume from area + { + Link[i].newVolume = Conduit[k].a1 * link_getLength(i) * + Conduit[k].barrels; + } + Link[i].oldVolume = Link[i].newVolume; + } + } +} + +//============================================================================= + +double getLinkInflow(int j, double dt) +// +// Input: j = link index +// dt = routing time step (sec) +// Output: returns link inflow (cfs) +// Purpose: finds flow into upstream end of link at current time step under +// Steady or Kin. Wave routing. +// +{ + int n1 = Link[j].node1; + double q; + if ( Link[j].type == CONDUIT || + Link[j].type == PUMP || + Node[n1].type == STORAGE ) q = link_getInflow(j); + else q = 0.0; + return node_getMaxOutflow(n1, q, dt); +} + +//============================================================================= + +void updateStorageState(int i, int j, int links[], double dt) +// +// Input: i = index of storage node +// j = current position in links array +// links = array of topo-sorted link indexes +// dt = routing time step (sec) +// Output: none +// Purpose: updates depth and volume of a storage node using successive +// approximation with under-relaxation for Steady or Kin. Wave +// routing. +// +{ + int iter; // iteration counter + int stopped; // TRUE when iterations stop + double vFixed; // fixed terms of flow balance eqn. + double v2; // new volume estimate (ft3) + double d1; // initial value of storage depth (ft) + double d2; // updated value of storage depth (ft) + + // --- see if storage node needs updating + if ( Node[i].type != STORAGE ) return; + if ( Node[i].updated ) return; + + // --- compute terms of flow balance eqn. + // v2 = v1 + (inflow - outflow)*dt + // that do not depend on storage depth at end of time step + vFixed = Node[i].oldVolume + + 0.5 * (Node[i].oldNetInflow + Node[i].inflow - + Node[i].outflow) * dt; + d1 = Node[i].newDepth; + + // --- iterate finding outflow (which depends on depth) and subsequent + // new volume and depth until negligible depth change occurs + iter = 1; + stopped = FALSE; + while ( iter < MAXITER && !stopped ) + { + // --- find new volume from flow balance eqn. + v2 = vFixed - 0.5 * getStorageOutflow(i, j, links, dt) * dt; + + // --- limit volume to full volume if no ponding + // and compute overflow rate + v2 = MAX(0.0, v2); + Node[i].overflow = 0.0; + if ( v2 > Node[i].fullVolume ) + { + Node[i].overflow = (v2 - MAX(Node[i].oldVolume, + Node[i].fullVolume)) / dt; + if ( Node[i].overflow < FUDGE ) Node[i].overflow = 0.0; + if ( !AllowPonding || Node[i].pondedArea == 0.0 ) + v2 = Node[i].fullVolume; + } + + // --- update node's volume & depth + Node[i].newVolume = v2; + d2 = node_getDepth(i, v2); + Node[i].newDepth = d2; + + // --- use under-relaxation to estimate new depth value + // and stop if close enough to previous value + d2 = (1.0 - OMEGA)*d1 + OMEGA*d2; + if ( fabs(d2 - d1) <= STOPTOL ) stopped = TRUE; + + // --- update old depth with new value and continue to iterate + d1 = d2; + iter++; + } + + // --- mark node as being updated + Node[i].updated = TRUE; +} + +//============================================================================= + +double getStorageOutflow(int i, int j, int links[], double dt) +// +// Input: i = index of storage node +// j = current position in links array +// links = array of topo-sorted link indexes +// dt = routing time step (sec) +// Output: returns total outflow from storage node (cfs) +// Purpose: computes total flow released from a storage node. +// +{ + int k, m; + double outflow = 0.0; + + for (k = j; k < Nobjects[LINK]; k++) + { + m = links[k]; + if ( Link[m].node1 != i ) break; + outflow += getLinkInflow(m, dt); + } + return outflow; +} + +//============================================================================= + +void setNewNodeState(int j, double dt) +// +// Input: j = node index +// dt = time step (sec) +// Output: none +// Purpose: updates state of node after current time step +// for Steady Flow or Kinematic Wave flow routing. +// +{ + int canPond; // TRUE if ponding can occur at node + double newNetInflow; // inflow - outflow at node (cfs) + + // --- update terminal storage nodes + if ( Node[j].type == STORAGE ) + { + if ( Node[j].updated == FALSE ) + updateStorageState(j, Nobjects[LINK], NULL, dt); + return; + } + + // --- update stored volume + newNetInflow = Node[j].inflow - Node[j].outflow - Node[j].losses; + Node[j].newVolume = Node[j].oldVolume + newNetInflow * dt; + if ( Node[j].newVolume < FUDGE ) Node[j].newVolume = 0.0; + + // --- determine any overflow lost from system + Node[j].overflow = 0.0; + canPond = (AllowPonding && Node[j].pondedArea > 0.0); + if ( Node[j].newVolume > Node[j].fullVolume ) + { + Node[j].overflow = (Node[j].newVolume - MAX(Node[j].oldVolume, + Node[j].fullVolume)) / dt; + if ( Node[j].overflow < FUDGE ) Node[j].overflow = 0.0; + if ( !canPond ) Node[j].newVolume = Node[j].fullVolume; + } + + // --- compute a depth from volume + // (depths at upstream nodes are subsequently adjusted in + // setNewLinkState to reflect depths in connected conduit) + Node[j].newDepth = node_getDepth(j, Node[j].newVolume); +} + +//============================================================================= + +void setNewLinkState(int j) +// +// Input: j = link index +// Output: none +// Purpose: updates state of link after current time step under +// Steady Flow or Kinematic Wave flow routing +// +{ + int k; + double a, y1, y2; + + Link[j].newDepth = 0.0; + Link[j].newVolume = 0.0; + + if ( Link[j].type == CONDUIT ) + { + // --- find avg. depth from entry/exit conditions + k = Link[j].subIndex; + a = 0.5 * (Conduit[k].a1 + Conduit[k].a2); + Link[j].newVolume = a * link_getLength(j) * Conduit[k].barrels; + y1 = xsect_getYofA(&Link[j].xsect, Conduit[k].a1); + y2 = xsect_getYofA(&Link[j].xsect, Conduit[k].a2); + Link[j].newDepth = 0.5 * (y1 + y2); + + // --- update depths at end nodes + updateNodeDepth(Link[j].node1, y1 + Link[j].offset1); + updateNodeDepth(Link[j].node2, y2 + Link[j].offset2); + + // --- check if capacity limited + if ( Conduit[k].a1 >= Link[j].xsect.aFull ) + { + Conduit[k].capacityLimited = TRUE; + Conduit[k].fullState = ALL_FULL; + } + else + { + Conduit[k].capacityLimited = FALSE; + Conduit[k].fullState = 0; + } + } +} + +//============================================================================= + +void updateNodeDepth(int i, double y) +// +// Input: i = node index +// y = flow depth (ft) +// Output: none +// Purpose: updates water depth at a node with a possibly higher value. +// +{ + // --- storage nodes were updated elsewhere + if ( Node[i].type == STORAGE ) return; + + // --- if non-outfall node is flooded, then use full depth + if ( Node[i].type != OUTFALL && Node[i].degree > 0 && + Node[i].overflow > 0.0 ) y = Node[i].fullDepth; + + // --- if current new depth below y + if ( Node[i].newDepth < y ) + { + // --- update new depth + Node[i].newDepth = y; + + // --- depth cannot exceed full depth (if value exists) + if ( Node[i].fullDepth > 0.0 && y > Node[i].fullDepth ) + { + Node[i].newDepth = Node[i].fullDepth; + } + } +} + +//============================================================================= + +int steadyflow_execute(int j, double* qin, double* qout, double tStep) +// +// Input: j = link index +// qin = inflow to link (cfs) +// tStep = time step (sec) +// Output: qin = adjusted inflow to link (limited by flow capacity) (cfs) +// qout = link's outflow (cfs) +// returns 1 if successful +// Purpose: performs steady flow routing through a single link. +// +{ + int k; + double s; + double q; + + // --- use Manning eqn. to compute flow area for conduits + if ( Link[j].type == CONDUIT ) + { + k = Link[j].subIndex; + q = (*qin) / Conduit[k].barrels; + if ( Link[j].xsect.type == DUMMY ) Conduit[k].a1 = 0.0; + else + { + // --- adjust flow for evap and infil losses + q -= link_getLossRate(j, q); + + // --- flow can't exceed full flow + if ( q > Link[j].qFull ) + { + q = Link[j].qFull; + Conduit[k].a1 = Link[j].xsect.aFull; + (*qin) = q * Conduit[k].barrels; + } + + // --- infer flow area from flow rate + else + { + s = q / Conduit[k].beta; + Conduit[k].a1 = xsect_getAofS(&Link[j].xsect, s); + } + } + Conduit[k].a2 = Conduit[k].a1; + + Conduit[k].q1Old = Conduit[k].q1; + Conduit[k].q2Old = Conduit[k].q2; + + Conduit[k].q1 = q; + Conduit[k].q2 = q; + (*qout) = q * Conduit[k].barrels; + } + else (*qout) = (*qin); + return 1; +} + +//============================================================================= diff --git a/src/forcmain.c b/src/forcmain.c new file mode 100644 index 000000000..a6314bbce --- /dev/null +++ b/src/forcmain.c @@ -0,0 +1,157 @@ +//----------------------------------------------------------------------------- +// forcemain.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Special Non-Manning Force Main functions +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double VISCOS = 1.1E-5; // Kinematic viscosity of water + // @ 20 deg C (sq ft/sec) + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// forcemain_getEquivN +// forcemain_getRoughFactor +// forcemain_getFricSlope + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static double forcemain_getFricFactor(double e, double hrad, double re); +static double forcemain_getReynolds(double v, double hrad); + +//============================================================================= + +double forcemain_getEquivN(int j, int k) +// +// Input: j = link index +// k = conduit index +// Output: returns an equivalent Manning's n for a force main +// Purpose: computes a Mannng's n that results in the same normal flow +// value for a force main flowing full under fully turbulent +// conditions using either the Hazen-Williams or Dary-Weisbach +// flow equations. +// +{ + TXsect xsect = Link[j].xsect; + double f; + double d = xsect.yFull; + switch ( ForceMainEqn ) + { + case H_W: + return 1.067 / xsect.rBot * pow(d/Conduit[k].slope, 0.04); + case D_W: + f = forcemain_getFricFactor(xsect.rBot, d/4.0, 1.0e12); + return sqrt(f/185.0) * pow(d, (1./6.)); + } + return Conduit[k].roughness; +} + +//============================================================================= + +double forcemain_getRoughFactor(int j, double lengthFactor) +// +// Input: j = link index +// lengthFactor = factor by which a pipe will be artifically lengthened +// Output: returns a roughness adjustment factor for a force main +// Purpose: computes an adjustment factor for a force main that compensates for +// any artificial lengthening the pipe may have received. +// +{ + TXsect xsect = Link[j].xsect; + double r; + switch ( ForceMainEqn ) + { + case H_W: + r = 1.318*xsect.rBot*pow(lengthFactor, 0.54); + return GRAVITY / pow(r, 1.852); + case D_W: + return 1.0/8.0/lengthFactor; + } + return 0.0; +} + +//============================================================================= + +double forcemain_getFricSlope(int j, double v, double hrad) +// +// Input: j = link index +// v = flow velocity (ft/sec) +// hrad = hydraulic radius (ft) +// Output: returns a force main pipe's friction slope +// Purpose: computes the headloss per unit length used in dynamic wave +// flow routing for a pressurized force main using either the +// Hazen-Williams or Darcy-Weisbach flow equations. +// Note: the pipe's roughness factor was saved in xsect.sBot in +// conduit_validate() in LINK.C. +// +{ + double re, f; + TXsect xsect = Link[j].xsect; + switch ( ForceMainEqn ) + { + case H_W: + return xsect.sBot * pow(v, 0.852) / pow(hrad, 1.1667); + case D_W: + re = forcemain_getReynolds(v, hrad); + f = forcemain_getFricFactor(xsect.rBot, hrad, re); + return f * xsect.sBot * v / hrad; + } + return 0.0; +} + +//============================================================================= + +double forcemain_getReynolds(double v, double hrad) +// +// Input: v = flow velocity (ft/sec) +// hrad = hydraulic radius (ft) +// Output: returns a flow's Reynolds Number +// Purpose: computes a flow's Reynolds Number +// +{ + return 4.0 * hrad * v / VISCOS; +} + +//============================================================================= + +double forcemain_getFricFactor(double e, double hrad, double re) +// +// Input: e = roughness height (ft) +// hrad = hydraulic radius (ft) +// re = Reynolds number +// Output: returns a Darcy-Weisbach friction factor +// Purpose: computes the Darcy-Weisbach friction factor for a force main +// using the Swamee and Jain approximation to the Colebrook-White +// equation. +// +{ + double f; + if ( re < 10.0 ) re = 10.0; + if ( re <= 2000.0 ) f = 64.0 / re; + else if ( re < 4000.0 ) + { + f = forcemain_getFricFactor(e, hrad, 4000.0); + f = 0.032 + (f - 0.032) * ( re - 2000.0) / 2000.0; + } + else + { + f = e/3.7/(4.0*hrad); + if ( re < 1.0e10 ) f += 5.74/pow(re, 0.9); + f = log10(f); + f = 0.25 / f / f; + } + return f; +} diff --git a/src/funcs.h b/src/funcs.h new file mode 100644 index 000000000..6f9782723 --- /dev/null +++ b/src/funcs.h @@ -0,0 +1,547 @@ +//----------------------------------------------------------------------------- +// funcs.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// M. Tryby (EPA) +// +// Global interfacing functions. +// +// Update History +// ============== +// Build 5.1.007: +// - climate_readAdjustments() added. +// Build 5.1.008: +// - Function list was re-ordered and blank lines added for readability. +// - Pollutant buildup/washoff functions for the new surfqual.c module added. +// - Several other functions added, re-named or have modified arguments. +// Build 5.1.010: +// - New roadway_getInflow() function added. +// Build 5.1.013: +// - Additional arguments added to function stats_updateSubcatchStats. +// Build 5.1.014: +// - Arguments to link_getLossRate function changed. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for reporting most frequent non-converging links. +// - Support added for named variables & math expressions in control rules. +// - Support added for tracking a gage's prior n-hour rainfall total. +// - Refactored external inflow code. +//----------------------------------------------------------------------------- + +#ifndef FUNCS_H +#define FUNCS_H + +//----------------------------------------------------------------------------- +// Project Methods +//----------------------------------------------------------------------------- +void project_open(const char *f1, const char *f2, const char *f3); +void project_close(void); + +void project_readInput(void); +int project_readOption(char* s1, char* s2); +void project_validate(void); +int project_init(void); + +int project_addObject(int type, char* id, int n); +int project_findObject(int type, const char* id); +char* project_findID(int type, char* id); + +double** project_createMatrix(int nrows, int ncols); +void project_freeMatrix(double** m); + +//----------------------------------------------------------------------------- +// Input Reader Methods +//----------------------------------------------------------------------------- +int input_countObjects(void); +int input_readData(void); + +//----------------------------------------------------------------------------- +// Report Writer Methods +//----------------------------------------------------------------------------- +int report_readOptions(char* tok[], int ntoks); + +void report_writeLine(const char* line); +void report_writeSysTime(void); +void report_writeLogo(void); +void report_writeTitle(void); +void report_writeOptions(void); +void report_writeReport(void); + +void report_writeRainStats(int gage, TRainStats* rainStats); +void report_writeRdiiStats(double totalRain, double totalRdii); + +void report_writeControlActionsHeading(void); +void report_writeControlAction(DateTime aDate, char* linkID, double value, + char* ruleID); + +void report_writeRunoffError(TRunoffTotals* totals, double area); +void report_writeLoadingError(TLoadingTotals* totals); +void report_writeGwaterError(TGwaterTotals* totals, double area); +void report_writeFlowError(TRoutingTotals* totals); +void report_writeQualError(TRoutingTotals* totals); + +void report_writeMaxStats(TMaxStats massBalErrs[], TMaxStats CourantCrit[], + int nMaxStats); +void report_writeMaxFlowTurns(TMaxStats flowTurns[], int nMaxStats); +void report_writeNonconvergedStats(TMaxStats maxNonconverged[], + int nMaxStats); +void report_writeTimeStepStats(TTimeStepStats* timeStepStats); + +void report_writeErrorMsg(int code, char* msg); +void report_writeErrorCode(void); +void report_writeInputErrorMsg(int k, int sect, char* line, long lineCount); +void report_writeWarningMsg(char* msg, char* id); +void report_writeTseriesErrorMsg(int code, TTable *tseries); + +void inputrpt_writeInput(void); +void statsrpt_writeReport(void); + +//----------------------------------------------------------------------------- +// Temperature/Evaporation Methods +//----------------------------------------------------------------------------- +int climate_readParams(char* tok[], int ntoks); +int climate_readEvapParams(char* tok[], int ntoks); +int climate_readAdjustments(char* tok[], int ntoks); +void climate_validate(void); +void climate_openFile(void); +void climate_initState(void); +void climate_setState(DateTime aDate); +DateTime climate_getNextEvapDate(void); + +//----------------------------------------------------------------------------- +// Rainfall Processing Methods +//----------------------------------------------------------------------------- +void rain_open(void); +void rain_close(void); + +//----------------------------------------------------------------------------- +// Snowmelt Processing Methods +//----------------------------------------------------------------------------- +int snow_readMeltParams(char* tok[], int ntoks); +int snow_createSnowpack(int subcacth, int snowIndex); + +void snow_validateSnowmelt(int snowIndex); +void snow_initSnowpack(int subcatch); +void snow_initSnowmelt(int snowIndex); + +void snow_getState(int subcatch, int subArea, double x[]); +void snow_setState(int subcatch, int subArea, double x[]); + +void snow_setMeltCoeffs(int snowIndex, double season); +void snow_plowSnow(int subcatch, double tStep); +double snow_getSnowMelt(int subcatch, double rainfall, double snowfall, + double tStep, double netPrecip[]); +double snow_getSnowCover(int subcatch); + +//----------------------------------------------------------------------------- +// Runoff Analyzer Methods +//----------------------------------------------------------------------------- +int runoff_open(void); +void runoff_execute(void); +void runoff_close(void); + +//----------------------------------------------------------------------------- +// Conveyance System Routing Methods +//----------------------------------------------------------------------------- +int routing_open(void); +double routing_getRoutingStep(int routingModel, double fixedStep); +void routing_execute(int routingModel, double routingStep); +void routing_close(int routingModel); + +//----------------------------------------------------------------------------- +// Output Filer Methods +//----------------------------------------------------------------------------- +int output_open(void); +void output_end(void); +void output_close(void); +void output_saveResults(double reportTime); +void output_updateAvgResults(void); +void output_readDateTime(long period, DateTime *aDate); +void output_readSubcatchResults(long period, int index); +void output_readNodeResults(int long, int index); +void output_readLinkResults(int long, int index); + +//----------------------------------------------------------------------------- +// Groundwater Methods +//----------------------------------------------------------------------------- +int gwater_readAquiferParams(int aquifer, char* tok[], int ntoks); +int gwater_readGroundwaterParams(char* tok[], int ntoks); +int gwater_readFlowExpression(char* tok[], int ntoks); +void gwater_deleteFlowExpression(int subcatch); + +void gwater_validateAquifer(int aquifer); +void gwater_validate(int subcatch); + +void gwater_initState(int subcatch); +void gwater_getState(int subcatch, double x[]); +void gwater_setState(int subcatch, double x[]); + +void gwater_getGroundwater(int subcatch, double evap, double infil, + double tStep); +double gwater_getVolume(int subcatch); + +//----------------------------------------------------------------------------- +// RDII Methods +//----------------------------------------------------------------------------- +int rdii_readRdiiInflow(char* tok[], int ntoks); +void rdii_deleteRdiiInflow(int node); +void rdii_initUnitHyd(int unitHyd); +int rdii_readUnitHydParams(char* tok[], int ntoks); +void rdii_openRdii(void); +void rdii_closeRdii(void); +int rdii_getNumRdiiFlows(DateTime aDate); +void rdii_getRdiiFlow(int index, int* node, double* q); + +//----------------------------------------------------------------------------- +// Landuse Methods +//----------------------------------------------------------------------------- +int landuse_readParams(int landuse, char* tok[], int ntoks); +int landuse_readPollutParams(int pollut, char* tok[], int ntoks); +int landuse_readBuildupParams(char* tok[], int ntoks); +int landuse_readWashoffParams(char* tok[], int ntoks); + +void landuse_getInitBuildup(TLandFactor* landFactor, double* initBuildup, + double area, double curb); +double landuse_getBuildup(int landuse, int pollut, double area, double curb, + double buildup, double tStep); + +double landuse_getWashoffLoad(int landuse, int p, double area, + TLandFactor landFactor[], double runoff, double vOutflow); +double landuse_getAvgBmpEffic(int j, int p); +double landuse_getCoPollutLoad(int p, double washoff[]); + +//----------------------------------------------------------------------------- +// Flow/Quality Routing Methods +//----------------------------------------------------------------------------- +void flowrout_init(int routingModel); +void flowrout_close(int routingModel); +double flowrout_getRoutingStep(int routingModel, double fixedStep); +int flowrout_execute(int links[], int routingModel, double tStep); + +void toposort_sortLinks(int links[]); +int kinwave_execute(int link, double* qin, double* qout, double tStep); + +void dynwave_validate(void); +void dynwave_init(void); +void dynwave_close(void); +double dynwave_getRoutingStep(double fixedStep); +int dynwave_execute(double tStep); +void dwflow_findConduitFlow(int j, int steps, double omega, double dt); + +void qualrout_init(void); +void qualrout_execute(double tStep); + +//----------------------------------------------------------------------------- +// Treatment Methods +//----------------------------------------------------------------------------- +int treatmnt_open(void); +void treatmnt_close(void); +int treatmnt_readExpression(char* tok[], int ntoks); +void treatmnt_delete(int node); +void treatmnt_treat(int node, double q, double v, double tStep); +void treatmnt_setInflow(double qIn, double wIn[]); + +//----------------------------------------------------------------------------- +// Mass Balance Methods +//----------------------------------------------------------------------------- +int massbal_open(void); +void massbal_close(void); +void massbal_report(void); + +void massbal_updateRunoffTotals(int type, double v); +void massbal_updateLoadingTotals(int type, int pollut, double w); +void massbal_updateGwaterTotals(double vInfil, double vUpperEvap, + double vLowerEvap, double vLowerPerc, double vGwater); +void massbal_updateRoutingTotals(double tStep); + + +void massbal_initTimeStepTotals(void); +void massbal_addInflowFlow(int type, double q); +void massbal_addInflowQual(int type, int pollut, double w); +void massbal_addOutflowFlow(double q, int isFlooded); +void massbal_addOutflowQual(int pollut, double mass, int isFlooded); +void massbal_addNodeLosses(double evapLoss, double infilLoss); +void massbal_addLinkLosses(double evapLoss, double infilLoss); +void massbal_addReactedMass(int pollut, double mass); +void massbal_addSeepageLoss(int pollut, double seepLoss); +void massbal_addToFinalStorage(int pollut, double mass); +double massbal_getStepFlowError(void); +double massbal_getRunoffError(void); +double massbal_getFlowError(void); + +//----------------------------------------------------------------------------- +// Simulation Statistics Methods +//----------------------------------------------------------------------------- +int stats_open(void); +void stats_close(void); +void stats_report(void); + +void stats_updateCriticalTimeCount(int node, int link); +void stats_updateFlowStats(double tStep, DateTime aDate); +void stats_updateTimeStepStats(double tStep, int trialsCount, int steadyState); + +void stats_updateSubcatchStats(int subcatch, double rainVol, + double runonVol, double evapVol, double infilVol, + double impervVol, double pervVol, double runoffVol, double runoff); +void stats_updateGwaterStats(int j, double infil, double evap, + double latFlow, double deepFlow, double theta, double waterTable, + double tStep); +void stats_updateMaxRunoff(void); +void stats_updateMaxNodeDepth(int node, double depth); +void stats_updateConvergenceStats(int node, int converged); + + +//----------------------------------------------------------------------------- +// Raingage Methods +//----------------------------------------------------------------------------- +int gage_readParams(int gage, char* tok[], int ntoks); +void gage_validate(int gage); +void gage_initState(int gage); +void gage_setState(int gage, DateTime aDate); +double gage_getPrecip(int gage, double *rainfall, double *snowfall); +void gage_setReportRainfall(int gage, DateTime aDate); +DateTime gage_getNextRainDate(int gage, DateTime aDate); +void gage_updatePastRain(int j, int tStep); +double gage_getPastRain(int gage, int hrs); + +//----------------------------------------------------------------------------- +// Subcatchment Methods +//----------------------------------------------------------------------------- +int subcatch_readParams(int subcatch, char* tok[], int ntoks); +int subcatch_readSubareaParams(char* tok[], int ntoks); +int subcatch_readLanduseParams(char* tok[], int ntoks); +int subcatch_readInitBuildup(char* tok[], int ntoks); + +void subcatch_validate(int subcatch); +void subcatch_initState(int subcatch); +void subcatch_setOldState(int subcatch); + +double subcatch_getFracPerv(int subcatch); +double subcatch_getStorage(int subcatch); +double subcatch_getDepth(int subcatch); + +void subcatch_getRunon(int subcatch); +void subcatch_addRunonFlow(int subcatch, double flow); +double subcatch_getRunoff(int subcatch, double tStep); + +double subcatch_getWtdOutflow(int subcatch, double wt); +void subcatch_getResults(int subcatch, double wt, float x[]); + +//----------------------------------------------------------------------------- +// Surface Pollutant Buildup/Washoff Methods +//----------------------------------------------------------------------------- +void surfqual_initState(int subcatch); +void surfqual_getWashoff(int subcatch, double runoff, double tStep); +void surfqual_getBuildup(int subcatch, double tStep); +void surfqual_sweepBuildup(int subcatch, DateTime aDate); +double surfqual_getWtdWashoff(int subcatch, int pollut, double wt); + +//----------------------------------------------------------------------------- +// Conveyance System Node Methods +//----------------------------------------------------------------------------- +int node_readParams(int node, int type, int subIndex, char* tok[], int ntoks); +void node_validate(int node); + +void node_initState(int node); +void node_initFlows(int node, double tStep); +void node_setOldHydState(int node); +void node_setOldQualState(int node); +void node_setOutletDepth(int node, double yNorm, double yCrit, double z); + +double node_getSurfArea(int node, double depth); +double node_getDepth(int node, double volume); +double node_getVolume(int node, double depth); +double node_getPondedArea(int node, double depth); + +double node_getOutflow(int node, int link); +double node_getLosses(int node, double tStep); +double node_getMaxOutflow(int node, double q, double tStep); +double node_getSystemOutflow(int node, int *isFlooded); +void node_getResults(int node, double wt, float x[]); + +//----------------------------------------------------------------------------- +// Conveyance System Inflow Methods +//----------------------------------------------------------------------------- +int inflow_readExtInflow(char* tok[], int ntoks); +int inflow_readDwfInflow(char* tok[], int ntoks); +int inflow_readDwfPattern(char* tok[], int ntoks); +int inflow_setExtInflow(int j, int param, int type, int tSeries, + int basePat, double cf, double baseline, double sf); + +void inflow_initDwfInflow(TDwfInflow* inflow); +void inflow_initDwfPattern(int pattern); + +double inflow_getExtInflow(TExtInflow* inflow, DateTime aDate); +double inflow_getDwfInflow(TDwfInflow* inflow, int m, int d, int h); + +void inflow_deleteExtInflows(int node); +void inflow_deleteDwfInflows(int node); + +//----------------------------------------------------------------------------- +// Routing Interface File Methods +//----------------------------------------------------------------------------- +int iface_readFileParams(char* tok[], int ntoks); +void iface_openRoutingFiles(void); +void iface_closeRoutingFiles(void); +int iface_getNumIfaceNodes(DateTime aDate); +int iface_getIfaceNode(int index); +double iface_getIfaceFlow(int index); +double iface_getIfaceQual(int index, int pollut); +void iface_saveOutletResults(DateTime reportDate, FILE* file); + +//----------------------------------------------------------------------------- +// Hot Start File Methods +//----------------------------------------------------------------------------- +int hotstart_open(void); +void hotstart_close(void); + +//----------------------------------------------------------------------------- +// Conveyance System Link Methods +//----------------------------------------------------------------------------- +int link_readParams(int link, int type, int subIndex, char* tok[], int ntoks); +int link_readXsectParams(char* tok[], int ntoks); +int link_readLossParams(char* tok[], int ntoks); + +void link_validate(int link); +void link_initState(int link); +void link_setOldHydState(int link); +void link_setOldQualState(int link); + +void link_setTargetSetting(int j); +void link_setSetting(int j, double tstep); +int link_setFlapGate(int link, int n1, int n2, double q); + +double link_getInflow(int link); +void link_setOutfallDepth(int link); +double link_getLength(int link); +double link_getYcrit(int link, double q); +double link_getYnorm(int link, double q); +double link_getVelocity(int link, double q, double y); +double link_getFroude(int link, double v, double y); +double link_getPower(int link); +double link_getLossRate(int link, double q); +char link_getFullState(double a1, double a2, double aFull); + +void link_getResults(int link, double wt, float x[]); + +//----------------------------------------------------------------------------- +// Link Cross-Section Methods +//----------------------------------------------------------------------------- +int xsect_isOpen(int type); +int xsect_setParams(TXsect *xsect, int type, double p[], double ucf); +void xsect_setIrregXsectParams(TXsect *xsect); +void xsect_setCustomXsectParams(TXsect *xsect); +void xsect_setStreetXsectParams(TXsect *xsect); +double xsect_getAmax(TXsect* xsect); + +double xsect_getSofA(TXsect* xsect, double area); +double xsect_getYofA(TXsect* xsect, double area); +double xsect_getRofA(TXsect* xsect, double area); +double xsect_getAofS(TXsect* xsect, double sFactor); +double xsect_getdSdA(TXsect* xsect, double area); +double xsect_getAofY(TXsect* xsect, double y); +double xsect_getRofY(TXsect* xsect, double y); +double xsect_getWofY(TXsect* xsect, double y); +double xsect_getYcrit(TXsect* xsect, double q); + +//----------------------------------------------------------------------------- +// Culvert/Roadway Methods +//----------------------------------------------------------------------------- +double culvert_getInflow(int link, double q, double h); +double roadway_getInflow(int link, double dir, double hcrest, double h1, + double h2); + +//----------------------------------------------------------------------------- +// Force Main Methods +//----------------------------------------------------------------------------- +double forcemain_getEquivN(int j, int k); +double forcemain_getRoughFactor(int j, double lengthFactor); +double forcemain_getFricSlope(int j, double v, double hrad); + +//----------------------------------------------------------------------------- +// Cross-Section Transect Methods +//----------------------------------------------------------------------------- +int transect_create(int n); +void transect_delete(void); +int transect_readParams(int* count, char* tok[], int ntoks); +void transect_validate(int j); +void transect_createStreetTransect(TStreet* street); + +//----------------------------------------------------------------------------- +// Street Cross-Section Methods +//----------------------------------------------------------------------------- +int street_create(int nStreets); +void street_delete(); +int street_readParams(char* tok[], int ntoks); +double street_getExtentFilled(int link); + +//----------------------------------------------------------------------------- +// Custom Shape Cross-Section Methods +//----------------------------------------------------------------------------- +int shape_validate(TShape *shape, TTable *curve); + +//----------------------------------------------------------------------------- +// Control Rule Methods +//----------------------------------------------------------------------------- +int controls_create(int n); +void controls_delete(void); +void controls_init(void); +void controls_addToCount(char* s); +int controls_addVariable(char* tok[], int ntoks); +int controls_addExpression(char* tok[], int ntoks); +int controls_addRuleClause(int rule, int keyword, char* Tok[], int nTokens); +int controls_evaluate(DateTime currentTime, DateTime elapsedTime, + double tStep); + +//----------------------------------------------------------------------------- +// Table & Time Series Methods +//----------------------------------------------------------------------------- +int table_readCurve(char* tok[], int ntoks); +int table_readTimeseries(char* tok[], int ntoks); + +int table_addEntry(TTable* table, double x, double y); +int table_getFirstEntry(TTable* table, double* x, double* y); +int table_getNextEntry(TTable* table, double* x, double* y); +void table_deleteEntries(TTable* table); + +void table_init(TTable* table); +int table_validate(TTable* table); + +double table_lookup(TTable* table, double x); +double table_lookupEx(TTable* table, double x); +double table_intervalLookup(TTable* table, double x); +double table_inverseLookup(TTable* table, double y); + +double table_getSlope(TTable *table, double x); +double table_getMaxY(TTable *table, double x); +double table_getStorageVolume(TTable* table, double x); +double table_getStorageDepth(TTable* table, double v); + +void table_tseriesInit(TTable *table); +double table_tseriesLookup(TTable* table, double t, char extend); + +//----------------------------------------------------------------------------- +// Utility Methods +//----------------------------------------------------------------------------- +double UCF(int quantity); // units conversion factor +int getInt(char *s, int *y); // get integer from string +int getFloat(char *s, float *y); // get float from string +int getDouble(char *s, double *y); // get double from string +char* getTempFileName(char *s); // get temporary file name +int findmatch(char *s, char *keyword[]); // search for matching keyword +int match(char *str, char *substr); // true if substr matches part of str +int strcomp(const char *s1, const char *s2); // case insensitive string compare +size_t sstrncpy(char *dest, const char *src, + size_t n); // safe string copy +size_t sstrcat(char* dest, const char* src, + size_t destsize); // safe string concatenation +void writecon(const char *s); // writes string to console +DateTime getDateTime(double elapsedMsec); // convert elapsed time to date +void getElapsedTime(DateTime aDate, // convert elapsed date + int* days, int* hrs, int* mins); +char* addAbsolutePath(char *fname); // add full path to a file name + +#endif //FUNCS_H diff --git a/src/gage.c b/src/gage.c new file mode 100644 index 000000000..031cc017c --- /dev/null +++ b/src/gage.c @@ -0,0 +1,705 @@ +//----------------------------------------------------------------------------- +// gage.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Rain gage functions. +// +// Update History +// ============== +// Build 5.1.007: +// - Support for monthly rainfall adjustments added. +// Build 5.1.013: +// - Validation no longer performed on unused gages. +// Build 5.2.0: +// - Support added for tracking a gage's prior n-hour rainfall total. +// - Support added for relative file names. +// - Support added for setting rainfall through API call. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +const double OneSecond = 1.1574074e-5; + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// gage_readParams (called by input_readLine) +// gage_validate (called by project_validate) +// gage_initState (called by project_init) +// gage_setState (called by runoff_execute & getRainfall in rdii.c) +// gage_getPrecip (called by subcatch_getRunoff) +// gage_getNextRainDate (called by runoff_getTimeStep) +// gage_updatePastRain (called by runoff_execute) +// gage_getPastRain (called by getRainValue in controls.c) +// gage_setReportRainfall (called by output_saveSubcatchResults) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int readGageSeriesFormat(char* tok[], int ntoks, double x[]); +static int readGageFileFormat(char* tok[], int ntoks, double x[]); +static int getFirstRainfall(int gage); +static int getNextRainfall(int gage); +static double convertRainfall(int gage, double rain); +static void initPastRain(int gage); + +//============================================================================= + +int gage_readParams(int j, char* tok[], int ntoks) +// +// Input: j = rain gage index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads rain gage parameters from a line of input data +// +// Data formats are: +// Name RainType RecdFreq SCF TIMESERIES SeriesName +// Name RainType RecdFreq SCF FILE FileName Station Units StartDate +// +{ + int k, err; + char *id; + char fname[MAXFNAME+1]; + char staID[MAXMSG+1]; + double x[7]; + + // --- check that gage exists + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(GAGE, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + + // --- assign default parameter values + x[0] = -1.0; // No time series index + x[1] = 1.0; // Rain type is volume + x[2] = 3600.0; // Recording freq. is 3600 sec + x[3] = 1.0; // Snow catch deficiency factor + x[4] = NO_DATE; // Default is no start/end date + x[5] = NO_DATE; + x[6] = 0.0; // US units + fname[0] = '\0'; + staID[0] = '\0'; + + if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); + k = findmatch(tok[4], GageDataWords); + if ( k == RAIN_TSERIES ) + { + err = readGageSeriesFormat(tok, ntoks, x); + } + else if ( k == RAIN_FILE ) + { + if ( ntoks < 8 ) return error_setInpError(ERR_ITEMS, ""); + sstrncpy(fname, tok[5], MAXFNAME); + sstrncpy(staID, tok[6], MAXMSG); + err = readGageFileFormat(tok, ntoks, x); + } + else return error_setInpError(ERR_KEYWORD, tok[4]); + + // --- save parameters to rain gage object + if ( err > 0 ) return err; + Gage[j].ID = id; + Gage[j].tSeries = (int)x[0]; + Gage[j].rainType = (int)x[1]; + Gage[j].rainInterval = (int)x[2]; + Gage[j].snowFactor = x[3]; + Gage[j].rainUnits = (int)x[6]; + if ( Gage[j].tSeries >= 0 ) Gage[j].dataSource = RAIN_TSERIES; + else Gage[j].dataSource = RAIN_FILE; + if ( Gage[j].dataSource == RAIN_FILE ) + { + sstrncpy(Gage[j].fname, addAbsolutePath(fname), MAXFNAME); + sstrncpy(Gage[j].staID, staID, MAXMSG); + Gage[j].startFileDate = x[4]; + Gage[j].endFileDate = x[5]; + } + Gage[j].unitsFactor = 1.0; + Gage[j].coGage = -1; + Gage[j].isUsed = FALSE; + return 0; +} + +//============================================================================= + +int readGageSeriesFormat(char* tok[], int ntoks, double x[]) +{ + int m, ts; + DateTime aTime; + + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + + // --- determine type of rain data + m = findmatch(tok[1], RainTypeWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + x[1] = (double)m; + + // --- get data time interval & convert to seconds + if ( getDouble(tok[2], &x[2]) ) x[2] = floor(x[2]*3600 + 0.5); + else if ( datetime_strToTime(tok[2], &aTime) ) + { + x[2] = floor(aTime*SECperDAY + 0.5); + } + else return error_setInpError(ERR_DATETIME, tok[2]); + if ( x[2] <= 0.0 ) return error_setInpError(ERR_DATETIME, tok[2]); + + // --- get snow catch deficiency factor + if ( !getDouble(tok[3], &x[3]) ) + return error_setInpError(ERR_DATETIME, tok[3]);; + + // --- get time series index + ts = project_findObject(TSERIES, tok[5]); + if ( ts < 0 ) return error_setInpError(ERR_NAME, tok[5]); + x[0] = (double)ts; + sstrncpy(tok[2], "", 0); + return 0; +} + +//============================================================================= + +int readGageFileFormat(char* tok[], int ntoks, double x[]) +{ + int m, u; + DateTime aDate; + DateTime aTime; + + // --- determine type of rain data + m = findmatch(tok[1], RainTypeWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + x[1] = (double)m; + + // --- get data time interval & convert to seconds + if ( getDouble(tok[2], &x[2]) ) x[2] *= 3600; + else if ( datetime_strToTime(tok[2], &aTime) ) + { + x[2] = floor(aTime*SECperDAY + 0.5); + } + else return error_setInpError(ERR_DATETIME, tok[2]); + if ( x[2] <= 0.0 ) return error_setInpError(ERR_DATETIME, tok[2]); + + // --- get snow catch deficiency factor + if ( !getDouble(tok[3], &x[3]) ) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- get rain depth units + u = findmatch(tok[7], RainUnitsWords); + if ( u < 0 ) return error_setInpError(ERR_KEYWORD, tok[7]); + x[6] = (double)u; + + // --- get start date (if present) + if ( ntoks > 8 && *tok[8] != '*') + { + if ( !datetime_strToDate(tok[8], &aDate) ) + return error_setInpError(ERR_DATETIME, tok[8]); + x[4] = (float) aDate; + } + return 0; +} + +//============================================================================= + +void gage_validate(int j) +// +// Input: j = rain gage index +// Output: none +// Purpose: checks for valid rain gage parameters +// +// NOTE: assumes that any time series used by a rain gage has been +// previously validated. +// +{ + int i, k; + int gageInterval; + + // --- for gage with time series data: + if ( Gage[j].dataSource == RAIN_TSERIES ) + { + // --- no validation for an unused gage + if ( !Gage[j].isUsed ) return; + + // --- see if gage uses same time series as another gage + k = Gage[j].tSeries; + for (i=0; i= 0 ) + { + report_writeErrorMsg(ERR_RAIN_GAGE_TSERIES, Gage[j].ID); + } + gageInterval = (int)(floor(Tseries[k].dxMin*SECperDAY + 0.5)); + if ( gageInterval > 0 && Gage[j].rainInterval > gageInterval ) + { + report_writeErrorMsg(ERR_RAIN_GAGE_INTERVAL, Gage[j].ID); + } + if ( Gage[j].rainInterval < gageInterval ) + { + report_writeWarningMsg(WARN09, Gage[j].ID); + } + if ( Gage[j].rainInterval < WetStep ) + { + report_writeWarningMsg(WARN01, Gage[j].ID); + WetStep = Gage[j].rainInterval; + } + } +} + +//============================================================================= + +void gage_initState(int j) +// +// Input: j = rain gage index +// Output: none +// Purpose: initializes state of rain gage. +// +{ + // --- initialize actual and reported rainfall + Gage[j].rainfall = 0.0; + Gage[j].apiRainfall = MISSING; + Gage[j].reportRainfall = 0.0; + if ( IgnoreRainfall ) return; + + // --- for gage with file data: + if ( Gage[j].dataSource == RAIN_FILE ) + { + // --- set current file position to start of period of record + Gage[j].currentFilePos = Gage[j].startFilePos; + + // --- assign units conversion factor + // (rain depths on interface file are in inches) + if ( UnitSystem == SI ) Gage[j].unitsFactor = MMperINCH; + } + + // --- get first & next rainfall values + if ( getFirstRainfall(j) ) + { + // --- find date at end of starting rain interval + Gage[j].endDate = datetime_addSeconds( + Gage[j].startDate, Gage[j].rainInterval); + + // --- if rainfall record begins after start of simulation, + if ( Gage[j].startDate > StartDateTime ) + { + // --- make next rainfall date the start of the rain record + Gage[j].nextDate = Gage[j].startDate; + Gage[j].nextRainfall = Gage[j].rainfall; + + // --- make start of current rain interval the simulation start + Gage[j].startDate = StartDateTime; + Gage[j].endDate = Gage[j].nextDate; + Gage[j].rainfall = 0.0; + } + + // --- otherwise find next recorded rainfall + else if ( !getNextRainfall(j) ) Gage[j].nextDate = NO_DATE; + } + else Gage[j].startDate = NO_DATE; + initPastRain(j); +} + +//============================================================================= + +void gage_setState(int j, DateTime t) +// +// Input: j = rain gage index +// t = a calendar date/time +// Output: none +// Purpose: updates state of rain gage for specified date. +// +{ + // --- return if gage not used by any subcatchment + if ( Gage[j].isUsed == FALSE ) return; + + // --- set rainfall to zero if disabled + if ( IgnoreRainfall ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- use rainfall from co-gage (gage with lower index that uses + // same rainfall time series or file) if it exists + if ( Gage[j].coGage >= 0) + { + Gage[j].rainfall = Gage[Gage[j].coGage].rainfall; + return; + } + + // --- use rainfall supplied by API function call + // (where constant ZERO (1.e-10) is used for 0 rainfall) + if (Gage[j].apiRainfall != MISSING) + { + Gage[j].rainfall = Gage[j].apiRainfall; + return; + } + + // --- otherwise march through rainfall record until date t is bracketed + t += OneSecond; + for (;;) + { + // --- no rainfall if no interval start date + if ( Gage[j].startDate == NO_DATE ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- no rainfall if time is before interval start date + if ( t < Gage[j].startDate ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- use current rainfall if time is before interval end date + if ( t < Gage[j].endDate ) + { + return; + } + + // --- no rainfall if t >= interval end date & no next interval exists + if ( Gage[j].nextDate == NO_DATE ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- no rainfall if t > interval end date & < next interval date + if ( t < Gage[j].nextDate ) + { + Gage[j].rainfall = 0.0; + return; + } + + // --- otherwise update next rainfall interval date + Gage[j].startDate = Gage[j].nextDate; + Gage[j].endDate = datetime_addSeconds(Gage[j].startDate, + Gage[j].rainInterval); + Gage[j].rainfall = Gage[j].nextRainfall; + if ( !getNextRainfall(j) ) Gage[j].nextDate = NO_DATE; + } +} + +//============================================================================= + +void initPastRain(int j) +{ + // --- initialize past hourly rain accumulation + int i; + for (i = 0; i <= MAXPASTRAIN; i++) + Gage[j].pastRain[i] = 0.0; + Gage[j].pastInterval = 0; +} + +//============================================================================= + +void gage_updatePastRain(int j, int tStep) +// +// Input: j = rain gage index +// tStep = current runoff time step (sec) +// Output: none +// Purpose: updates past MAXPASTRAIN hourly rain totals. +// +// Note: pastRain[0] is past rain volume prior to 1 hour, +// pastRain[n] is past rain volume after n hours, +// pastInterval is time since last hour was reached. +{ + int i, t; + double r; + + // --- current rainfall intensity (in/sec or mm/sec) + r = Gage[j].rainfall / 3600.; + + // --- process each hourly interval of current time step + while (tStep > 0) + { + // --- time for most recent rainfall interval to reach 1 hr + t = 3600 - Gage[j].pastInterval; + + // --- remaining time step is greater than this time + if (tStep > t) + { + // --- add current rain to most recent interval + Gage[j].pastRain[0] += t * r; + + // --- shift all prior hourly rain amounts by 1 hour + for (i = MAXPASTRAIN; i > 0; i-- ) + Gage[j].pastRain[i] = Gage[j].pastRain[i-1]; + + // --- begin a new most recent interval + Gage[j].pastInterval = 0; + Gage[j].pastRain[0] = 0.0; + tStep -= t; + } + // --- time to reach 1 hr in most recent interval is greater + // than remaining time step so update most recent interval + else + { + Gage[j].pastRain[0] += tStep * r; + Gage[j].pastInterval += tStep; + tStep = 0; + } + } +} + +//============================================================================= + +double gage_getPastRain(int j, int n) +// +// Input: j = rain gage index +// n = number of hours prior to current date +// Output: cumulative rain volume (inches or mm) in last n hours +// Purpose: retrieves rainfall total over some previous number of hours. +// +{ + int i; + double result = 0.0; + if (n < 1 || n > MAXPASTRAIN) return 0.0; + for (i = 1; i <= n; i++) + result += Gage[j].pastRain[i]; + return result; +} + +//============================================================================= + +DateTime gage_getNextRainDate(int j, DateTime aDate) +// +// Input: j = rain gage index +// aDate = calendar date/time +// Output: next date with rainfall occurring +// Purpose: finds the next date from specified date when rainfall occurs. +// +{ + if ( Gage[j].isUsed == FALSE ) return aDate; + aDate += OneSecond; + if ( aDate < Gage[j].startDate ) return Gage[j].startDate; + if ( aDate < Gage[j].endDate ) return Gage[j].endDate; + return Gage[j].nextDate; +} + +//============================================================================= + +double gage_getPrecip(int j, double *rainfall, double *snowfall) +// +// Input: j = rain gage index +// Output: rainfall = rainfall rate (ft/sec) +// snowfall = snow fall rate (ft/sec) +// returns total precipitation (ft/sec) +// Purpose: determines whether gage's recorded rainfall is rain or snow. +// +{ + *rainfall = 0.0; + *snowfall = 0.0; + if ( !IgnoreSnowmelt && Temp.ta <= Snow.snotmp ) + { + *snowfall = Gage[j].rainfall * Gage[j].snowFactor / UCF(RAINFALL); + } + else *rainfall = Gage[j].rainfall / UCF(RAINFALL); + return (*rainfall) + (*snowfall); +} + +//============================================================================= + +void gage_setReportRainfall(int j, DateTime reportDate) +// +// Input: j = rain gage index +// reportDate = date/time value of current reporting time +// Output: none +// Purpose: sets the rainfall value reported at the current reporting time. +// +{ + double result; + + // --- use value from co-gage if it exists + if ( Gage[j].coGage >= 0) + { + Gage[j].reportRainfall = Gage[Gage[j].coGage].reportRainfall; + return; + } + + // --- rainfall set by API call + if (Gage[j].apiRainfall != MISSING) + { + Gage[j].reportRainfall = Gage[j].apiRainfall; + return; + } + + // --- otherwise increase reporting time by 1 second to avoid + // roundoff problems + reportDate += OneSecond; + + // --- use current rainfall if report date/time is before end + // of current rain interval + if ( reportDate < Gage[j].endDate ) result = Gage[j].rainfall; + + // --- use 0.0 if report date/time is before start of next rain interval + else if ( reportDate < Gage[j].nextDate ) result = 0.0; + + // --- otherwise report date/time falls right on end of current rain + // interval and start of next interval so use next interval's rainfall + else result = Gage[j].nextRainfall; + Gage[j].reportRainfall = result; +} + +//============================================================================= + +int getFirstRainfall(int j) +// +// Input: j = rain gage index +// Output: returns TRUE if successful +// Purpose: positions rainfall record to date with first rainfall. +// +{ + int k; // time series index + float vFirst; // first rain volume (ft or m) + double rFirst; // first rain intensity (in/hr or mm/hr) + + // --- assign default values to date & rainfall + Gage[j].startDate = NO_DATE; + Gage[j].rainfall = 0.0; + + // --- initialize internal cumulative rainfall value + Gage[j].rainAccum = 0; + + // --- use rain interface file if applicable + if ( Gage[j].dataSource == RAIN_FILE ) + { + if ( Frain.file && Gage[j].endFilePos > Gage[j].startFilePos ) + { + // --- retrieve 1st date & rainfall volume from file + fseek(Frain.file, Gage[j].startFilePos, SEEK_SET); + fread(&Gage[j].startDate, sizeof(DateTime), 1, Frain.file); + fread(&vFirst, sizeof(float), 1, Frain.file); + Gage[j].currentFilePos = ftell(Frain.file); + + // --- convert rainfall to intensity + Gage[j].rainfall = convertRainfall(j, (double)vFirst); + return 1; + } + return 0; + } + + // --- otherwise access user-supplied rainfall time series + else + { + k = Gage[j].tSeries; + if ( k >= 0 ) + { + // --- retrieve first rainfall value from time series + if ( table_getFirstEntry(&Tseries[k], &Gage[j].startDate, + &rFirst) ) + { + // --- convert rainfall to intensity + Gage[j].rainfall = convertRainfall(j, rFirst); + return 1; + } + } + return 0; + } +} + +//============================================================================= + +int getNextRainfall(int j) +// +// Input: j = rain gage index +// Output: returns 1 if successful; 0 if not +// Purpose: positions rainfall record to date with next non-zero rainfall +// while updating the gage's next rain intensity value. +// +// Note: zero rainfall values explicitly entered into a rain file or +// time series are skipped over so that a proper accounting of +// wet and dry periods can be maintained. +// +{ + int k; // time series index + float vNext; // next rain volume (ft or m) + double rNext; // next rain intensity (in/hr or mm/hr) + + Gage[j].nextRainfall = 0.0; + do + { + if ( Gage[j].dataSource == RAIN_FILE ) + { + if ( Frain.file && Gage[j].currentFilePos < Gage[j].endFilePos ) + { + fseek(Frain.file, Gage[j].currentFilePos, SEEK_SET); + fread(&Gage[j].nextDate, sizeof(DateTime), 1, Frain.file); + fread(&vNext, sizeof(float), 1, Frain.file); + Gage[j].currentFilePos = ftell(Frain.file); + rNext = convertRainfall(j, (double)vNext); + } + else return 0; + } + + else + { + k = Gage[j].tSeries; + if ( k >= 0 ) + { + if ( !table_getNextEntry(&Tseries[k], + &Gage[j].nextDate, &rNext) ) return 0; + rNext = convertRainfall(j, rNext); + } + else return 0; + } + } while (rNext == 0.0); + Gage[j].nextRainfall = rNext; + return 1; +} + +//============================================================================= + +double convertRainfall(int j, double r) +// +// Input: j = rain gage index +// r = rainfall value (user units) +// Output: returns rainfall intensity (user units) +// Purpose: converts rainfall value to an intensity (depth per hour). +// +{ + double r1; + switch ( Gage[j].rainType ) + { + case RAINFALL_INTENSITY: + r1 = r; + break; + + case RAINFALL_VOLUME: + r1 = r / Gage[j].rainInterval * 3600.0; + break; + + case CUMULATIVE_RAINFALL: + if ( r < Gage[j].rainAccum ) + r1 = r / Gage[j].rainInterval * 3600.0; + else r1 = (r - Gage[j].rainAccum) / Gage[j].rainInterval * 3600.0; + Gage[j].rainAccum = r; + break; + + default: r1 = r; + } + return r1 * Gage[j].unitsFactor * Adjust.rainFactor; +} + +//============================================================================= diff --git a/src/globals.h b/src/globals.h new file mode 100644 index 000000000..70f2431aa --- /dev/null +++ b/src/globals.h @@ -0,0 +1,172 @@ +//----------------------------------------------------------------------------- +// globals.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Global Variables +// +// Update History +// ============== +// Build 5.1.004: +// - Ignore RDII option added. +// Build 5.1.007: +// - Monthly climate variable adjustments added. +// Build 5.1.008: +// - Number of parallel threads for dynamic wave routing added. +// - Minimum dynamic wave routing variable time step added. +// Build 5.1.011: +// - Changed WarningCode to Warnings (# warnings issued) +// - Added error message text as a variable. +// - Added elapsed simulation time (in decimal days) variable. +// - Added variables associated with detailed routing events. +// Build 5.1.012: +// - InSteadyState variable made local to routing_execute in routing.c. +// Build 5.1.013: +// - CrownCutoff and RuleStep added as analysis option variables. +// Build 5.1.015: +// - Fixes bug in summary statistics when Report Start date > Start Date. +// Build 5.2.0: +// - Support for relative file names added. +//----------------------------------------------------------------------------- + +#ifndef GLOBALS_H +#define GLOBALS_H + + +EXTERN TFile + Finp, // Input file + Fout, // Output file + Frpt, // Report file + Fclimate, // Climate file + Frain, // Rainfall file + Frunoff, // Runoff file + Frdii, // RDII inflow file + Fhotstart1, // Hot start input file + Fhotstart2, // Hot start output file + Finflows, // Inflows routing file + Foutflows; // Outflows routing file + +EXTERN long + Nperiods, // Number of reporting periods + TotalStepCount, // Total routing steps used + ReportStepCount, // Reporting routing steps used + NonConvergeCount; // Number of non-converging steps + +EXTERN char + Msg[MAXMSG+1], // Text of output message + ErrorMsg[MAXMSG+1], // Text of error message + Title[MAXTITLE][MAXMSG+1],// Project title + TempDir[MAXFNAME+1], // Temporary file directory + InpDir[MAXFNAME+1]; // Input file directory + +EXTERN TRptFlags + RptFlags; // Reporting options + +EXTERN int + Nobjects[MAX_OBJ_TYPES], // Number of each object type + Nnodes[MAX_NODE_TYPES], // Number of each node sub-type + Nlinks[MAX_LINK_TYPES], // Number of each link sub-type + UnitSystem, // Unit system + FlowUnits, // Flow units + InfilModel, // Infiltration method + RouteModel, // Flow routing method + ForceMainEqn, // Flow equation for force mains + LinkOffsets, // Link offset convention + SurchargeMethod, // EXTRAN or SLOT method + AllowPonding, // Allow water to pond at nodes + InertDamping, // Degree of inertial damping + NormalFlowLtd, // Normal flow limited + SlopeWeighting, // Use slope weighting + Compatibility, // SWMM 5/3/4 compatibility + SkipSteadyState, // Skip over steady state periods + IgnoreRainfall, // Ignore rainfall/runoff + IgnoreRDII, // Ignore RDII + IgnoreSnowmelt, // Ignore snowmelt + IgnoreGwater, // Ignore groundwater + IgnoreRouting, // Ignore flow routing + IgnoreQuality, // Ignore water quality + ErrorCode, // Error code number + Warnings, // Number of warning messages + WetStep, // Runoff wet time step (sec) + DryStep, // Runoff dry time step (sec) + ReportStep, // Reporting time step (sec) + RuleStep, // Rule evaluation time step (sec) + SweepStart, // Day of year when sweeping starts + SweepEnd, // Day of year when sweeping ends + MaxTrials, // Max. trials for DW routing + NumThreads, // Number of parallel threads used + NumEvents; // Number of detailed events + +EXTERN double + RouteStep, // Routing time step (sec) + MinRouteStep, // Minimum variable time step (sec) + LengtheningStep, // Time step for lengthening (sec) + StartDryDays, // Antecedent dry days + CourantFactor, // Courant time step factor + MinSurfArea, // Minimum nodal surface area + MinSlope, // Minimum conduit slope + RunoffError, // Runoff continuity error + GwaterError, // Groundwater continuity error + FlowError, // Flow routing error + QualError, // Quality routing error + HeadTol, // DW routing head tolerance (ft) + SysFlowTol, // Tolerance for steady system flow + LatFlowTol, // Tolerance for steady nodal inflow + CrownCutoff; // Fractional pipe crown cutoff + +EXTERN DateTime + StartDate, // Starting date + StartTime, // Starting time + StartDateTime, // Starting Date+Time + EndDate, // Ending date + EndTime, // Ending time + EndDateTime, // Ending Date+Time + ReportStartDate, // Report start date + ReportStartTime, // Report start time + ReportStart; // Report start Date+Time + +EXTERN double + ReportTime, // Current reporting time (msec) + OldRunoffTime, // Previous runoff time (msec) + NewRunoffTime, // Current runoff time (msec) + OldRoutingTime, // Previous routing time (msec) + NewRoutingTime, // Current routing time (msec) + TotalDuration, // Simulation duration (msec) + ElapsedTime; // Current elapsed time (days) + +EXTERN TTemp Temp; // Temperature data +EXTERN TEvap Evap; // Evaporation data +EXTERN TWind Wind; // Wind speed data +EXTERN TSnow Snow; // Snow melt data +EXTERN TAdjust Adjust; // Climate adjustments + +EXTERN TSnowmelt* Snowmelt; // Array of snow melt objects +EXTERN TGage* Gage; // Array of rain gages +EXTERN TSubcatch* Subcatch; // Array of subcatchments +EXTERN TAquifer* Aquifer; // Array of groundwater aquifers +EXTERN TUnitHyd* UnitHyd; // Array of unit hydrographs +EXTERN TNode* Node; // Array of nodes +EXTERN TOutfall* Outfall; // Array of outfall nodes +EXTERN TDivider* Divider; // Array of divider nodes +EXTERN TStorage* Storage; // Array of storage nodes +EXTERN TLink* Link; // Array of links +EXTERN TConduit* Conduit; // Array of conduit links +EXTERN TPump* Pump; // Array of pump links +EXTERN TOrifice* Orifice; // Array of orifice links +EXTERN TWeir* Weir; // Array of weir links +EXTERN TOutlet* Outlet; // Array of outlet device links +EXTERN TPollut* Pollut; // Array of pollutants +EXTERN TLanduse* Landuse; // Array of landuses +EXTERN TPattern* Pattern; // Array of time patterns +EXTERN TTable* Curve; // Array of curve tables +EXTERN TTable* Tseries; // Array of time series tables +EXTERN TTransect* Transect; // Array of transect data +EXTERN TStreet* Street; // Array of defined Street cross-sections +EXTERN TShape* Shape; // Array of custom conduit shapes +EXTERN TEvent* Event; // Array of routing events + + +#endif //GLOBALS_H diff --git a/src/gwater.c b/src/gwater.c new file mode 100644 index 000000000..48db9de17 --- /dev/null +++ b/src/gwater.c @@ -0,0 +1,872 @@ +//----------------------------------------------------------------------------- +// gwater.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Groundwater functions. +// +// Update History +// ============== +// Build 5.1.007: +// - User-supplied function for deep GW seepage flow added. +// - New variable names for use in user-supplied GW flow equations added. +// Build 5.1.008: +// - More variable names for user-supplied GW flow equations added. +// - Subcatchment area made into a shared variable. +// - Evaporation loss initialized to 0. +// - Support for collecting GW statistics added. +// Build 5.1.010: +// - Unsaturated hydraulic conductivity added to GW flow equation variables. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" +#include "odesolve.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double GWTOL = 0.0001; // ODE solver tolerance +static const double XTOL = 0.001; // tolerance for moisture & depth + +enum GWstates {THETA, // moisture content of upper GW zone + LOWERDEPTH}; // depth of lower saturated GW zone + +enum GWvariables { + gwvHGW, // water table height (ft) + gwvHSW, // surface water height (ft) + gwvHCB, // channel bottom height (ft) + gwvHGS, // ground surface height (ft) + gwvKS, // sat. hyd. condutivity (ft/s) + gwvK, // unsat. hyd. conductivity (ft/s) + gwvTHETA, // upper zone moisture content + gwvPHI, // soil porosity + gwvFI, // surface infiltration (ft/s) + gwvFU, // uper zone percolation rate (ft/s) + gwvA, // subcatchment area (ft2) + gwvMAX}; + +// Names of GW variables that can be used in GW outflow expression +static char* GWVarWords[] = {"HGW", "HSW", "HCB", "HGS", "KS", "K", + "THETA", "PHI", "FI", "FU", "A", NULL}; + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +// NOTE: all flux rates are in ft/sec, all depths are in ft. +static double Area; // subcatchment area (ft2) +static double Infil; // infiltration rate from surface +static double MaxEvap; // max. evaporation rate +static double AvailEvap; // available evaporation rate +static double UpperEvap; // evaporation rate from upper GW zone +static double LowerEvap; // evaporation rate from lower GW zone +static double UpperPerc; // percolation rate from upper to lower zone +static double LowerLoss; // loss rate from lower GW zone +static double GWFlow; // flow rate from lower zone to conveyance node +static double MaxUpperPerc; // upper limit on UpperPerc +static double MaxGWFlowPos; // upper limit on GWFlow when its positve +static double MaxGWFlowNeg; // upper limit on GWFlow when its negative +static double FracPerv; // fraction of surface that is pervious +static double TotalDepth; // total depth of GW aquifer +static double Theta; // moisture content of upper zone +static double HydCon; // unsaturated hydraulic conductivity (ft/s) +static double Hgw; // ht. of saturated zone +static double Hstar; // ht. from aquifer bottom to node invert +static double Hsw; // ht. from aquifer bottom to water surface +static double Tstep; // current time step (sec) +static TAquifer A; // aquifer being analyzed +static TGroundwater* GW; // groundwater object being analyzed +static MathExpr* LatFlowExpr; // user-supplied lateral GW flow expression +static MathExpr* DeepFlowExpr; // user-supplied deep GW flow expression + +//----------------------------------------------------------------------------- +// External Functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// gwater_readAquiferParams (called by input_readLine) +// gwater_readGroundwaterParams (called by input_readLine) +// gwater_readFlowExpression (called by input_readLine) +// gwater_deleteFlowExpression (called by deleteObjects in project.c) +// gwater_validateAquifer (called by swmm_open) +// gwater_validate (called by subcatch_validate) +// gwater_initState (called by subcatch_initState) +// gwater_getVolume (called by massbal_open & massbal_getGwaterError) +// gwater_getGroundwater (called by getSubareaRunoff in subcatch.c) +// gwater_getState (called by saveRunoff in hotstart.c) +// gwater_setState (called by readRunoff in hotstart.c) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static void getDxDt(double t, double* x, double* dxdt); +static void getFluxes(double upperVolume, double lowerDepth); +static void getEvapRates(double theta, double upperDepth); +static double getUpperPerc(double theta, double upperDepth); +static double getGWFlow(double lowerDepth); +static void updateMassBal(double area, double tStep); + +// Used to process custom GW outflow equations +static int getVariableIndex(char* s); +static double getVariableValue(int varIndex); + +//============================================================================= + +int gwater_readAquiferParams(int j, char* tok[], int ntoks) +// +// Input: j = aquifer index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error message +// Purpose: reads aquifer parameter values from line of input data +// +// Data line contains following parameters: +// ID, porosity, wiltingPoint, fieldCapacity, conductivity, +// conductSlope, tensionSlope, upperEvapFraction, lowerEvapDepth, +// gwRecession, bottomElev, waterTableElev, upperMoisture +// (evapPattern) +// +{ + int i, p; + double x[12]; + char *id; + + // --- check that aquifer exists + if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(AQUIFER, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + + // --- read remaining tokens as numbers + for (i = 0; i < 11; i++) x[i] = 0.0; + for (i = 1; i < 13; i++) + { + if ( ! getDouble(tok[i], &x[i-1]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + + // --- read upper evap pattern if present + p = -1; + if ( ntoks > 13 ) + { + p = project_findObject(TIMEPATTERN, tok[13]); + if ( p < 0 ) return error_setInpError(ERR_NAME, tok[13]); + } + + // --- assign parameters to aquifer object + Aquifer[j].ID = id; + Aquifer[j].porosity = x[0]; + Aquifer[j].wiltingPoint = x[1]; + Aquifer[j].fieldCapacity = x[2]; + Aquifer[j].conductivity = x[3] / UCF(RAINFALL); + Aquifer[j].conductSlope = x[4]; + Aquifer[j].tensionSlope = x[5] / UCF(LENGTH); + Aquifer[j].upperEvapFrac = x[6]; + Aquifer[j].lowerEvapDepth = x[7] / UCF(LENGTH); + Aquifer[j].lowerLossCoeff = x[8] / UCF(RAINFALL); + Aquifer[j].bottomElev = x[9] / UCF(LENGTH); + Aquifer[j].waterTableElev = x[10] / UCF(LENGTH); + Aquifer[j].upperMoisture = x[11]; + Aquifer[j].upperEvapPat = p; + return 0; +} + +//============================================================================= + +int gwater_readGroundwaterParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads groundwater inflow parameters for a subcatchment from +// a line of input data. +// +// Data format is: +// subcatch aquifer node surfElev a1 b1 a2 b2 a3 fixedDepth + +// (nodeElev bottomElev waterTableElev upperMoisture ) +// +{ + int i, j, k, m, n; + double x[11]; + TGroundwater* gw; + + // --- check that specified subcatchment, aquifer & node exist + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + j = project_findObject(SUBCATCH, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- check for enough tokens + if ( ntoks < 11 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that specified aquifer and node exists + k = project_findObject(AQUIFER, tok[1]); + if ( k < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n = project_findObject(NODE, tok[2]); + if ( n < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // -- read in the flow parameters + for ( i = 0; i < 7; i++ ) + { + if ( ! getDouble(tok[i+3], &x[i]) ) + return error_setInpError(ERR_NUMBER, tok[i+3]); + } + + // --- read in optional depth parameters + for ( i = 7; i < 11; i++) + { + x[i] = MISSING; + m = i + 3; + if ( ntoks > m && *tok[m] != '*' ) + { + if (! getDouble(tok[m], &x[i]) ) + return error_setInpError(ERR_NUMBER, tok[m]); + if ( i < 10 ) x[i] /= UCF(LENGTH); + } + } + + // --- create a groundwater flow object + if ( !Subcatch[j].groundwater ) + { + gw = (TGroundwater *) malloc(sizeof(TGroundwater)); + if ( !gw ) return error_setInpError(ERR_MEMORY, ""); + Subcatch[j].groundwater = gw; + } + else gw = Subcatch[j].groundwater; + + // --- populate the groundwater flow object with its parameters + gw->aquifer = k; + gw->node = n; + gw->surfElev = x[0] / UCF(LENGTH); + gw->a1 = x[1]; + gw->b1 = x[2]; + gw->a2 = x[3]; + gw->b2 = x[4]; + gw->a3 = x[5]; + gw->fixedDepth = x[6] / UCF(LENGTH); + gw->nodeElev = x[7]; //already converted to ft. + gw->bottomElev = x[8]; + gw->waterTableElev = x[9]; + gw->upperMoisture = x[10]; + return 0; +} + +//============================================================================= + +int gwater_readFlowExpression(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads mathematical expression for lateral or deep groundwater +// flow for a subcatchment from a line of input data. +// +// Format is: subcatch LATERAL/DEEP +// where subcatch is the ID of the subcatchment, LATERAL is for lateral +// GW flow, DEEP is for deep GW flow and is any well-formed math +// expression. +// +{ + int i, j, k; + char exprStr[MAXLINE+1]; + MathExpr* expr; + + // --- return if too few tokens + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that subcatchment exists + j = project_findObject(SUBCATCH, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- check if expression is for lateral or deep GW flow + k = 1; + if ( match(tok[1], "LAT") ) k = 1; + else if ( match(tok[1], "DEEP") ) k = 2; + else return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- concatenate remaining tokens into a single string + sstrncpy(exprStr, tok[2], MAXLINE); + for ( i = 3; i < ntoks; i++) + { + sstrcat(exprStr, " ", MAXLINE+1); + sstrcat(exprStr, tok[i], MAXLINE+1); + } + + // --- delete any previous flow eqn. + if ( k == 1 ) mathexpr_delete(Subcatch[j].gwLatFlowExpr); + else mathexpr_delete(Subcatch[j].gwDeepFlowExpr); + + // --- create a parsed expression tree from the string expr + // (getVariableIndex is the function that converts a GW + // variable's name into an index number) + expr = mathexpr_create(exprStr, getVariableIndex); + if ( expr == NULL ) return error_setInpError(ERR_MATH_EXPR, ""); + + // --- save expression tree with the subcatchment + if ( k == 1 ) Subcatch[j].gwLatFlowExpr = expr; + else Subcatch[j].gwDeepFlowExpr = expr; + return 0; +} + +//============================================================================= + +void gwater_deleteFlowExpression(int j) +// +// Input: j = subcatchment index +// Output: none +// Purpose: deletes a subcatchment's custom groundwater flow expressions. +// +{ + mathexpr_delete(Subcatch[j].gwLatFlowExpr); + mathexpr_delete(Subcatch[j].gwDeepFlowExpr); +} + +//============================================================================= + +void gwater_validateAquifer(int j) +// +// Input: j = aquifer index +// Output: none +// Purpose: validates groundwater aquifer properties . +// +{ + int p; + + if ( Aquifer[j].porosity <= 0.0 + || Aquifer[j].fieldCapacity >= Aquifer[j].porosity + || Aquifer[j].wiltingPoint >= Aquifer[j].fieldCapacity + || Aquifer[j].conductivity <= 0.0 + || Aquifer[j].conductSlope < 0.0 + || Aquifer[j].tensionSlope < 0.0 + || Aquifer[j].upperEvapFrac < 0.0 + || Aquifer[j].lowerEvapDepth < 0.0 + || Aquifer[j].waterTableElev < Aquifer[j].bottomElev + || Aquifer[j].upperMoisture > Aquifer[j].porosity + || Aquifer[j].upperMoisture < Aquifer[j].wiltingPoint ) + report_writeErrorMsg(ERR_AQUIFER_PARAMS, Aquifer[j].ID); + + p = Aquifer[j].upperEvapPat; + if ( p >= 0 && Pattern[p].type != MONTHLY_PATTERN ) + { + report_writeErrorMsg(ERR_AQUIFER_PARAMS, Aquifer[j].ID); + } +} + +//============================================================================= + +void gwater_validate(int j) +{ + TAquifer a; // Aquifer data structure + TGroundwater* gw; // Groundwater data structure + + gw = Subcatch[j].groundwater; + if ( gw ) + { + a = Aquifer[gw->aquifer]; + + // ... use aquifer values for missing groundwater parameters + if ( gw->bottomElev == MISSING ) gw->bottomElev = a.bottomElev; + if ( gw->waterTableElev == MISSING ) gw->waterTableElev = a.waterTableElev; + if ( gw->upperMoisture == MISSING ) gw->upperMoisture = a.upperMoisture; + + // ... ground elevation can't be below water table elevation + if ( gw->surfElev < gw->waterTableElev ) + report_writeErrorMsg(ERR_GROUND_ELEV, Subcatch[j].ID); + } +} + +//============================================================================= + +void gwater_initState(int j) +// +// Input: j = subcatchment index +// Output: none +// Purpose: initializes state of subcatchment's groundwater. +// +{ + TAquifer a; // Aquifer data structure + TGroundwater* gw; // Groundwater data structure + + gw = Subcatch[j].groundwater; + if ( gw ) + { + a = Aquifer[gw->aquifer]; + + // ... initial moisture content + gw->theta = gw->upperMoisture; + if ( gw->theta >= a.porosity ) + { + gw->theta = a.porosity - XTOL; + } + + // ... initial depth of lower (saturated) zone + gw->lowerDepth = gw->waterTableElev - gw->bottomElev; + if ( gw->lowerDepth >= gw->surfElev - gw->bottomElev ) + { + gw->lowerDepth = gw->surfElev - gw->bottomElev - XTOL; + } + + // ... initial lateral groundwater outflow + gw->oldFlow = 0.0; + gw->newFlow = 0.0; + gw->evapLoss = 0.0; + + // ... initial available infiltration volume into upper zone + gw->maxInfilVol = (gw->surfElev - gw->waterTableElev) * + (a.porosity - gw->theta) / + subcatch_getFracPerv(j); + } +} + +//============================================================================= + +void gwater_getState(int j, double x[]) +// +// Input: j = subcatchment index +// Output: x[] = array of groundwater state variables +// Purpose: retrieves state of subcatchment's groundwater. +// +{ + TGroundwater* gw = Subcatch[j].groundwater; + x[0] = gw->theta; + x[1] = gw->bottomElev + gw->lowerDepth; + x[2] = gw->newFlow; + x[3] = gw->maxInfilVol; +} + +//============================================================================= + +void gwater_setState(int j, double x[]) +// +// Input: j = subcatchment index +// x[] = array of groundwater state variables +// Purpose: assigns values to a subcatchment's groundwater state. +// +{ + TGroundwater* gw = Subcatch[j].groundwater; + if ( gw == NULL ) return; + gw->theta = x[0]; + gw->lowerDepth = x[1] - gw->bottomElev; + gw->oldFlow = x[2]; + if ( x[3] != MISSING ) gw->maxInfilVol = x[3]; +} + +//============================================================================= + +double gwater_getVolume(int j) +// +// Input: j = subcatchment index +// Output: returns total volume of groundwater in ft/ft2 +// Purpose: finds volume of groundwater stored in upper & lower zones +// +{ + TAquifer a; + TGroundwater* gw; + double upperDepth; + gw = Subcatch[j].groundwater; + if ( gw == NULL ) return 0.0; + a = Aquifer[gw->aquifer]; + upperDepth = gw->surfElev - gw->bottomElev - gw->lowerDepth; + return (upperDepth * gw->theta) + (gw->lowerDepth * a.porosity); +} + +//============================================================================= + +void gwater_getGroundwater(int j, double evap, double infil, double tStep) +// +// Purpose: computes groundwater flow from subcatchment during current time step. +// Input: j = subcatchment index +// evap = pervious surface evaporation volume consumed (ft3) +// infil = surface infiltration volume (ft3) +// tStep = time step (sec) +// Output: none +// +{ + int n; // node exchanging groundwater + double x[2]; // upper moisture content & lower depth + double vUpper; // upper vol. available for percolation + double nodeFlow; // max. possible GW flow from node + + // --- save subcatchment's groundwater and aquifer objects to + // shared variables + GW = Subcatch[j].groundwater; + if ( GW == NULL ) return; + LatFlowExpr = Subcatch[j].gwLatFlowExpr; + DeepFlowExpr = Subcatch[j].gwDeepFlowExpr; + A = Aquifer[GW->aquifer]; + + // --- get fraction of total area that is pervious + FracPerv = subcatch_getFracPerv(j); + if ( FracPerv <= 0.0 ) return; + Area = Subcatch[j].area; + + // --- convert infiltration volume (ft3) to equivalent rate + // over entire GW (subcatchment) area + infil = infil / Area / tStep; + Infil = infil; + Tstep = tStep; + + // --- convert pervious surface evaporation already exerted (ft3) + // to equivalent rate over entire GW (subcatchment) area + evap = evap / Area / tStep; + + // --- convert max. surface evap rate (ft/sec) to a rate + // that applies to GW evap (GW evap can only occur + // through the pervious land surface area) + MaxEvap = Evap.rate * FracPerv; + + // --- available subsurface evaporation is difference between max. + // rate and pervious surface evap already exerted + AvailEvap = MAX((MaxEvap - evap), 0.0); + + // --- save total depth & outlet node properties to shared variables + TotalDepth = GW->surfElev - GW->bottomElev; + if ( TotalDepth <= 0.0 ) return; + n = GW->node; + + // --- establish min. water table height above aquifer bottom at which + // GW flow can occur (override node's invert if a value was provided + // in the GW object) + if ( GW->nodeElev != MISSING ) Hstar = GW->nodeElev - GW->bottomElev; + else Hstar = Node[n].invertElev - GW->bottomElev; + + // --- establish surface water height (relative to aquifer bottom) + // for drainage system node connected to the GW aquifer + if ( GW->fixedDepth > 0.0 ) + { + Hsw = GW->fixedDepth + Node[n].invertElev - GW->bottomElev; + } + else Hsw = Node[n].newDepth + Node[n].invertElev - GW->bottomElev; + + // --- store state variables (upper zone moisture content, lower zone + // depth) in work vector x + x[THETA] = GW->theta; + x[LOWERDEPTH] = GW->lowerDepth; + + // --- set limit on percolation rate from upper to lower GW zone + vUpper = (TotalDepth - x[LOWERDEPTH]) * (x[THETA] - A.fieldCapacity); + vUpper = MAX(0.0, vUpper); + MaxUpperPerc = vUpper / tStep; + + // --- set limit on GW flow out of aquifer based on volume of lower zone + MaxGWFlowPos = x[LOWERDEPTH]*A.porosity / tStep; + + // --- set limit on GW flow into aquifer from drainage system node + // based on min. of capacity of upper zone and drainage system + // inflow to the node + MaxGWFlowNeg = (TotalDepth - x[LOWERDEPTH]) * (A.porosity - x[THETA]) + / tStep; + nodeFlow = (Node[n].inflow + Node[n].newVolume/tStep) / Area; + MaxGWFlowNeg = -MIN(MaxGWFlowNeg, nodeFlow); + + // --- integrate eqns. for d(Theta)/dt and d(LowerDepth)/dt + // NOTE: ODE solver must have been initialized previously + odesolve_integrate(x, 2, 0, tStep, GWTOL, tStep, getDxDt); + + // --- keep state variables within allowable bounds + x[THETA] = MAX(x[THETA], A.wiltingPoint); + if ( x[THETA] >= A.porosity ) + { + x[THETA] = A.porosity - XTOL; + x[LOWERDEPTH] = TotalDepth - XTOL; + } + x[LOWERDEPTH] = MAX(x[LOWERDEPTH], 0.0); + if ( x[LOWERDEPTH] >= TotalDepth ) + { + x[LOWERDEPTH] = TotalDepth - XTOL; + } + + // --- save new values of state values + GW->theta = x[THETA]; + GW->lowerDepth = x[LOWERDEPTH]; + getFluxes(GW->theta, GW->lowerDepth); + GW->oldFlow = GW->newFlow; + GW->newFlow = GWFlow; + GW->evapLoss = UpperEvap + LowerEvap; + + //--- find max. infiltration volume (as depth over + // the pervious portion of the subcatchment) + // that upper zone can support in next time step + GW->maxInfilVol = (TotalDepth - x[LOWERDEPTH]) * + (A.porosity - x[THETA]) / FracPerv; + + // --- update GW mass balance + updateMassBal(Area, tStep); + + // --- update GW statistics + stats_updateGwaterStats(j, infil, GW->evapLoss, GWFlow, LowerLoss, + GW->theta, GW->lowerDepth + GW->bottomElev, tStep); +} + +//============================================================================= + +void updateMassBal(double area, double tStep) +// +// Input: area = subcatchment area (ft2) +// tStep = time step (sec) +// Output: none +// Purpose: updates GW mass balance with volumes of water fluxes. +// +{ + double vInfil; // infiltration volume + double vUpperEvap; // upper zone evap. volume + double vLowerEvap; // lower zone evap. volume + double vLowerPerc; // lower zone deep perc. volume + double vGwater; // volume of exchanged groundwater + double ft2sec = area * tStep; + + vInfil = Infil * ft2sec; + vUpperEvap = UpperEvap * ft2sec; + vLowerEvap = LowerEvap * ft2sec; + vLowerPerc = LowerLoss * ft2sec; + vGwater = 0.5 * (GW->oldFlow + GW->newFlow) * ft2sec; + massbal_updateGwaterTotals(vInfil, vUpperEvap, vLowerEvap, vLowerPerc, + vGwater); +} + +//============================================================================= + +void getFluxes(double theta, double lowerDepth) +// +// Input: upperVolume = vol. depth of upper zone (ft) +// upperDepth = depth of upper zone (ft) +// Output: none +// Purpose: computes water fluxes into/out of upper/lower GW zones. +// +{ + double upperDepth; + + // --- find upper zone depth + lowerDepth = MAX(lowerDepth, 0.0); + lowerDepth = MIN(lowerDepth, TotalDepth); + upperDepth = TotalDepth - lowerDepth; + + // --- save lower depth and theta to global variables + Hgw = lowerDepth; + Theta = theta; + + // --- find evaporation rate from both zones + getEvapRates(theta, upperDepth); + + // --- find percolation rate from upper to lower zone + UpperPerc = getUpperPerc(theta, upperDepth); + UpperPerc = MIN(UpperPerc, MaxUpperPerc); + + // --- find loss rate to deep GW + if ( DeepFlowExpr != NULL ) + LowerLoss = mathexpr_eval(DeepFlowExpr, getVariableValue) / + UCF(RAINFALL); + else + LowerLoss = A.lowerLossCoeff * lowerDepth / TotalDepth; + LowerLoss = MIN(LowerLoss, lowerDepth/Tstep); + + // --- find GW flow rate from lower zone to drainage system node + GWFlow = getGWFlow(lowerDepth); + if ( LatFlowExpr != NULL ) + { + GWFlow += mathexpr_eval(LatFlowExpr, getVariableValue) / UCF(GWFLOW); + } + if ( GWFlow >= 0.0 ) GWFlow = MIN(GWFlow, MaxGWFlowPos); + else GWFlow = MAX(GWFlow, MaxGWFlowNeg); +} + +//============================================================================= + +void getDxDt(double t, double* x, double* dxdt) +// +// Input: t = current time (not used) +// x = array of state variables +// Output: dxdt = array of time derivatives of state variables +// Purpose: computes time derivatives of upper moisture content +// and lower depth. +// +{ + double qUpper; // inflow - outflow for upper zone (ft/sec) + double qLower; // inflow - outflow for lower zone (ft/sec) + double denom; + + getFluxes(x[THETA], x[LOWERDEPTH]); + qUpper = Infil - UpperEvap - UpperPerc; + qLower = UpperPerc - LowerLoss - LowerEvap - GWFlow; + + // --- d(upper zone moisture)/dt = (net upper zone flow) / + // (upper zone depth) + denom = TotalDepth - x[LOWERDEPTH]; + if (denom > 0.0) + dxdt[THETA] = qUpper / denom; + else + dxdt[THETA] = 0.0; + + // --- d(lower zone depth)/dt = (net lower zone flow) / + // (upper zone moisture deficit) + denom = A.porosity - x[THETA]; + if (denom > 0.0) + dxdt[LOWERDEPTH] = qLower / denom; + else + dxdt[LOWERDEPTH] = 0.0; +} + +//============================================================================= + +void getEvapRates(double theta, double upperDepth) +// +// Input: theta = moisture content of upper zone +// upperDepth = depth of upper zone (ft) +// Output: none +// Purpose: computes evapotranspiration out of upper & lower zones. +// +{ + int p, month; + double f; + double lowerFrac, upperFrac; + + // --- no GW evaporation when infiltration is occurring + UpperEvap = 0.0; + LowerEvap = 0.0; + if ( Infil > 0.0 ) return; + + // --- get monthly-adjusted upper zone evap fraction + upperFrac = A.upperEvapFrac; + f = 1.0; + p = A.upperEvapPat; + if ( p >= 0 ) + { + month = datetime_monthOfYear(getDateTime(NewRunoffTime)); + f = Pattern[p].factor[month-1]; + } + upperFrac *= f; + + // --- upper zone evaporation requires that soil moisture + // be above the wilting point + if ( theta > A.wiltingPoint ) + { + // --- actual evap is upper zone fraction applied to max. potential + // rate, limited by the available rate after any surface evap + UpperEvap = upperFrac * MaxEvap; + UpperEvap = MIN(UpperEvap, AvailEvap); + } + + // --- check if lower zone evaporation is possible + if ( A.lowerEvapDepth > 0.0 ) + { + // --- find the fraction of the lower evaporation depth that + // extends into the saturated lower zone + lowerFrac = (A.lowerEvapDepth - upperDepth) / A.lowerEvapDepth; + lowerFrac = MAX(0.0, lowerFrac); + lowerFrac = MIN(lowerFrac, 1.0); + + // --- make the lower zone evap rate proportional to this fraction + // and the evap not used in the upper zone + LowerEvap = lowerFrac * (1.0 - upperFrac) * MaxEvap; + LowerEvap = MIN(LowerEvap, (AvailEvap - UpperEvap)); + } +} + +//============================================================================= + +double getUpperPerc(double theta, double upperDepth) +// +// Input: theta = moisture content of upper zone +// upperDepth = depth of upper zone (ft) +// Output: returns percolation rate (ft/sec) +// Purpose: finds percolation rate from upper to lower zone. +// +{ + double delta; // unfilled water content of upper zone + double dhdz; // avg. change in head with depth + double hydcon; // unsaturated hydraulic conductivity + + // --- no perc. from upper zone if no depth or moisture content too low + if ( upperDepth <= 0.0 || theta <= A.fieldCapacity ) return 0.0; + + // --- compute hyd. conductivity as function of moisture content + delta = theta - A.porosity; + hydcon = A.conductivity * exp(delta * A.conductSlope); + + // --- compute integral of dh/dz term + delta = theta - A.fieldCapacity; + dhdz = 1.0 + A.tensionSlope * 2.0 * delta / upperDepth; + + // --- compute upper zone percolation rate + HydCon = hydcon; + return hydcon * dhdz; +} + +//============================================================================= + +double getGWFlow(double lowerDepth) +// +// Input: lowerDepth = depth of lower zone (ft) +// Output: returns groundwater flow rate (ft/sec) +// Purpose: finds groundwater outflow from lower saturated zone. +// +{ + double q, t1, t2, t3; + + // --- water table must be above Hstar for flow to occur + if ( lowerDepth <= Hstar ) return 0.0; + + // --- compute groundwater component of flow + if ( GW->b1 == 0.0 ) t1 = GW->a1; + else t1 = GW->a1 * pow( (lowerDepth - Hstar)*UCF(LENGTH), GW->b1); + + // --- compute surface water component of flow + if ( GW->b2 == 0.0 ) t2 = GW->a2; + else if (Hsw > Hstar) + { + t2 = GW->a2 * pow( (Hsw - Hstar)*UCF(LENGTH), GW->b2); + } + else t2 = 0.0; + + // --- compute groundwater/surface water interaction term + t3 = GW->a3 * lowerDepth * Hsw * UCF(LENGTH) * UCF(LENGTH); + + // --- compute total groundwater flow + q = (t1 - t2 + t3) / UCF(GWFLOW); + if ( q < 0.0 && GW->a3 != 0.0 ) q = 0.0; + return q; +} + +//============================================================================= + +int getVariableIndex(char* s) +// +// Input: s = name of a groundwater variable +// Output: returns index of groundwater variable +// Purpose: finds position of GW variable in list of GW variable names. +// +{ + int k; + + k = findmatch(s, GWVarWords); + if ( k >= 0 ) return k; + return -1; +} + +//============================================================================= + +double getVariableValue(int varIndex) +// +// Input: varIndex = index of a GW variable +// Output: returns current value of GW variable +// Purpose: finds current value of a GW variable. +// +{ + switch (varIndex) + { + case gwvHGW: return Hgw * UCF(LENGTH); + case gwvHSW: return Hsw * UCF(LENGTH); + case gwvHCB: return Hstar * UCF(LENGTH); + case gwvHGS: return TotalDepth * UCF(LENGTH); + case gwvKS: return A.conductivity * UCF(RAINFALL); + case gwvK: return HydCon * UCF(RAINFALL); + case gwvTHETA:return Theta; + case gwvPHI: return A.porosity; + case gwvFI: return Infil * UCF(RAINFALL); + case gwvFU: return UpperPerc * UCF(RAINFALL); + case gwvA: return Area * UCF(LANDAREA); + default: return 0.0; + } +} diff --git a/src/hash.c b/src/hash.c new file mode 100644 index 000000000..4a73e6ccd --- /dev/null +++ b/src/hash.c @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------------- +// hash.c +// +// Implementation of a simple Hash Table for string storage & retrieval +// CASE INSENSITIVE +// +// Written by L. Rossman +// Last Updated on 6/19/03 +// +// The hash table data structure (HTable) is defined in "hash.h". +// Interface Functions: +// HTcreate() - creates a hash table +// HTinsert() - inserts a string & its index value into a hash table +// HTfind() - retrieves the index value of a string from a table +// HTfree() - frees a hash table +//----------------------------------------------------------------------------- + +#include +#include +#include "hash.h" +#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) + +/* Case-insensitive comparison of strings s1 and s2 */ +int samestr(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); + return(0); +} /* End of samestr */ + +/* Use Fletcher's checksum to compute 2-byte hash of string */ +unsigned int hash(const char *str) +{ + unsigned int sum1= 0, check1; + unsigned long sum2= 0L; + while( '\0' != *str ) + { + sum1 += UCHAR(*str); + str++; + if ( 255 <= sum1 ) sum1 -= 255; + sum2 += sum1; + } + check1= sum2; + check1 %= 255; + check1= 255 - (sum1+check1) % 255; + sum1= 255 - (sum1+check1) % 255; + return( ( ( check1 << 8 ) | sum1 ) % HTMAXSIZE); +} + +HTtable *HTcreate() +{ + int i; + HTtable *ht = (HTtable *) calloc(HTMAXSIZE, sizeof(HTtable)); + if (ht != NULL) for (i=0; i= HTMAXSIZE ) return(0); + entry = (struct HTentry *) malloc(sizeof(struct HTentry)); + if (entry == NULL) return(0); + entry->key = key; + entry->data = data; + entry->next = ht[i]; + ht[i] = entry; + return(1); +} + +int HTfind(HTtable *ht, const char *key) +{ + unsigned int i = hash(key); + struct HTentry *entry; + if ( i >= HTMAXSIZE ) return(NOTFOUND); + entry = ht[i]; + while (entry != NULL) + { + if ( samestr(entry->key,key) ) return(entry->data); + entry = entry->next; + } + return(NOTFOUND); +} + +char *HTfindKey(HTtable *ht, const char *key) +{ + unsigned int i = hash(key); + struct HTentry *entry; + if ( i >= HTMAXSIZE ) return(NULL); + entry = ht[i]; + while (entry != NULL) + { + if ( samestr(entry->key,key) ) return(entry->key); + entry = entry->next; + } + return(NULL); +} + +void HTfree(HTtable *ht) +{ + struct HTentry *entry, + *nextentry; + int i; + for (i=0; inext; + free(entry); + entry = nextentry; + } + } + free(ht); +} diff --git a/src/hash.h b/src/hash.h new file mode 100644 index 000000000..ba42be00e --- /dev/null +++ b/src/hash.h @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// hash.h +// +// Header file for Hash Table module hash.c. +//----------------------------------------------------------------------------- + +#ifndef HASH_H +#define HASH_H + + +#define HTMAXSIZE 1999 +#define NOTFOUND -1 + +struct HTentry +{ + char *key; + int data; + struct HTentry *next; +}; + +typedef struct HTentry *HTtable; + +HTtable* HTcreate(void); +int HTinsert(HTtable *, char *, int); +int HTfind(HTtable *, const char *); +char* HTfindKey(HTtable *, const char *); +void HTfree(HTtable *); + + +#endif //HASH_H diff --git a/src/headers.h b/src/headers.h new file mode 100644 index 000000000..87139b060 --- /dev/null +++ b/src/headers.h @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------------- +// headers.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Header files included in most SWMM5 modules. +// +// DO NOT CHANGE THE ORDER OF THE #INCLUDE STATEMENTS +//----------------------------------------------------------------------------- +#include "macros.h" +#include "objects.h" +#define EXTERN extern +#include "globals.h" +#include "funcs.h" +#include "error.h" +#include "text.h" +#include "keywords.h" diff --git a/src/hotstart.c b/src/hotstart.c new file mode 100644 index 000000000..69f7abcc9 --- /dev/null +++ b/src/hotstart.c @@ -0,0 +1,544 @@ +//----------------------------------------------------------------------------- +// hotstart.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Hot Start file functions. +// +// A SWMM hot start file contains the state of a SWMM project after +// a simulation has been run, allowing it to be used to initialize +// a subsequent simulation that picks up where the previous run ended. +// +// An abridged version of the hot start file (version 2) is available +// that contains only variables that appear in the binary output file +// (groundwater upper moisture and water table elevation, node depth, +// lateral inflow, and quality, and link flow, depth, setting and quality). +// +// When reading a previously saved hot start file checks are made to +// insure the the current SWMM project has the same number of major +// components (subcatchments, land uses, nodes, links, and pollutants) +// and unit system as does the hot start file. No test is made to +// insure that these components are of the same sub-type and maintain +// the same order as when the hot start file was created. +// +// Update History +// ============== +// Build 5.1.008: +// - Storage node hydraulic residence time (HRT) was added to the file. +// - Link control settings are now applied when reading a hot start file. +// - Runoff read from file assigned to newRunoff property instead of oldRunoff. +// - Array indexing bug when reading snowpack state from file fixed. +// Build 5.1.011: +// - Link control setting bug when reading a hot start file fixed. +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Local Variables +//----------------------------------------------------------------------------- +static int fileVersion; + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// hotstart_open (called by swmm_start in swmm5.c) +// hotstart_close (called by swmm_end in swmm5.c) + +//----------------------------------------------------------------------------- +// Function declarations +//----------------------------------------------------------------------------- +static int openHotstartFile1(void); +static int openHotstartFile2(void); +static void readRunoff(void); +static void saveRunoff(void); +static void readRouting(void); +static void saveRouting(void); +static int readFloat(float *x, FILE* f); +static int readDouble(double* x, FILE* f); + +//============================================================================= + +int hotstart_open() +{ + // --- open hot start files + if ( !openHotstartFile1() ) return FALSE; //input hot start file + if ( !openHotstartFile2() ) return FALSE; //output hot start file + return TRUE; +} + +//============================================================================= + +void hotstart_close() +{ + if ( Fhotstart2.file ) + { + saveRunoff(); + saveRouting(); + fclose(Fhotstart2.file); + } +} + +//============================================================================= + +int openHotstartFile1() +// +// Input: none +// Output: none +// Purpose: opens a previously saved routing hotstart file. +// +{ + int nSubcatch; + int nNodes; + int nLinks; + int nPollut; + int nLandUses; + int flowUnits; + char fStamp[] = "SWMM5-HOTSTART"; + char fileStamp[] = "SWMM5-HOTSTART"; + char fStampx[] = "SWMM5-HOTSTARTx"; + char fileStamp2[] = "SWMM5-HOTSTART2"; + char fileStamp3[] = "SWMM5-HOTSTART3"; + char fileStamp4[] = "SWMM5-HOTSTART4"; + + // --- try to open the file + if ( Fhotstart1.mode != USE_FILE ) return TRUE; + if ( (Fhotstart1.file = fopen(Fhotstart1.name, "r+b")) == NULL) + { + report_writeErrorMsg(ERR_HOTSTART_FILE_OPEN, Fhotstart1.name); + return FALSE; + } + + // --- check that file contains proper header records + fread(fStampx, sizeof(char), strlen(fileStamp2), Fhotstart1.file); + if ( strcmp(fStampx, fileStamp4) == 0 ) fileVersion = 4; + else if ( strcmp(fStampx, fileStamp3) == 0 ) fileVersion = 3; + else if ( strcmp(fStampx, fileStamp2) == 0 ) fileVersion = 2; + else + { + rewind(Fhotstart1.file); + fread(fStamp, sizeof(char), strlen(fileStamp), Fhotstart1.file); + if ( strcmp(fStamp, fileStamp) != 0 ) + { + report_writeErrorMsg(ERR_HOTSTART_FILE_FORMAT, ""); + return FALSE; + } + fileVersion = 1; + } + + nSubcatch = -1; + nNodes = -1; + nLinks = -1; + nPollut = -1; + nLandUses = -1; + flowUnits = -1; + if ( fileVersion >= 2 ) + { + fread(&nSubcatch, sizeof(int), 1, Fhotstart1.file); + } + else nSubcatch = Nobjects[SUBCATCH]; + if ( fileVersion >= 3 ) + { + fread(&nLandUses, sizeof(int), 1, Fhotstart1.file); + } + else nLandUses = Nobjects[LANDUSE]; + fread(&nNodes, sizeof(int), 1, Fhotstart1.file); + fread(&nLinks, sizeof(int), 1, Fhotstart1.file); + fread(&nPollut, sizeof(int), 1, Fhotstart1.file); + fread(&flowUnits, sizeof(int), 1, Fhotstart1.file); + if ( nSubcatch != Nobjects[SUBCATCH] + || nLandUses != Nobjects[LANDUSE] + || nNodes != Nobjects[NODE] + || nLinks != Nobjects[LINK] + || nPollut != Nobjects[POLLUT] + || flowUnits != FlowUnits ) + { + report_writeErrorMsg(ERR_HOTSTART_FILE_FORMAT, ""); + return FALSE; + } + + // --- read contents of the file and close it + if ( fileVersion >= 3 ) readRunoff(); + readRouting(); + fclose(Fhotstart1.file); + if ( ErrorCode ) return FALSE; + else return TRUE; +} + +//============================================================================= + +int openHotstartFile2() +// +// Input: none +// Output: none +// Purpose: opens a new routing hotstart file to save results to. +// +{ + int nSubcatch; + int nLandUses; + int nNodes; + int nLinks; + int nPollut; + int flowUnits; + char fileStamp[] = "SWMM5-HOTSTART4"; + + // --- try to open file + if ( Fhotstart2.mode != SAVE_FILE ) return TRUE; + if ( (Fhotstart2.file = fopen(Fhotstart2.name, "w+b")) == NULL) + { + report_writeErrorMsg(ERR_HOTSTART_FILE_OPEN, Fhotstart2.name); + return FALSE; + } + + // --- write file stamp & number of objects to file + nSubcatch = Nobjects[SUBCATCH]; + nLandUses = Nobjects[LANDUSE]; + nNodes = Nobjects[NODE]; + nLinks = Nobjects[LINK]; + nPollut = Nobjects[POLLUT]; + flowUnits = FlowUnits; + fwrite(fileStamp, sizeof(char), strlen(fileStamp), Fhotstart2.file); + fwrite(&nSubcatch, sizeof(int), 1, Fhotstart2.file); + fwrite(&nLandUses, sizeof(int), 1, Fhotstart2.file); + fwrite(&nNodes, sizeof(int), 1, Fhotstart2.file); + fwrite(&nLinks, sizeof(int), 1, Fhotstart2.file); + fwrite(&nPollut, sizeof(int), 1, Fhotstart2.file); + fwrite(&flowUnits, sizeof(int), 1, Fhotstart2.file); + return TRUE; +} + +//============================================================================= + +void saveRouting() +// +// Input: none +// Output: none +// Purpose: saves current state of all nodes and links to hotstart file. +// +{ + int i, j; + float x[3]; + + for (i = 0; i < Nobjects[NODE]; i++) + { + x[0] = (float)Node[i].newDepth; + x[1] = (float)Node[i].newLatFlow; + fwrite(x, sizeof(float), 2, Fhotstart2.file); + + if ( Node[i].type == STORAGE ) + { + j = Node[i].subIndex; + x[0] = (float)Storage[j].hrt; + fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); + } + + for (j = 0; j < Nobjects[POLLUT]; j++) + { + x[0] = (float)Node[i].newQual[j]; + fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); + } + } + for (i = 0; i < Nobjects[LINK]; i++) + { + x[0] = (float)Link[i].newFlow; + x[1] = (float)Link[i].newDepth; + x[2] = (float)Link[i].setting; + fwrite(x, sizeof(float), 3, Fhotstart2.file); + for (j = 0; j < Nobjects[POLLUT]; j++) + { + x[0] = (float)Link[i].newQual[j]; + fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); + } + } +} + +//============================================================================= + +void readRouting() +// +// Input: none +// Output: none +// Purpose: reads initial state of all nodes, links and groundwater objects +// from hotstart file. +// +{ + int i, j; + float x; + double xgw[4]; + FILE* f = Fhotstart1.file; + + // --- for file format 2, assign GW moisture content and lower depth + if ( fileVersion == 2 ) + { + // --- flow and available upper zone volume not used + xgw[2] = 0.0; + xgw[3] = MISSING; + for (i = 0; i < Nobjects[SUBCATCH]; i++) + { + // --- read moisture content and water table elevation as floats + if ( !readFloat(&x, f) ) return; + xgw[0] = x; + if ( !readFloat(&x, f) ) return; + xgw[1] = x; + + // --- set GW state + if ( Subcatch[i].groundwater != NULL ) gwater_setState(i, xgw); + } + } + + // --- read node states + for (i = 0; i < Nobjects[NODE]; i++) + { + if ( !readFloat(&x, f) ) return; + Node[i].newDepth = x; + if ( !readFloat(&x, f) ) return; + Node[i].newLatFlow = x; + + if ( fileVersion >= 4 && Node[i].type == STORAGE ) + { + if ( !readFloat(&x, f) ) return; + j = Node[i].subIndex; + Storage[j].hrt = x; + } + + for (j = 0; j < Nobjects[POLLUT]; j++) + { + if ( !readFloat(&x, f) ) return; + Node[i].newQual[j] = x; + } + + // --- read in zeros here for backwards compatibility + if ( fileVersion <= 2 ) + { + for (j = 0; j < Nobjects[POLLUT]; j++) + { + if ( !readFloat(&x, f) ) return; + } + } + } + + // --- read link states + for (i = 0; i < Nobjects[LINK]; i++) + { + if ( !readFloat(&x, f) ) return; + Link[i].newFlow = x; + if ( !readFloat(&x, f) ) return; + Link[i].newDepth = x; + if ( !readFloat(&x, f) ) return; + Link[i].setting = x; + + // --- set link's target setting to saved setting + Link[i].targetSetting = x; + link_setTargetSetting(i); + link_setSetting(i, 0.0); + + for (j = 0; j < Nobjects[POLLUT]; j++) + { + if ( !readFloat(&x, f) ) return; + Link[i].newQual[j] = x; + } + + } +} + +//============================================================================= + +void saveRunoff(void) +// +// Input: none +// Output: none +// Purpose: saves current state of all subcatchments to hotstart file. +// +{ + int i, j, k; + double x[6]; + FILE* f = Fhotstart2.file; + + for (i = 0; i < Nobjects[SUBCATCH]; i++) + { + // Ponded depths for each sub-area & total runoff (4 elements) + for (j = 0; j < 3; j++) x[j] = Subcatch[i].subArea[j].depth; + x[3] = Subcatch[i].newRunoff; + fwrite(x, sizeof(double), 4, f); + + // Infiltration state (max. of 6 elements) + for (j=0; j<6; j++) x[j] = 0.0; + infil_getState(i, x); + fwrite(x, sizeof(double), 6, f); + + // Groundwater state (4 elements) + if ( Subcatch[i].groundwater != NULL ) + { + gwater_getState(i, x); + fwrite(x, sizeof(double), 4, f); + } + + // Snowpack state (5 elements for each of 3 snow surfaces) + if ( Subcatch[i].snowpack != NULL ) + { + for (j=0; j<3; j++) + { + snow_getState(i, j, x); + fwrite(x, sizeof(double), 5, f); + } + } + + // Water quality + if ( Nobjects[POLLUT] > 0 ) + { + // Runoff quality + for (j=0; j 0 ) + { + // Runoff quality + for (j=0; j +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// Imported variables +//----------------------------------------------------------------------------- +extern double Qcf[]; // flow units conversion factors + // (see swmm5.c) + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +static int IfaceFlowUnits; // flow units for routing interface file +static int IfaceStep; // interface file time step (sec) +static int NumIfacePolluts; // number of pollutants in interface file +static int* IfacePolluts; // indexes of interface file pollutants +static int NumIfaceNodes; // number of nodes on interface file +static int* IfaceNodes; // indexes of nodes on interface file +static double** OldIfaceValues; // interface flows & WQ at previous time +static double** NewIfaceValues; // interface flows & WQ at next time +static double IfaceFrac; // fraction of interface file time step +static DateTime OldIfaceDate; // previous date of interface values +static DateTime NewIfaceDate; // next date of interface values + +//----------------------------------------------------------------------------- +// External Functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// iface_readFileParams (called by input_readLine) +// iface_openRoutingFiles (called by routing_open) +// iface_closeRoutingFiles (called by routing_close) +// iface_getNumIfaceNodes (called by addIfaceInflows in routing.c) +// iface_getIfaceNode (called by addIfaceInflows in routing.c) +// iface_getIfaceFlow (called by addIfaceInflows in routing.c) +// iface_getIfaceQual (called by addIfaceInflows in routing.c) +// iface_saveOutletResults (called by output_saveResults) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static void openFileForOutput(void); +static void openFileForInput(void); +static int getIfaceFilePolluts(void); +static int getIfaceFileNodes(void); +static void setOldIfaceValues(void); +static void readNewIfaceValues(void); +static int isOutletNode(int node); + +//============================================================================= + +int iface_readFileParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads interface file information from a line of input data. +// +// Data format is: +// USE/SAVE FileType FileName +// +{ + char k; + int j; + char fname[MAXFNAME+1]; + + // --- determine file disposition and type + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + k = (char)findmatch(tok[0], FileModeWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); + j = findmatch(tok[1], FileTypeWords); + if ( j < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + if ( ntoks < 3 ) return 0; + sstrncpy(fname, tok[2], MAXFNAME); + + // --- process file name + switch ( j ) + { + case RAINFALL_FILE: + Frain.mode = k; + sstrncpy(Frain.name, addAbsolutePath(fname), MAXFNAME); + break; + + case RUNOFF_FILE: + Frunoff.mode = k; + sstrncpy(Frunoff.name, addAbsolutePath(fname), MAXFNAME); + break; + + case HOTSTART_FILE: + if ( k == USE_FILE ) + { + Fhotstart1.mode = k; + sstrncpy(Fhotstart1.name, addAbsolutePath(fname), MAXFNAME); + } + else if ( k == SAVE_FILE ) + { + Fhotstart2.mode = k; + sstrncpy(Fhotstart2.name, addAbsolutePath(fname), MAXFNAME); + } + break; + + case RDII_FILE: + Frdii.mode = k; + sstrncpy(Frdii.name, fname, MAXFNAME); + break; + + case INFLOWS_FILE: + if ( k != USE_FILE ) return error_setInpError(ERR_ITEMS, ""); + Finflows.mode = k; + sstrncpy(Finflows.name, addAbsolutePath(fname), MAXFNAME); + break; + + case OUTFLOWS_FILE: + if ( k != SAVE_FILE ) return error_setInpError(ERR_ITEMS, ""); + Foutflows.mode = k; + sstrncpy(Foutflows.name, addAbsolutePath(fname), MAXFNAME); + break; + } + return 0; +} + +//============================================================================= + +void iface_openRoutingFiles() +// +// Input: none +// Output: none +// Purpose: opens routing interface files. +// +{ + // --- initialize shared variables + NumIfacePolluts = 0; + IfacePolluts = NULL; + NumIfaceNodes = 0; + IfaceNodes = NULL; + OldIfaceValues = NULL; + NewIfaceValues = NULL; + + // --- check that inflows & outflows files are not the same + if ( Foutflows.mode != NO_FILE && Finflows.mode != NO_FILE ) + { + if ( strcomp(Foutflows.name, Finflows.name) ) + { + report_writeErrorMsg(ERR_ROUTING_FILE_NAMES, ""); + return; + } + } + + // --- open the file for reading or writing + if ( Foutflows.mode == SAVE_FILE ) openFileForOutput(); + if ( Finflows.mode == USE_FILE ) openFileForInput(); +} + +//============================================================================= + +void iface_closeRoutingFiles() +// +// Input: none +// Output: none +// Purpose: closes routing interface files. +// +{ + FREE(IfacePolluts); + FREE(IfaceNodes); + if ( OldIfaceValues != NULL ) project_freeMatrix(OldIfaceValues); + if ( NewIfaceValues != NULL ) project_freeMatrix(NewIfaceValues); + if ( Finflows.file ) fclose(Finflows.file); + if ( Foutflows.file ) fclose(Foutflows.file); +} + +//============================================================================= + +int iface_getNumIfaceNodes(DateTime currentDate) +// +// Input: currentDate = current date/time +// Output: returns number of interface nodes if data exists or +// 0 otherwise +// Purpose: reads inflow data from interface file at current date. +// +{ + // --- return 0 if file begins after current date + if ( OldIfaceDate > currentDate ) return 0; + + // --- keep updating new interface values until current date bracketed + while ( NewIfaceDate < currentDate && NewIfaceDate != NO_DATE ) + { + setOldIfaceValues(); + readNewIfaceValues(); + } + + // --- return 0 if no data available + if ( NewIfaceDate == NO_DATE ) return 0; + + // --- find fraction current date is bewteen old & new interface dates + IfaceFrac = (currentDate - OldIfaceDate) / (NewIfaceDate - OldIfaceDate); + IfaceFrac = MAX(0.0, IfaceFrac); + IfaceFrac = MIN(IfaceFrac, 1.0); + + // --- return number of interface nodes + return NumIfaceNodes; +} + +//============================================================================= + +int iface_getIfaceNode(int index) +// +// Input: index = interface file node index +// Output: returns project node index +// Purpose: finds index of project node associated with interface node index +// +{ + if ( index >= 0 && index < NumIfaceNodes ) return IfaceNodes[index]; + else return -1; +} + +//============================================================================= + +double iface_getIfaceFlow(int index) +// +// Input: index = interface file node index +// Output: returns inflow to node +// Purpose: finds interface flow for particular node index. +// +{ + double q1, q2; + + if ( index >= 0 && index < NumIfaceNodes ) + { + // --- interpolate flow between old and new values + q1 = OldIfaceValues[index][0]; + q2 = NewIfaceValues[index][0]; + return (1.0 - IfaceFrac)*q1 + IfaceFrac*q2; + } + else return 0.0; +} + +//============================================================================= + +double iface_getIfaceQual(int index, int pollut) +// +// Input: index = index of node on interface file +// pollut = index of pollutant on interface file +// Output: returns inflow pollutant concentration +// Purpose: finds interface concentration for particular node index & pollutant. +// +{ + int j; + double c1, c2; + + if ( index >= 0 && index < NumIfaceNodes ) + { + // --- find index of pollut on interface file + j = IfacePolluts[pollut]; + if ( j < 0 ) return 0.0; + + // --- interpolate flow between old and new values + // (remember that 1st col. of values matrix is for flow) + c1 = OldIfaceValues[index][j+1]; + c2 = NewIfaceValues[index][j+1]; + return (1.0 - IfaceFrac)*c1 + IfaceFrac*c2; + } + else return 0.0; +} + +//============================================================================= + +void iface_saveOutletResults(DateTime reportDate, FILE* file) +// +// Input: reportDate = reporting date/time +// file = ptr. to interface file +// Output: none +// Purpose: saves system outflows to routing interface file. +// +{ + int i, p, yr, mon, day, hr, min, sec; + char theDate[26]; + datetime_decodeDate(reportDate, &yr, &mon, &day); + datetime_decodeTime(reportDate, &hr, &min, &sec); + snprintf(theDate, 26, " %04d %02d %02d %02d %02d %02d ", + yr, mon, day, hr, min, sec); + for (i=0; i 0 ) + { + report_writeErrorMsg(err, Finflows.name); + return; + } + + // --- match nodes in file with those in project + err = getIfaceFileNodes(); + if ( err > 0 ) + { + report_writeErrorMsg(err, Finflows.name); + return; + } + + // --- create matrices for old & new interface flows & WQ values + OldIfaceValues = project_createMatrix(NumIfaceNodes, + 1+NumIfacePolluts); + NewIfaceValues = project_createMatrix(NumIfaceNodes, + 1+NumIfacePolluts); + if ( OldIfaceValues == NULL || NewIfaceValues == NULL ) + { + report_writeErrorMsg(ERR_MEMORY, ""); + return; + } + + // --- read in new interface flows & WQ values + readNewIfaceValues(); + OldIfaceDate = NewIfaceDate; +} + +//============================================================================= + +int getIfaceFilePolluts() +// +// Input: none +// Output: returns an error code +// Purpose: reads names of pollutants saved on the inflows interface file. +// +{ + int i, j; + char line[MAXLINE+1]; // line from inflows interface file + char s1[MAXLINE+1]; // general string variable + char s2[MAXLINE+1]; + + // --- read number of pollutants (minus FLOW) + fgets(line, MAXLINE, Finflows.file); + NumIfacePolluts = -1; + if (sscanf(line, "%d", &NumIfacePolluts)) + NumIfacePolluts--; + if ( NumIfacePolluts < 0 ) return ERR_ROUTING_FILE_FORMAT; + + // --- read flow units + fgets(line, MAXLINE, Finflows.file); + if (sscanf(line, "%s %s", s1, s2) < 2) + return ERR_ROUTING_FILE_FORMAT; + if ( !strcomp(s1, "FLOW") ) + return ERR_ROUTING_FILE_FORMAT; + IfaceFlowUnits = findmatch(s2, FlowUnitWords); + if ( IfaceFlowUnits < 0 ) + return ERR_ROUTING_FILE_FORMAT; + + // --- allocate memory for pollutant index array + if ( Nobjects[POLLUT] > 0 ) + { + IfacePolluts = (int *) calloc(Nobjects[POLLUT], sizeof(int)); + if ( !IfacePolluts ) return ERR_MEMORY; + for (i=0; i 0 && Nobjects[POLLUT] > 0 ) + { + // --- check each pollutant name on file with project's pollutants + for (i=0; i +#include +#include "headers.h" +#include "infil.h" + +//----------------------------------------------------------------------------- +// Local Variables +//----------------------------------------------------------------------------- +typedef union TInfil { + THorton horton; + TGrnAmpt grnAmpt; + TCurveNum curveNum; +} TInfil; +TInfil *Infil; + +static double Fumax; // saturated water volume in upper soil zone (ft) +static double InfilFactor; + +//----------------------------------------------------------------------------- +// External Functions (declared in infil.h) +//----------------------------------------------------------------------------- +// infil_create (called by createObjects in project.c) +// infil_delete (called by deleteObjects in project.c) +// infil_readParams (called by input_readLine) +// infil_initState (called by subcatch_initState) +// infil_getState (called by writeRunoffFile in hotstart.c) +// infil_setState (called by readRunoffFile in hotstart.c) +// infil_getInfil (called by getSubareaRunoff in subcatch.c) + +// Called locally and by storage node methods in node.c +// grnampt_setParams +// grnampt_initState +// grnampt_getInfil + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int horton_setParams(THorton *infil, double p[]); +static void horton_initState(THorton *infil); +static void horton_getState(THorton *infil, double x[]); +static void horton_setState(THorton *infil, double x[]); +static double horton_getInfil(THorton *infil, double tstep, double irate, + double depth); +static double modHorton_getInfil(THorton *infil, double tstep, double irate, + double depth); + +static void grnampt_getState(TGrnAmpt *infil, double x[]); +static void grnampt_setState(TGrnAmpt *infil, double x[]); +static double grnampt_getUnsatInfil(TGrnAmpt *infil, double tstep, + double irate, double depth, int modelType); +static double grnampt_getSatInfil(TGrnAmpt *infil, double tstep, + double irate, double depth); +static double grnampt_getF2(double f1, double c1, double ks, double ts); + +static int curvenum_setParams(TCurveNum *infil, double p[]); +static void curvenum_initState(TCurveNum *infil); +static void curvenum_getState(TCurveNum *infil, double x[]); +static void curvenum_setState(TCurveNum *infil, double x[]); +static double curvenum_getInfil(TCurveNum *infil, double tstep, double irate, + double depth); + +//============================================================================= + +void infil_create(int n) +// +// Purpose: creates an array of infiltration objects. +// Input: n = number of subcatchments +// Output: none +// +{ + Infil = (TInfil *) calloc(n, sizeof(TInfil)); + if (Infil == NULL) ErrorCode = ERR_MEMORY; + InfilFactor = 1.0; + return; +} + +//============================================================================= + +void infil_delete() +// +// Purpose: deletes infiltration objects associated with subcatchments +// Input: none +// Output: none +// +{ + FREE(Infil); +} + +//============================================================================= + +int infil_readParams(int m, char* tok[], int ntoks) +// +// Input: m = default infiltration model +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: sets infiltration parameters from a line of input data. +// +// Format of data line is: +// subcatch p1 p2 ... (infilMethod) +{ + int i, j, n, status; + double x[5]; + + // --- check that subcatchment exists + j = project_findObject(SUBCATCH, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- check for infiltration method keyword is last token + i = findmatch(tok[ntoks-1], InfilModelWords); + if ( i >= 0 ) + { + m = i; + --ntoks; + } + + // --- number of input tokens depends on infiltration model m + if ( m == HORTON ) n = 5; + else if ( m == MOD_HORTON ) n = 5; + else if ( m == GREEN_AMPT ) n = 4; + else if ( m == MOD_GREEN_AMPT ) n = 4; + else if ( m == CURVE_NUMBER ) n = 4; + else return 0; + + if ( ntoks < n ) return error_setInpError(ERR_ITEMS, ""); + + // --- parse numerical values from tokens + for (i = 0; i < 5; i++) x[i] = 0.0; + for (i = 1; i < n; i++) + { + if (!getDouble(tok[i], &x[i - 1])) + return error_setInpError(ERR_NUMBER, tok[i]); + } + + // --- special case for Horton infil. - last parameter is optional + if ( (m == HORTON || m == MOD_HORTON) && ntoks > n ) + { + if ( ! getDouble(tok[n], &x[n-1]) ) + return error_setInpError(ERR_NUMBER, tok[n]); + } + + // --- assign parameter values to infil, infilModel object + Subcatch[j].infil = j; + Subcatch[j].infilModel = m; + switch (m) + { + case HORTON: + case MOD_HORTON: status = horton_setParams(&Infil[j].horton, x); + break; + case GREEN_AMPT: + case MOD_GREEN_AMPT: + status = grnampt_setParams(&Infil[j].grnAmpt, x); + break; + case CURVE_NUMBER: status = curvenum_setParams(&Infil[j].curveNum, x); + break; + default: status = TRUE; + } + if ( !status ) return error_setInpError(ERR_INFIL_PARAMS, ""); + return 0; +} + +//============================================================================= + +void infil_initState(int j) +// +// Input: j = subcatchment index +// Output: none +// Purpose: initializes state of infiltration for a subcatchment. +// +{ + switch (Subcatch[j].infilModel) + { + case HORTON: + case MOD_HORTON: horton_initState(&Infil[j].horton); break; + case GREEN_AMPT: + case MOD_GREEN_AMPT: + grnampt_initState(&Infil[j].grnAmpt); break; + case CURVE_NUMBER: curvenum_initState(&Infil[j].curveNum); break; + } +} + +//============================================================================= + +void infil_getState(int j, double x[]) +// +// Input: j = subcatchment index +// Output: x = subcatchment's infiltration state +// Purpose: retrieves the current infiltration state for a subcatchment. +// +{ + switch (Subcatch[j].infilModel) + { + case HORTON: + case MOD_HORTON: horton_getState(&Infil[j].horton, x); break; + case GREEN_AMPT: + case MOD_GREEN_AMPT: + grnampt_getState(&Infil[j].grnAmpt, x); break; + case CURVE_NUMBER: curvenum_getState(&Infil[j].curveNum, x); break; + } +} + +//============================================================================= + +void infil_setState(int j, double x[]) +// +// Input: j = subcatchment index +// m = infiltration method code +// Output: none +// Purpose: sets the current infiltration state for a subcatchment. +// +{ + switch (Subcatch[j].infilModel) + { + case HORTON: + case MOD_HORTON: horton_setState(&Infil[j].horton, x); break; + case GREEN_AMPT: + case MOD_GREEN_AMPT: + grnampt_setState(&Infil[j].grnAmpt, x); break; + case CURVE_NUMBER: curvenum_setState(&Infil[j].curveNum, x); break; + } +} + +//============================================================================= + +void infil_setInfilFactor(int j) +// +// Input: j = subcatchment index +// Output: none +// Purpose: assigns a value to the infiltration adjustment factor. +{ + int m; + int p; + + // ... set factor to the global conductivity adjustment factor + InfilFactor = Adjust.hydconFactor; + + // ... override global factor with subcatchment's adjustment if assigned + if (j >= 0) + { + p = Subcatch[j].infilPattern; + if (p >= 0 && Pattern[p].type == MONTHLY_PATTERN) + { + m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; + InfilFactor = Pattern[p].factor[m]; + } + } +} + +//============================================================================= + +double infil_getInfil(int j, double tstep, double rainfall, + double runon, double depth) +// +// Input: j = subcatchment index +// tstep = runoff time step (sec) +// rainfall = rainfall rate (ft/sec) +// runon = runon rate from other sub-areas or subcatchments (ft/sec) +// depth = depth of surface water on subcatchment (ft) +// Output: returns infiltration rate (ft/sec) +// Purpose: computes infiltration rate depending on infiltration method. +// +{ + switch (Subcatch[j].infilModel) + { + case HORTON: + return horton_getInfil(&Infil[j].horton, tstep, rainfall+runon, depth); + + case MOD_HORTON: + return modHorton_getInfil(&Infil[j].horton, tstep, rainfall+runon, + depth); + + case GREEN_AMPT: + case MOD_GREEN_AMPT: + return grnampt_getInfil(&Infil[j].grnAmpt, tstep, rainfall+runon, depth, + Subcatch[j].infilModel); + + case CURVE_NUMBER: + depth += runon * tstep; + return curvenum_getInfil(&Infil[j].curveNum, tstep, rainfall, depth); + + default: + return 0.0; + } +} + +//============================================================================= + +int horton_setParams(THorton *infil, double p[]) +// +// Input: infil = ptr. to Horton infiltration object +// p[] = array of parameter values +// Output: returns TRUE if parameters are valid, FALSE otherwise +// Purpose: assigns Horton infiltration parameters to a subcatchment. +// +{ + int k; + for (k = 0; k < 5; k++) if ( p[k] < 0.0 ) return FALSE; + + // --- max. & min. infil rates (ft/sec) + infil->f0 = p[0] / UCF(RAINFALL); + infil->fmin = p[1] / UCF(RAINFALL); + + // --- convert decay const. to 1/sec + infil->decay = p[2] / 3600.; + + // --- convert drying time (days) to a regeneration const. (1/sec) + // assuming that former is time to reach 98% dry along an + // exponential drying curve + if (p[3] == 0.0 ) p[3] = TINY; + infil->regen = -log(1.0-0.98) / p[3] / SECperDAY; + + // --- optional max. infil. capacity (ft) (p[4] = 0 if no value supplied) + infil->Fmax = p[4] / UCF(RAINDEPTH); + if ( infil->f0 < infil->fmin ) return FALSE; + return TRUE; +} + +//============================================================================= + +void horton_initState(THorton *infil) +// +// Input: infil = ptr. to Horton infiltration object +// Output: none +// Purpose: initializes time on Horton infiltration curve for a subcatchment. +// +{ + infil->tp = 0.0; + infil->Fe = 0.0; +} + +//============================================================================= + +void horton_getState(THorton *infil, double x[]) +{ + x[0] = infil->tp; + x[1] = infil->Fe; +} + +void horton_setState(THorton *infil, double x[]) +{ + infil->tp = x[0]; + infil->Fe = x[1]; +} + +//============================================================================= + +double horton_getInfil(THorton *infil, double tstep, double irate, double depth) +// +// Input: infil = ptr. to Horton infiltration object +// tstep = runoff time step (sec), +// irate = net "rainfall" rate (ft/sec), +// = rainfall + snowmelt + runon - evaporation +// depth = depth of ponded water (ft). +// Output: returns infiltration rate (ft/sec) +// Purpose: computes Horton infiltration for a subcatchment. +// +{ + // --- assign local variables + int iter; + double fa, fp = 0.0; + double Fp, F1, t1, tlim, ex, kt; + double FF, FF1, r; + double f0 = infil->f0 * InfilFactor; + double fmin = infil->fmin * InfilFactor; + double Fmax = infil->Fmax; + double tp = infil->tp; + double df = f0 - fmin; + double kd = infil->decay; + double kr = infil->regen * Evap.recoveryFactor; + + // --- special cases of no infil. or constant infil + if ( df < 0.0 || kd < 0.0 || kr < 0.0 ) return 0.0; + if ( df == 0.0 || kd == 0.0 ) + { + fp = f0; + fa = irate + depth / tstep; + if ( fp > fa ) fp = fa; + return MAX(0.0, fp); + } + + // --- compute water available for infiltration + fa = irate + depth / tstep; + + // --- case where there is water to infiltrate + if ( fa > ZERO ) + { + // --- compute average infil. rate over time step + t1 = tp + tstep; // future cumul. time + tlim = 16.0 / kd; // for tp >= tlim, f = fmin + if ( tp >= tlim ) + { + Fp = fmin * tp + df / kd; + F1 = Fp + fmin * tstep; + } + else + { + Fp = fmin * tp + df / kd * (1.0 - exp(-kd * tp)); + F1 = fmin * t1 + df / kd * (1.0 - exp(-kd * t1)); + } + fp = (F1 - Fp) / tstep; + fp = MAX(fp, fmin); + + // --- limit infil rate to available infil + if ( fp > fa ) fp = fa; + + // --- if fp on flat portion of curve then increase tp by tstep + if ( t1 > tlim ) tp = t1; + + // --- if infil < available capacity then increase tp by tstep + else if ( fp < fa ) tp = t1; + + // --- if infil limited by available capacity then + // solve F(tp) - F1 = 0 using Newton-Raphson method + else + { + F1 = Fp + fp * tstep; + tp = tp + tstep / 2.0; + for ( iter=1; iter<=20; iter++ ) + { + kt = MIN( 60.0, kd*tp ); + ex = exp(-kt); + FF = fmin * tp + df / kd * (1.0 - ex) - F1; + FF1 = fmin + df * ex; + r = FF / FF1; + tp = tp - r; + if ( fabs(r) <= 0.001 * tstep ) break; + } + } + + // --- limit cumulative infiltration to Fmax + if ( Fmax > 0.0 ) + { + if ( infil->Fe + fp * tstep > Fmax ) + fp = (Fmax - infil->Fe) / tstep; + fp = MAX(fp, 0.0); + infil->Fe += fp * tstep; + } + } + + // --- case where infil. capacity is regenerating; update tp. + else if (kr > 0.0) + { + r = exp(-kr * tstep); + tp = 1.0 - exp(-kd * tp); + tp = -log(1.0 - r*tp) / kd; + + // reduction in cumulative infiltration + if ( Fmax > 0.0 ) + { + infil->Fe = fmin*tp + (df/kd)*(1.0 - exp(-kd*tp)); + } + } + infil->tp = tp; + return fp; +} + +//============================================================================= + +double modHorton_getInfil(THorton *infil, double tstep, double irate, + double depth) +// +// Input: infil = ptr. to Horton infiltration object +// tstep = runoff time step (sec), +// irate = net "rainfall" rate (ft/sec), +// = rainfall + snowmelt + runon +// depth = depth of ponded water (ft). +// Output: returns infiltration rate (ft/sec) +// Purpose: computes modified Horton infiltration for a subcatchment. +// +{ + // --- assign local variables + double f = 0.0; + double fp, fa; + double f0 = infil->f0 * InfilFactor; + double fmin = infil->fmin * InfilFactor; + double df = f0 - fmin; + double kd = infil->decay; + double kr = infil->regen * Evap.recoveryFactor; + + // --- special cases of no or constant infiltration + if ( df < 0.0 || kd < 0.0 || kr < 0.0 ) return 0.0; + if ( df == 0.0 || kd == 0.0 ) + { + fp = f0; + fa = irate + depth / tstep; + if ( fp > fa ) fp = fa; + return MAX(0.0, fp); + } + + // --- compute water available for infiltration + fa = irate + depth / tstep; + + // --- case where there is water to infiltrate + if ( fa > ZERO ) + { + // --- saturated condition + if ( infil->Fmax > 0.0 && infil->Fe >= infil->Fmax ) return 0.0; + + // --- potential infiltration + fp = f0 - kd * infil->Fe; + fp = MAX(fp, fmin); + + // --- actual infiltration + f = MIN(fa, fp); + + // --- new cumulative infiltration minus seepage + infil->Fe += MAX((f - fmin), 0.0) * tstep; + if ( infil->Fmax > 0.0 ) infil->Fe = MAX(infil->Fe, infil->Fmax); + } + + // --- reduce cumulative infiltration for dry condition + else if (kr > 0.0) + { + infil->Fe *= exp(-kr * tstep); + infil->Fe = MAX(infil->Fe, 0.0); + } + return f; +} + +//============================================================================= + +void grnampt_getParams(int j, double p[]) +// +// Input: j = subcatchment index +// p[] = array of parameter values +// Output: none +// Purpose: retrieves Green-Ampt infiltration parameters for a subcatchment. +// +{ + p[0] = Infil[j].grnAmpt.S * UCF(RAINDEPTH); // Capillary suction head (ft) + p[1] = Infil[j].grnAmpt.Ks * UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) + p[2] = Infil[j].grnAmpt.IMDmax; // Max. init. moisture deficit +} + +//============================================================================= + +int grnampt_setParams(TGrnAmpt *infil, double p[]) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// p[] = array of parameter values +// Output: returns TRUE if parameters are valid, FALSE otherwise +// Purpose: assigns Green-Ampt infiltration parameters to a subcatchment. +// +{ + double ksat; // sat. hyd. conductivity in in/hr + + if ( p[0] < 0.0 || p[1] <= 0.0 || p[2] < 0.0 || p[2] > 1.0) return FALSE; + infil->S = p[0] / UCF(RAINDEPTH); // Capillary suction head (ft) + infil->Ks = p[1] / UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) + infil->IMDmax = p[2]; // Max. init. moisture deficit + + // --- find depth of upper soil zone (ft) using Mein's eqn. + ksat = infil->Ks * 12. * 3600.; + infil->Lu = 4.0 * sqrt(ksat) / 12.; + return TRUE; +} + +//============================================================================= + +void grnampt_initState(TGrnAmpt *infil) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// Output: none +// Purpose: initializes state of Green-Ampt infiltration for a subcatchment. +// +{ + if (infil == NULL) return; + infil->IMD = infil->IMDmax; + infil->Fu = 0.0; + infil->F = 0.0; + infil->Sat = FALSE; + infil->T = 0.0; +} + +void grnampt_getState(TGrnAmpt *infil, double x[]) +{ + x[0] = infil->IMD; + x[1] = infil->F; + x[2] = infil->Fu; + x[3] = infil->Sat; + x[4] = infil->T; +} + +void grnampt_setState(TGrnAmpt *infil, double x[]) +{ + infil->IMD = x[0]; + infil->F = x[1]; + infil->Fu = x[2]; + infil->Sat = (char)x[3]; + infil->T = x[4]; +} + +//============================================================================= + +double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, + double depth, int modelType) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// tstep = time step (sec), +// irate = net "rainfall" rate to upper zone (ft/sec); +// = rainfall + snowmelt + runon, +// does not include ponded water (added on below) +// depth = depth of ponded water (ft) +// modelType = either GREEN_AMPT or MOD_GREEN_AMPT +// Output: returns infiltration rate (ft/sec) +// Purpose: computes Green-Ampt infiltration for a subcatchment +// or a storage node. +// +{ + // --- find saturated upper soil zone water volume + Fumax = infil->IMDmax * infil->Lu * sqrt(InfilFactor); + + // --- reduce time until next event + infil->T -= tstep; + + // --- use different procedures depending on upper soil zone saturation + if ( infil->Sat ) return grnampt_getSatInfil(infil, tstep, irate, depth); + else return grnampt_getUnsatInfil(infil, tstep, irate, depth, modelType); +} + +//============================================================================= + +double grnampt_getUnsatInfil(TGrnAmpt *infil, double tstep, double irate, + double depth, int modelType) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// tstep = runoff time step (sec), +// irate = net "rainfall" rate to upper zone (ft/sec); +// = rainfall + snowmelt + runon, +// does not include ponded water (added on below) +// depth = depth of ponded water (ft) +// modelType = either GREEN_AMPT or MOD_GREEN_AMPT +// Output: returns infiltration rate (ft/sec) +// Purpose: computes Green-Ampt infiltration when upper soil zone is +// unsaturated. +// +{ + double ia, c1, F2, dF, Fs, kr, ts; + double ks = infil->Ks * InfilFactor; + double lu = infil->Lu * sqrt(InfilFactor); + + // --- get available infiltration rate (rainfall + ponded water) + ia = irate + depth / tstep; + if ( ia < ZERO ) ia = 0.0; + + // --- no rainfall so recover upper zone moisture + if ( ia == 0.0 ) + { + if ( infil->Fu <= 0.0 ) return 0.0; + kr = lu / 90000.0 * Evap.recoveryFactor; + dF = kr * Fumax * tstep; + infil->F -= dF; + infil->Fu -= dF; + if ( infil->Fu <= 0.0 ) + { + infil->Fu = 0.0; + infil->F = 0.0; + infil->IMD = infil->IMDmax; + return 0.0; + } + + // --- if new wet event begins then reset IMD & F + if ( infil->T <= 0.0 ) + { + infil->IMD = (Fumax - infil->Fu) / lu; + infil->F = 0.0; + } + return 0.0; + } + + // --- rainfall does not exceed Ksat + if ( ia <= ks ) + { + dF = ia * tstep; + infil->F += dF; + infil->Fu += dF; + infil->Fu = MIN(infil->Fu, Fumax); + if ( modelType == GREEN_AMPT && infil->T <= 0.0 ) + { + infil->IMD = (Fumax - infil->Fu) / lu; + infil->F = 0.0; + } + return ia; + } + + // --- rainfall exceeds Ksat; renew time to drain upper zone + infil->T = 5400.0 / lu / Evap.recoveryFactor; + + // --- find volume needed to saturate surface layer + Fs = ks * (infil->S + depth) * infil->IMD / (ia - ks); + + // --- surface layer already saturated + if ( infil->F > Fs ) + { + infil->Sat = TRUE; + return grnampt_getSatInfil(infil, tstep, irate, depth); + } + + // --- surface layer remains unsaturated + if ( infil->F + ia*tstep < Fs ) + { + dF = ia * tstep; + infil->F += dF; + infil->Fu += dF; + infil->Fu = MIN(infil->Fu, Fumax); + return ia; + } + + // --- surface layer becomes saturated during time step; + // --- compute portion of tstep when saturated + ts = tstep - (Fs - infil->F) / ia; + if ( ts <= 0.0 ) ts = 0.0; + + // --- compute new total volume infiltrated + c1 = (infil->S + depth) * infil->IMD; + F2 = grnampt_getF2(Fs, c1, ks, ts); + if ( F2 > Fs + ia*ts ) F2 = Fs + ia*ts; + + // --- compute infiltration rate + dF = F2 - infil->F; + infil->F = F2; + infil->Fu += dF; + infil->Fu = MIN(infil->Fu, Fumax); + infil->Sat = TRUE; + return dF / tstep; +} + +//============================================================================= + +double grnampt_getSatInfil(TGrnAmpt *infil, double tstep, double irate, + double depth) +// +// Input: infil = ptr. to Green-Ampt infiltration object +// tstep = runoff time step (sec), +// irate = net "rainfall" rate to upper zone (ft/sec); +// = rainfall + snowmelt + runon, +// does not include ponded water (added on below) +// depth = depth of ponded water (ft). +// Output: returns infiltration rate (ft/sec) +// Purpose: computes Green-Ampt infiltration when upper soil zone is +// saturated. +// +{ + double ia, c1, dF, F2; + double ks = infil->Ks * InfilFactor; + double lu = infil->Lu * sqrt(InfilFactor); + + // --- get available infiltration rate (rainfall + ponded water) + ia = irate + depth / tstep; + if ( ia < ZERO ) return 0.0; + + // --- re-set new event recovery time + infil->T = 5400.0 / lu / Evap.recoveryFactor; + + // --- solve G-A equation for new cumulative infiltration volume (F2) + c1 = (infil->S + depth) * infil->IMD; + F2 = grnampt_getF2(infil->F, c1, ks, tstep); + dF = F2 - infil->F; + + // --- all available water infiltrates -- set saturated state to false + if ( dF > ia * tstep ) + { + dF = ia * tstep; + infil->Sat = FALSE; + } + + // --- update total infiltration and upper zone moisture deficit + infil->F += dF; + infil->Fu += dF; + infil->Fu = MIN(infil->Fu, Fumax); + return dF / tstep; +} + +//============================================================================= + +double grnampt_getF2(double f1, double c1, double ks, double ts) +// +// Input: f1 = old infiltration volume (ft) +// c1 = head * moisture deficit (ft) +// ks = sat. hyd. conductivity (ft/sec) +// ts = time step (sec) +// Output: returns infiltration volume at end of time step (ft) +// Purpose: computes new infiltration volume over a time step +// using Green-Ampt formula for saturated upper soil zone. +// +{ + int i; + double f2 = f1; + double f2min; + double df2; + double c2; + + // --- find min. infil. volume + f2min = f1 + ks * ts; + + // --- use min. infil. volume for 0 moisture deficit + if ( c1 == 0.0 ) return f2min; + + // --- use direct form of G-A equation for small time steps + // and c1/f1 < 100 + if ( ts < 10.0 && f1 > 0.01 * c1 ) + { + f2 = f1 + ks * (1.0 + c1/f1) * ts; + return MAX(f2, f2min); + } + + // --- use Newton-Raphson method to solve integrated G-A equation + // (convergence limit reduced from that used in previous releases) + c2 = c1 * log(f1 + c1) - ks * ts; + for ( i = 1; i <= 20; i++ ) + { + df2 = (f2 - f1 - c1 * log(f2 + c1) + c2) / (1.0 - c1 / (f2 + c1) ); + if ( fabs(df2) < 0.00001 ) + { + return MAX(f2, f2min); + } + f2 -= df2; + } + return f2min; +} + +//============================================================================= + +int curvenum_setParams(TCurveNum *infil, double p[]) +// +// Input: infil = ptr. to Curve Number infiltration object +// p[] = array of parameter values +// Output: returns TRUE if parameters are valid, FALSE otherwise +// Purpose: assigns Curve Number infiltration parameters to a subcatchment. +// +{ + + // --- convert Curve Number to max. infil. capacity + if ( p[0] < 10.0 ) p[0] = 10.0; + if ( p[0] > 99.0 ) p[0] = 99.0; + infil->Smax = (1000.0 / p[0] - 10.0) / 12.0; + if ( infil->Smax < 0.0 ) return FALSE; + + // --- convert drying time (days) to a regeneration const. (1/sec) + if ( p[2] > 0.0 ) infil->regen = 1.0 / (p[2] * SECperDAY); + else return FALSE; + + // --- compute inter-event time from regeneration const. as in Green-Ampt + infil->Tmax = 0.06 / infil->regen; + + return TRUE; +} + +//============================================================================= + +void curvenum_initState(TCurveNum *infil) +// +// Input: infil = ptr. to Curve Number infiltration object +// Output: none +// Purpose: initializes state of Curve Number infiltration for a subcatchment. +// +{ + infil->S = infil->Smax; + infil->P = 0.0; + infil->F = 0.0; + infil->T = 0.0; + infil->Se = infil->Smax; + infil->f = 0.0; +} + +void curvenum_getState(TCurveNum *infil, double x[]) +{ + x[0] = infil->S; + x[1] = infil->P; + x[2] = infil->F; + x[3] = infil->T; + x[4] = infil->Se; + x[5] = infil->f; +} + +void curvenum_setState(TCurveNum *infil, double x[]) +{ + infil->S = x[0]; + infil->P = x[1]; + infil->F = x[2]; + infil->T = x[3]; + infil->Se = x[4]; + infil->f = x[5]; +} + +//============================================================================= + +double curvenum_getInfil(TCurveNum *infil, double tstep, double irate, + double depth) +// +// Input: infil = ptr. to Curve Number infiltration object +// tstep = runoff time step (sec), +// irate = rainfall rate (ft/sec); +// depth = depth of runon + ponded water (ft) +// Output: returns infiltration rate (ft/sec) +// Purpose: computes infiltration rate using the Curve Number method. +// Note: this function treats runon from other subcatchments as part +// of the ponded depth and not as an effective rainfall rate. +{ + double F1; // new cumulative infiltration (ft) + double f1 = 0.0; // new infiltration rate (ft/sec) + double fa = irate + depth/tstep; // max. available infil. rate (ft/sec) + + // --- case where there is rainfall + if ( irate > ZERO ) + { + // --- check if new rain event + if ( infil->T >= infil->Tmax ) + { + infil->P = 0.0; + infil->F = 0.0; + infil->f = 0.0; + infil->Se = infil->S; + } + infil->T = 0.0; + + // --- update cumulative precip. + infil->P += irate * tstep; + + // --- find potential new cumulative infiltration + F1 = infil->P * (1.0 - infil->P / (infil->P + infil->Se)); + + // --- compute potential infiltration rate + f1 = (F1 - infil->F) / tstep; + if ( f1 < 0.0 || infil->S <= 0.0 ) f1 = 0.0; + + } + + // --- case of no rainfall + else + { + // --- if there is ponded water then use previous infil. rate + if ( depth > MIN_TOTAL_DEPTH && infil->S > 0.0 ) + { + f1 = infil->f; + if ( f1*tstep > infil->S ) f1 = infil->S / tstep; + } + + // --- otherwise update inter-event time + else infil->T += tstep; + } + + // --- if there is some infiltration + if ( f1 > 0.0 ) + { + // --- limit infil. rate to max. available rate + f1 = MIN(f1, fa); + f1 = MAX(f1, 0.0); + + // --- update actual cumulative infiltration + infil->F += f1 * tstep; + + // --- reduce infil. capacity if a regen. constant was supplied + if ( infil->regen > 0.0 ) + { + infil->S -= f1 * tstep; + if ( infil->S < 0.0 ) infil->S = 0.0; + } + } + + // --- otherwise regenerate infil. capacity + else + { + infil->S += infil->regen * infil->Smax * tstep * Evap.recoveryFactor; + if ( infil->S > infil->Smax ) infil->S = infil->Smax; + } + infil->f = f1; + return f1; +} diff --git a/src/infil.h b/src/infil.h new file mode 100644 index 000000000..f5ddf31aa --- /dev/null +++ b/src/infil.h @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------------- +// infil.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Public interface for infiltration functions. +// +// Update History +// ============== +// Build 5.1.010: +// - New Modified Green Ampt infiltration option added. +// Build 5.1.013: +// - New function infil_setInfilFactor() added. +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. +//----------------------------------------------------------------------------- + +#ifndef INFIL_H +#define INFIL_H + +//--------------------- +// Enumerated Constants +//--------------------- +enum InfilType { + HORTON, // Horton infiltration + MOD_HORTON, // Modified Horton infiltration + GREEN_AMPT, // Green-Ampt infiltration + MOD_GREEN_AMPT, // Modified Green-Ampt infiltration + CURVE_NUMBER}; // SCS Curve Number infiltration + +//--------------------- +// Horton Infiltration +//--------------------- +typedef struct +{ + double f0; // initial infil. rate (ft/sec) + double fmin; // minimum infil. rate (ft/sec) + double Fmax; // maximum total infiltration (ft); + double decay; // decay coeff. of infil. rate (1/sec) + double regen; // regeneration coeff. of infil. rate (1/sec) + //----------------------------- + double tp; // present time on infiltration curve (sec) + double Fe; // cumulative infiltration (ft) +} THorton; + + +//------------------------- +// Green-Ampt Infiltration +//------------------------- +typedef struct +{ + double S; // avg. capillary suction (ft) + double Ks; // saturated conductivity (ft/sec) + double IMDmax; // max. soil moisture deficit (ft/ft) + //----------------------------- + double IMD; // current initial soil moisture deficit + double F; // current cumulative infiltrated volume (ft) + double Fu; // current upper zone infiltrated volume (ft) + double Lu; // depth of upper soil zone (ft) + double T; // time until start of next rain event (sec) + char Sat; // saturation flag +} TGrnAmpt; + + +//-------------------------- +// Curve Number Infiltration +//-------------------------- +typedef struct +{ + double Smax; // max. infiltration capacity (ft) + double regen; // infil. capacity regeneration constant (1/sec) + double Tmax; // maximum inter-event time (sec) + //----------------------------- + double S; // current infiltration capacity (ft) + double F; // current cumulative infiltration (ft) + double P; // current cumulative precipitation (ft) + double T; // current inter-event time (sec) + double Se; // current event infiltration capacity (ft) + double f; // previous infiltration rate (ft/sec) + +} TCurveNum; + +//----------------------------------------------------------------------------- +// Exported Variables +//----------------------------------------------------------------------------- +extern THorton* HortInfil; +extern TGrnAmpt* GAInfil; +extern TCurveNum* CNInfil; + +//----------------------------------------------------------------------------- +// Infiltration Methods +//----------------------------------------------------------------------------- +void infil_create(int n); +void infil_delete(void); +int infil_readParams(int m, char* tok[], int ntoks); +void infil_initState(int j); +void infil_getState(int j, double x[]); +void infil_setState(int j, double x[]); +void infil_setInfilFactor(int j); +double infil_getInfil(int area, double tstep, double rainfall, double runon, + double depth); + +void grnampt_getParams(int j, double p[]); +int grnampt_setParams(TGrnAmpt *infil, double p[]); +void grnampt_initState(TGrnAmpt *infil); +double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, + double depth, int modelType); + +#endif diff --git a/src/inflow.c b/src/inflow.c new file mode 100644 index 000000000..7cb5e250f --- /dev/null +++ b/src/inflow.c @@ -0,0 +1,484 @@ +//----------------------------------------------------------------------------- +// inflow.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Manages any Direct External or Dry Weather Flow inflows +// that have been assigned to nodes of the drainage system. +// +// Update History +// ============== +// Build 5.2.0: +// - Removed references to unused extIfaceInflow member of ExtInflow struct. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// External Functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// inflow_initDwfPattern (called createObjects in project.c) +// inflow_readExtInflow (called by input_readLine) +// inflow_readDwfInflow (called by input_readLine) +// inflow_deleteExtInflows (called by deleteObjects in project.c) +// inflow_deleteDwfInflows (called by deleteObjects in project.c) +// inflow_getExtInflow (called by addExternalInflows in routing.c) +// inflow_setExtInflow (called by setNodeInflow in swmm5.c) +// inflow_getDwfInflow (called by addDryWeatherInflows in routing.c) + +//----------------------------------------------------------------------------- +// Local Functions +//----------------------------------------------------------------------------- +double getPatternFactor(int p, int month, int day, int hour); + + +int inflow_readExtInflow(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error message +// Purpose: reads parameters of a direct external inflow from a line of input. +// +// Formats of data line are: +// nodeID FLOW tSeriesID (FLOW 1.0 scaleFactor baseline basePat) +// nodeID pollutID tSeriesID (CONCEN/MASS unitsFactor scaleFactor baseline basePat) +// +{ + int j; // object index + int param; // FLOW (-1) or pollutant index + int type = CONCEN_INFLOW; // FLOW, CONCEN or MASS inflow + int tseries = -1; // time series index + int basePat = -1; // baseline pattern + double cf = 1.0; // units conversion factor + double sf = 1.0; // scaling factor + double baseline = 0.0; // baseline value + + // --- find index of node receiving the inflow + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + j = project_findObject(NODE, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- find index of inflow pollutant or use -1 for FLOW + param = project_findObject(POLLUT, tok[1]); + if ( param < 0 ) + { + if ( match(tok[1], w_FLOW) ) param = -1; + else return error_setInpError(ERR_NAME, tok[1]); + } + + // --- find index of inflow time series (if supplied) in data base + if ( strlen(tok[2]) > 0 ) + { + tseries = project_findObject(TSERIES, tok[2]); + if ( tseries < 0 ) return error_setInpError(ERR_NAME, tok[2]); + Tseries[tseries].refersTo = EXTERNAL_INFLOW; + } + + // --- assign type & cf values for a FLOW inflow + if (param == -1) + { + type = FLOW_INFLOW; + cf = 1.0/UCF(FLOW); + } + + // --- do the same for a pollutant inflow + if ( ntoks >= 4 && param > -1) + { + if ( match(tok[3], w_CONCEN) ) type = CONCEN_INFLOW; + else if ( match(tok[3], w_MASS) ) type = MASS_INFLOW; + else return error_setInpError(ERR_KEYWORD, tok[3]); + if ( ntoks >= 5 && type == MASS_INFLOW ) + { + if ( ! getDouble(tok[4], &cf) ) + { + return error_setInpError(ERR_NUMBER, tok[4]); + } + if ( cf <= 0.0 ) return error_setInpError(ERR_NUMBER, tok[4]); + } + } + + // --- get sf and baseline values + if ( ntoks >= 6 ) + { + if ( ! getDouble(tok[5], &sf) ) + { + return error_setInpError(ERR_NUMBER, tok[5]); + } + } + if ( ntoks >= 7 ) + { + if ( ! getDouble(tok[6], &baseline) ) + { + return error_setInpError(ERR_NUMBER, tok[6]); + } + } + + // --- get baseline time pattern + if ( ntoks >= 8 ) + { + basePat = project_findObject(TIMEPATTERN, tok[7]); + if ( basePat < 0 ) return error_setInpError(ERR_NAME, tok[7]); + } + + // --- include LperFT3 term in conversion factor for MASS_INFLOW + if ( type == MASS_INFLOW ) cf /= LperFT3; + + return(inflow_setExtInflow(j, param, type, tseries, basePat, + cf, baseline, sf)); +} + +//============================================================================= + +int inflow_setExtInflow(int j, int param, int type, int tseries, int basePat, + double cf, double baseline, double sf) +// Purpose: This function assigns property values to the inflow object +// Inputs: j = Node index +// param = FLOW (-1) or pollutant index +// type = FLOW, CONCEN or MASS inflow +// tSeries = time series index +// basePat = baseline pattern +// cf = units conversion factor +// baseline = baseline inflow value +// sf = scaling factor +// Return: returns Error Code + +{ + TExtInflow* inflow; // external inflow object + + // --- check if an external inflow object for this constituent already exists + inflow = Node[j].extInflow; + while ( inflow ) + { + if ( inflow->param == param ) break; + inflow = inflow->next; + } + + // --- if it doesn't exist, then create it + if ( inflow == NULL ) + { + inflow = (TExtInflow *) malloc(sizeof(TExtInflow)); + if ( inflow == NULL ) + { + return error_setInpError(ERR_MEMORY, ""); + } + inflow->next = Node[j].extInflow; + Node[j].extInflow = inflow; + } + + // --- assign property values to the inflow object + inflow->param = param; + inflow->type = type; + inflow->tSeries = tseries; + inflow->cFactor = cf; + inflow->sFactor = sf; + inflow->baseline = baseline; + inflow->basePat = basePat; + return 0; +} + +//============================================================================= + +void inflow_deleteExtInflows(int j) +// +// Input: j = node index +// Output: none +// Purpose: deletes all time series inflow data for a node. +// +{ + TExtInflow* inflow1; + TExtInflow* inflow2; + inflow1 = Node[j].extInflow; + while ( inflow1 ) + { + inflow2 = inflow1->next; + free(inflow1); + inflow1 = inflow2; + } +} + +//============================================================================= + +double inflow_getExtInflow(TExtInflow* inflow, DateTime aDate) +// +// Input: inflow = external inflow data structure +// aDate = current simulation date/time +// Output: returns current value of external inflow parameter +// Purpose: retrieves the value of an external inflow at a specific +// date and time. +// +{ + int month, day, hour; + int p = inflow->basePat; // baseline pattern + int k = inflow->tSeries; // time series index + double cf = inflow->cFactor; // units conversion factor + double sf = inflow->sFactor; // scaling factor + double blv = inflow->baseline; // baseline value + double tsv = 0.0; // time series value + + if ( p >= 0 ) + { + month = datetime_monthOfYear(aDate) - 1; + day = datetime_dayOfWeek(aDate) - 1; + hour = datetime_hourOfDay(aDate); + blv *= getPatternFactor(p, month, day, hour); + } + if ( k >= 0 ) tsv = table_tseriesLookup(&Tseries[k], aDate, FALSE) * sf; + return cf * (tsv + blv); +} + +//============================================================================= + +int inflow_readDwfInflow(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error message +// Purpose: reads dry weather inflow parameters from line of input data. +// +// Format of data line is: +// nodeID FLOW/pollutID avgValue (pattern1 pattern2 ... pattern4) +// +{ + int i; + int j; // node index + int k; // pollutant index (-1 for flow) + int m; // time pattern index + int pats[4]; // time pattern index array + double x; // avg. DWF value + TDwfInflow* inflow; // dry weather flow inflow object + + // --- find index of node receiving the inflow + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + j = project_findObject(NODE, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- find index of inflow pollutant (-1 for FLOW) + k = project_findObject(POLLUT, tok[1]); + if ( k < 0 ) + { + if ( match(tok[1], w_FLOW) ) k = -1; + else return error_setInpError(ERR_NAME, tok[1]); + } + + // --- get avg. value of DWF inflow + if ( !getDouble(tok[2], &x) ) + return error_setInpError(ERR_NUMBER, tok[2]); + if ( k == -1 ) x /= UCF(FLOW); + + // --- get time patterns assigned to the inflow + for (i=0; i<4; i++) pats[i] = -1; + for (i=3; i<7; i++) + { + if ( i >= ntoks ) break; + if ( strlen(tok[i]) == 0 ) continue; + m = project_findObject(TIMEPATTERN, tok[i]); + if ( m < 0 ) return error_setInpError(ERR_NAME, tok[i]); + pats[i-3] = m; + } + + // --- check if inflow for this constituent already exists + inflow = Node[j].dwfInflow; + while ( inflow ) + { + if ( inflow->param == k ) break; + inflow = inflow->next; + } + + // --- if it doesn't exist, then create it + if ( inflow == NULL ) + { + inflow = (TDwfInflow *) malloc(sizeof(TDwfInflow)); + if ( inflow == NULL ) return error_setInpError(ERR_MEMORY, ""); + inflow->next = Node[j].dwfInflow; + Node[j].dwfInflow = inflow; + } + + // --- assign property values to the inflow object + inflow->param = k; + inflow->avgValue = x; + for (i=0; i<4; i++) inflow->patterns[i] = pats[i]; + return 0; +} + +//============================================================================= + +void inflow_deleteDwfInflows(int j) +// +// Input: j = node index +// Output: none +// Purpose: deletes all dry weather inflow data for a node. +// +{ + TDwfInflow* inflow1; + TDwfInflow* inflow2; + inflow1 = Node[j].dwfInflow; + while ( inflow1 ) + { + inflow2 = inflow1->next; + free(inflow1); + inflow1 = inflow2; + } +} + +//============================================================================= + +void inflow_initDwfInflow(TDwfInflow* inflow) +// +// Input: inflow = dry weather inflow data structure +// Output: none +// Purpose: initialzes a dry weather inflow by ordering its time patterns. +// +// This function sorts the user-supplied time patterns for a dry weather +// inflow in the order of the PatternType enumeration (monthly, daily, +// weekday hourly, weekend hourly) to help speed up pattern processing. +// +{ + int i, p; + int tmpPattern[4]; // index of each type of DWF pattern + + // --- assume no patterns were supplied + for (i=0; i<4; i++) tmpPattern[i] = -1; + + // --- assign supplied patterns to proper position (by type) in tmpPattern + for (i=0; i<4; i++) + { + p = inflow->patterns[i]; + if ( p >= 0 ) tmpPattern[Pattern[p].type] = p; + } + + // --- re-fill inflow pattern array by pattern type + for (i=0; i<4; i++) inflow->patterns[i] = tmpPattern[i]; +} + +//============================================================================= + +double inflow_getDwfInflow(TDwfInflow* inflow, int month, int day, int hour) +// +// Input: inflow = dry weather inflow data structure +// month = current month of year of simulation +// day = current day of week of simulation +// hour = current hour of day of simulation +// Output: returns value of dry weather inflow parameter +// Purpose: computes dry weather inflow value at a specific point in time. +// +{ + int p1, p2; // pattern index + double f = 1.0; // pattern factor + + p1 = inflow->patterns[MONTHLY_PATTERN]; + if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); + p1 = inflow->patterns[DAILY_PATTERN]; + if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); + p1 = inflow->patterns[HOURLY_PATTERN]; + p2 = inflow->patterns[WEEKEND_PATTERN]; + if ( p2 >= 0 ) + { + if ( day == 0 || day == 6 ) + f *= getPatternFactor(p2, month, day, hour); + else if ( p1 >= 0 ) + f *= getPatternFactor(p1, month, day, hour); + } + else if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); + return f * inflow->avgValue; + +} + +//============================================================================= + +void inflow_initDwfPattern(int j) +// +// Input: j = time pattern index +// Output: none +// Purpose: initialzes a dry weather inflow time pattern. +// +{ + int i; + for (i=0; i<24; i++) Pattern[j].factor[i] = 1.0; + Pattern[j].count = 0; + Pattern[j].type = -1; + Pattern[j].ID = NULL; +} + +//============================================================================= + +int inflow_readDwfPattern(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error message +// Purpose: reads values of a time pattern from a line of input data. +// +// Format of data line is: +// patternID patternType value(1) value(2) ... +// patternID value(n) value(n+1) .... (for continuation lines) +{ + int i, j, k, n = 1; + + // --- check for minimum number of tokens + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that pattern exists in database + j = project_findObject(TIMEPATTERN, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- check if this is first line of pattern + // (ID pointer will not have been assigned yet) + if ( Pattern[j].ID == NULL ) + { + // --- assign ID pointer & pattern type + Pattern[j].ID = project_findID(TIMEPATTERN, tok[0]); + k = findmatch(tok[1], PatternTypeWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + Pattern[j].type = k; + n = 2; + } + + // --- start reading pattern factors from rest of line + while ( ntoks > n && Pattern[j].count < 24 ) + { + i = Pattern[j].count; + if ( !getDouble(tok[n], &Pattern[j].factor[i]) ) + return error_setInpError(ERR_NUMBER, tok[n]); + Pattern[j].count++; + n++; + } + return 0; +} + +//============================================================================= + +double getPatternFactor(int p, int month, int day, int hour) +// +// Input: p = time pattern index +// month = current month of year of simulation +// day = current day of week of simulation +// hour = current hour of day of simulation +// Output: returns value of a time pattern multiplier +// Purpose: computes time pattern multiplier for a specific point in time. +{ + switch ( Pattern[p].type ) + { + case MONTHLY_PATTERN: + if ( month >= 0 && month < 12 ) return Pattern[p].factor[month]; + break; + case DAILY_PATTERN: + if ( day >= 0 && day < 7 ) return Pattern[p].factor[day]; + break; + case HOURLY_PATTERN: + if ( hour >= 0 && hour < 24 ) return Pattern[p].factor[hour]; + break; + case WEEKEND_PATTERN: + if ( day == 0 || day == 6 ) + { + if ( hour >= 0 && hour < 24 ) return Pattern[p].factor[hour]; + } + break; + } + return 1.0; +} diff --git a/src/inlet.c b/src/inlet.c new file mode 100644 index 000000000..4defe127e --- /dev/null +++ b/src/inlet.c @@ -0,0 +1,1955 @@ +//----------------------------------------------------------------------------- +// inlet.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 10/08/22 (Build 5.2.2) +// Author: L. Rossman +// +// Street/Channel Inlet Functions +// +// Computes capture efficiency of inlets placed in Street conduits +// or Rectangular/Trapezoidal channels using FHWA HEC-22 methods (see +// Brown, S.A. et al., Urban Drainage Design Manual, Federal Highway +// Administration Hydraulic Engineering Circular No. 22, 3rd Edition, +// FHWA-NHI-10-009, August 2013). +// +// Build 5.2.1: +// - Substitutes the constant BIG for HUGE. +// Build 5.2.2: +// - Additional statistics added to Street Flow Summary table. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" + +// Grate inlet +typedef struct +{ + int type; // type of grate used + double length; // length (parallel to flow) (ft) + double width; // width (perpendicular to flow) (ft) + double fracOpenArea; // fraction of grate area that is open + double splashVeloc; // splash-over velocity (ft/s) +} TGrateInlet; + +// Slotted drain inlet +typedef struct +{ + double length; // length (parallel to flow) (ft) + double width; // width (perpendicular to flow) (ft) +} TSlottedInlet; + +// Curb opening inlet +typedef struct +{ + double length; // length of curb opening (ft) + double height; // height of curb opening (ft) + int throatAngle; // type of throat angle +} TCurbInlet; + +// Custom inlet +typedef struct +{ + int onGradeCurve; // flow diversion curve index + int onSagCurve; // flow rating curve index +} TCustomInlet; + +// Inlet design object +typedef struct +{ + char * ID; // name assigned to inlet design + int type; // type of inlet used (grate, curb, etc) + TGrateInlet grateInlet; // length = 0 if not used + TSlottedInlet slottedInlet; // length = 0 if not used + TCurbInlet curbInlet; // length = 0 if not used + int customCurve; // curve index = -1 if not used +} TInletDesign; + + +// Inlet performance statistics +typedef struct +{ + int flowPeriods; // # periods with approach flow + int capturePeriods; // # periods with captured flow + int backflowPeriods; // # periods with backflow + double peakFlow; // peak flow seen by inlet (cfs) + double peakFlowCapture; // capture efficiency at peak flow + double avgFlowCapture; // average capture efficiency + double bypassFreq; // frequency of bypass flow +} TInletStats; + +// Inlet list object +struct TInlet +{ + int linkIndex; // index of conduit link with the inlet + int designIndex; // index of inlet's design + int nodeIndex; // index of node receiving captured flow + int numInlets; // # inlets on each side of street or in channel + int placement; // whether inlet is on-grade or on-sag + double clogFactor; // fractional degree of inlet clogging + double flowLimit; // inlet flow restriction (cfs) + double localDepress; // local gutter depression (ft) + double localWidth; // local depression width (ft) + + double flowFactor; // flow = flowFactor * (flow spread)^2.67 + double flowCapture; // captured flow rate (cfs) + double backflow; // backflow from capture node (cfs) + double backflowRatio; // inlet backflow / capture node overflow + TInletStats stats; // inlet performance statistics + TInlet * nextInlet; // next inlet in list +}; + +// Shared inlet variables +TInletDesign * InletDesigns; // array of available inlet designs +int InletDesignCount; // number of inlet designs +int UsesInlets; // TRUE if project uses inlets + +//----------------------------------------------------------------------------- +// Enumerations +//----------------------------------------------------------------------------- + +enum InletType { + GRATE_INLET, CURB_INLET, COMBO_INLET, SLOTTED_INLET, + DROP_GRATE_INLET, DROP_CURB_INLET, CUSTOM_INLET +}; + +enum GrateType { + P50, P50x100, P30, CURVED_VANE, TILT_BAR_45, + TILT_BAR_30, RETICULINE, GENERIC +}; + +enum InletPlacementType { AUTOMATIC, ON_GRADE, ON_SAG }; + +enum ThroatAngleType { HORIZONTAL_THROAT, INCLINED_THROAT, VERTICAL_THROAT }; + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static char* InletTypeWords[] = + {"GRATE", "CURB", "", "SLOTTED", "DROP_GRATE", "DROP_CURB", "CUSTOM", NULL}; + +static char* GrateTypeWords[] = + {"P_BAR-50", "P_BAR-50x100", "P_BAR-30", "CURVED_VANE", "TILT_BAR-45", "TILT_BAR-30", + "RETICULINE", "GENERIC", NULL}; + +static char* ThroatAngleWords[] = + {"HORIZONTAL", "INCLINED", "VERTICAL", NULL}; + +static char *PlacementTypeWords[] = + {"AUTOMATIC", "ON_GRADE", "ON_SAG"}; + +// Coefficients for cubic polynomials fitted to Splash Over Velocity v. +// Grate Length curves in Chart 5B of HEC-22 manual taken from Denver +// UDFCD manual. +static const double SplashCoeffs[][4] = { + {2.22, 4.03, 0.65, 0.06}, //P_BAR-50 + {0.74, 2.44, 0.27, 0.02}, //P_BAR-50x100 + {1.76, 3.12, 0.45, 0.03}, //P_BAR-30 + {0.30, 4.85, 1.31, 0.15}, //Curved_Vane + {0.99, 2.64, 0.36, 0.03}, //Tilt_Bar-45 + {0.51, 2.34, 0.2, 0.01}, //Tilt_Bar-30 + {0.28, 2.28, 0.18, 0.01}}; //Reticuline + +// Grate opening ratios (Chart 9B of HEC-22 manual) +static const double GrateOpeningRatios[] = { + 0.90, //P_BAR-50 + 0.80, //P_BAR-50x100 + 0.60, //P_BAR-30 + 0.35, //Curved_Vane + 0.17, //Tilt_Bar-45 (assumed) + 0.34, //Tilt_Bar-30 + 0.80, //Reticuline + 1.00}; //Generic + +//----------------------------------------------------------------------------- +// Imported Variables +//----------------------------------------------------------------------------- +extern TLinkStats* LinkStats; // defined in STATS.C +extern TNodeStats* NodeStats; // defined in STATS.C + +//----------------------------------------------------------------------------- +// Local Shared Variables +//----------------------------------------------------------------------------- +// Variables as named in the HEC-22 manual. +static double Sx; // street cross slope +static double SL; // conduit longitudinal slope +static double Sw; // gutter + cross slope +static double a; // street gutter depression (ft) +static double W; // street gutter width (ft) +static double T; // top width of flow spread (ft) +static double n; // Manning's roughness coeff. + +// Additional variables +static int Nsides; // 1- or 2-sided street +static double Tcrown; // distance from street curb to crown (ft) +static double Beta; // = 1.486 * sqrt(SL) / n +static double Qfactor; // factor f in Izzard's eqn. Q = f*T^2.67 +static TXsect* xsect; // cross-section data of inlet's conduit +static double* InletFlow; // captured inlet flow received by each node +static TInlet* FirstInlet; // head of list of deployed inlets + +//----------------------------------------------------------------------------- +// External functions (declared in inlet.h) +//----------------------------------------------------------------------------- +// inlet_create called by createObjects in project.c +// inlet_delete called by deleteObjects in project.c +// inlet_readDesignParams called by parseLine in input.c +// inlet_readUsageParams called by parseLine in input.c +// inlet_validate called by project_validate +// inlet_findCapturedFlows called by routing_execute +// inlet_adjustQualInflows called by routing_execute +// inlet_adjustQualOutflows called by routing execute +// inlet_writeStatsReport called by statsrpt_writeReport +// inlet_capturedFlow called by findLinkMassFlow in qualrout.c + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int readGrateInletParams(int inletIndex, char* tok[], int ntoks); +static int readCurbInletParams(int inletIndex, char* tok[], int ntoks); +static int readSlottedInletParams(int inletIndex, char* tok[], int ntoks); +static int readCustomInletParams(int inletIndex, char* tok[], int ntoks); + +static void initInletStats(TInlet* inlet); +static void updateInletStats(TInlet* inlet, double q); +static void writeStreetStatsHeader(); +static void writeStreetStats(int link); + +static void getBackflowRatios(); +static double getInletArea(TInlet* inlet); + +static int getInletPlacement(TInlet* inlet, int node); +static void getConduitGeometry(TInlet* inlet); +static double getFlowSpread(double flow); +static double getEo(double slopeRatio, double spread, double gutterWidth); + +static double getCustomCapturedFlow(TInlet* inlet, double flow, double depth); +static double getOnGradeCapturedFlow(TInlet* inlet, double flow, double depth); +static double getOnGradeInletCapture(int inletIndex, double flow, double depth); +static double getGrateInletCapture(int inletIndex, double flow); +static double getCurbInletCapture(double flow, double length); + +static double getGutterFlowRatio(double gutterWidth); +static double getGutterAreaRatio(double grateWidth, double area); +static double getSplashOverVelocity(int grateType, double grateLength); + +static double getOnSagCapturedFlow(TInlet* inlet, double flow, double depth); +static double getOnSagInletCapture(int inletIndex, double depth); +static void findOnSagGrateFlows(int inletIndex, double depth, + double *weirFlow, double *orificeFlow); +static void findOnSagCurbFlows(int inletIndex, double depth, + double openingLength, double *weirFlow, + double *orificeFlow); +static double getCurbOrificeFlow(double flowDepth, double openingHeight, + double openingLength, int throatAngle); +static double getOnSagSlottedFlow(int inletIndex, double depth); + +//============================================================================= + +int inlet_create(int numInlets) +// +// Input: numInlets = number of inlet designs to create +// Output: none +// Purpose: creats a collection of inlet designs. +// +{ + int i; + + InletDesigns = NULL; + InletFlow = NULL; + InletDesignCount = 0; + UsesInlets = FALSE; + FirstInlet = NULL; + InletDesigns = (TInletDesign *)calloc(numInlets, sizeof(TInletDesign)); + if (InletDesigns == NULL) return ERR_MEMORY; + InletDesignCount = numInlets; + + InletFlow = (double *)calloc(Nobjects[NODE], sizeof(double)); + if (InletFlow == NULL) return ERR_MEMORY; + + for (i = 0; i < InletDesignCount; i++) + { + InletDesigns[i].customCurve = -1; + InletDesigns[i].curbInlet.length = 0.0; + InletDesigns[i].grateInlet.length = 0.0; + InletDesigns[i].slottedInlet.length = 0.0; + InletDesigns[i].type = CUSTOM_INLET; + } + return 0; +} + +//============================================================================= + +void inlet_delete() +// +// Input: none +// Output: none +// Purpose: frees all memory allocated for inlet analysis. +// +{ + TInlet* inlet = FirstInlet; + TInlet* nextInlet; + while (inlet) + { + nextInlet = inlet->nextInlet; + free(inlet); + inlet = nextInlet; + } + FirstInlet = NULL; + FREE(InletFlow); + FREE(InletDesigns); +} + +//============================================================================= + +int inlet_readDesignParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts a set of inlet design parameters from a tokenized line +// of the [INLETS] section of a SWMM input file. +// +// Format of input line is: +// ID GRATE Length Width GrateType (OpenArea) (SplashVeloc) +// ID CURB Length Height (ThroatType) +// ID SLOTTED Length Width +// ID DROP_GRATE Length Width GrateType (OpenArea) (SplashVeloc) +// ID DROP_CURB Length Height +// ID CUSTOM CurveID +// +{ + int i; + + // --- check for minimum number of tokens + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that design ID already registered in project + i = project_findObject(INLET, tok[0]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[0]); + InletDesigns[i].ID = project_findID(INLET, tok[0]); + + // --- retrieve type of inlet design + InletDesigns[i].type = findmatch(tok[1], InletTypeWords); + + // --- read inlet's design parameters + switch (InletDesigns[i].type) + { + case GRATE_INLET: + case DROP_GRATE_INLET: + return readGrateInletParams(i, tok, ntoks); + case CURB_INLET: + case DROP_CURB_INLET: + return readCurbInletParams(i, tok, ntoks); + case SLOTTED_INLET: + return readSlottedInletParams(i, tok, ntoks); + case CUSTOM_INLET: + return readCustomInletParams(i, tok, ntoks); + default: return error_setInpError(ERR_KEYWORD, tok[1]); + } + return 0; +} +//============================================================================= + +int inlet_readUsageParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts inlet usage parameters from a tokenized line +// of the [INLET_USAGE] section of a SWMM input file. +// +// Format of input line is: +// linkID inletID nodeID (#Inlets %Clog Qmax aLocal wLocal placement) +// where +// linkID = ID name of link containing the inlet +// inletID = ID name of inlet design being used +// nodeID = ID name of node receiving captured flow +// #Inlets = number of identical inlets used (default = 1) +// %Clog = percent that inlet is clogged +// Qmax = maximum flow that inlet can capture (default = 0 (no limit)) +// aLocal = local gutter depression (ft or m) (default = 0) +// wLocal = width of local gutter depression (ft or m) (default = 0) +// placement = ON_GRADE, ON_SAG, or AUTO (the default) +// +{ + int linkIndex, designIndex, nodeIndex, numInlets = 1; + int placement = AUTOMATIC; + double flowLimit = 0.0, pctClogged = 0.0; + double aLocal = 0.0, wLocal = 0.0; + TInlet* inlet; + + // --- check that inlet's link exists + if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); + linkIndex = project_findObject(LINK, tok[0]); + if (linkIndex < 0) return error_setInpError(ERR_NAME, tok[0]); + + // --- check that inlet design type exists + designIndex = project_findObject(INLET, tok[1]); + if (designIndex < 0) return error_setInpError(ERR_NAME, tok[1]); + + // --- check that receiving node exists + nodeIndex = project_findObject(NODE, tok[2]); + if (nodeIndex < 0) return error_setInpError(ERR_NAME, tok[2]); + + // --- get number of inlets + if (ntoks > 3) + if (!getInt(tok[3], &numInlets) || numInlets < 1) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- get flow limit & percent clogged + if (ntoks > 4) + { + if (!getDouble(tok[4], &pctClogged) || pctClogged < 0.0 + || pctClogged > 99.) + return error_setInpError(ERR_NUMBER, tok[4]); + } + if (ntoks > 5) + if (!getDouble(tok[5], &flowLimit) || flowLimit < 0.0) + return error_setInpError(ERR_NUMBER, tok[5]); + + // --- get local depression parameters + if (ntoks > 6) + if (!getDouble(tok[6], &aLocal) || aLocal < 0.0) + return error_setInpError(ERR_NUMBER, tok[6]); + if (ntoks > 7) + if (!getDouble(tok[7], &wLocal) || wLocal < 0.0) + return error_setInpError(ERR_NUMBER, tok[7]); + + // --- get inlet placement + if (ntoks > 8) + { + placement = findmatch(tok[8], PlacementTypeWords); + if (placement < 0) return error_setInpError(ERR_KEYWORD, tok[8]); + } + + // --- create an inlet usage object for the link + inlet = Link[linkIndex].inlet; + if (inlet == NULL) + { + inlet = (TInlet *)malloc(sizeof(TInlet)); + if (!inlet) return error_setInpError(ERR_MEMORY, ""); + Link[linkIndex].inlet = inlet; + inlet->nextInlet = FirstInlet; + FirstInlet = inlet; + } + + // --- save inlet usage parameters + inlet->linkIndex = linkIndex; + inlet->designIndex = designIndex; + inlet->nodeIndex = nodeIndex; + inlet->numInlets = numInlets; + inlet->placement = placement; + inlet->clogFactor = 1.0 - (pctClogged / 100.); + inlet->flowLimit = flowLimit / UCF(FLOW); + inlet->localDepress = aLocal / UCF(LENGTH); + inlet->localWidth = wLocal / UCF(LENGTH); + inlet->flowFactor = 0.0; + inlet->backflowRatio = 0.0; + initInletStats(inlet); + UsesInlets = TRUE; + return 0; +} + +//============================================================================= + +void inlet_validate() +// +// Input: none +// Output: none +// Purpose: checks that inlets have been assigned to conduits with proper +// cross section shapes and counts the number of inlets that each +// node receives either bypased or captured flow from. +// +{ + int i, j, inletType, inletValid; + TInlet* inlet; + TInlet* prevInlet; + + // --- traverse the list of inlets placed in conduits + if (!UsesInlets) return; + prevInlet = FirstInlet; + inlet = FirstInlet; + while (inlet) + { + // --- check that inlet's conduit can accept the inlet's type + inletValid = FALSE; + i = inlet->linkIndex; + xsect = &Link[i].xsect; + inletType = InletDesigns[inlet->designIndex].type; + if (inletType == CUSTOM_INLET) + { + j = InletDesigns[inlet->designIndex].customCurve; + if (j >= 0) + { + if (Curve[j].curveType == DIVERSION_CURVE || + Curve[j].curveType == RATING_CURVE) + inletValid = TRUE; + } + } + else if ((xsect->type == TRAPEZOIDAL || xsect->type == RECT_OPEN) && + (inletType == DROP_GRATE_INLET || + inletType == DROP_CURB_INLET)) + inletValid = TRUE; + else if (xsect->type == STREET_XSECT && + inletType != DROP_GRATE_INLET && + inletType != DROP_CURB_INLET) + inletValid = TRUE; + + // --- if inlet placement is valid then + if (inletValid) + { + // --- record that receptor node has inlets + Node[Link[i].node2].inlet = BYPASS; + Node[inlet->nodeIndex].inlet = CAPTURE; + + // --- initialize inlet's backflow + inlet->backflow = 0.0; + + // --- compute street inlet's flow factor for Izzard's eqn. + // (used in Q = flowFactor * Spread^2.67 equation) + getConduitGeometry(inlet); + inlet->flowFactor = (0.56/n) * pow(SL,0.5) * pow(Sx,1.67); + + // --- save reference to current inlet & continue to next inlet + prevInlet = inlet; + inlet = inlet->nextInlet; + } + + // --- if inlet placement is not valid then issue a warning message + // and remove the inlet from the conduit + else + { + report_writeWarningMsg(WARN12, Link[i].ID); + if (inlet == FirstInlet) + { + FirstInlet = inlet->nextInlet; + prevInlet = FirstInlet; + free(inlet); + inlet = FirstInlet; + } + else + { + prevInlet->nextInlet = inlet->nextInlet; + free(inlet); + inlet = prevInlet->nextInlet; + } + Link[i].inlet = NULL; + } + } + + // --- determine how capture node's overflow is split between its inlets + getBackflowRatios(); +} + +//============================================================================= + +void inlet_findCapturedFlows(double tStep) +// +// Input: tStep = current flow routing time step (sec) +// Output: none +// Purpose: computes flow captured by each inlet and adjusts the +// lateral flows of the inlet's bypass and capture nodes accordingly. +// +// This function is called after regular lateral flows to all nodes have been +// set but before a flow routing step has been taken. +{ + int i, j, m, placement; + double q; + TInlet *inlet; + + // --- For non-DW routing find conduit flow into each node + // (used to limit max. amount of on-sag capture) + if (!UsesInlets) return; + memset(InletFlow, 0, Nobjects[NODE]*sizeof(double)); + if (RouteModel != DW) + { + for (j = 0; j < Nobjects[NODE]; j++) + Node[j].inflow = MAX(0., Node[j].newLatFlow); + for (i = 0; i < Nobjects[LINK]; i++) + Node[Link[i].node2].inflow += MAX(0.0, Link[i].newFlow); + } + + // --- loop through each inlet + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- identify indexes of inlet's bypass (j) and capture (m) nodes + i = inlet->linkIndex; + j = Link[i].node2; + m = inlet->nodeIndex; + + // --- get inlet's placement (ON_GRADE or ON_SAG) + placement = getInletPlacement(inlet, j); + + // --- find flow captured by a Custom inlet + if (InletDesigns[inlet->designIndex].type == CUSTOM_INLET) + { + q = fabs(Link[i].newFlow); + inlet->flowCapture = getCustomCapturedFlow(inlet, q, Node[j].newDepth); + } + + // --- find flow captured by on-grade inlet + else if (placement == ON_GRADE) + { + q = fabs(Link[i].newFlow); + inlet->flowCapture = getOnGradeCapturedFlow(inlet, q, Node[j].newDepth); + } + + // --- find flow captured by on-sag inlet + else + { + q = Node[j].inflow; + inlet->flowCapture = getOnSagCapturedFlow(inlet, q, Node[j].newDepth); + } + if (fabs(inlet->flowCapture) < FUDGE) inlet->flowCapture = 0.0; + + // --- add to total flow captured by inlet's node + InletFlow[j] += inlet->flowCapture; + + // --- capture node's overflow becomes inlet's backflow + inlet->backflow = Node[m].overflow * inlet->backflowRatio; + if (fabs(inlet->backflow) < FUDGE) inlet->backflow = 0.0; + } + + // --- make second pass through each inlet + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- identify indexes of inlet's bypass (j) and capture (m) nodes + i = inlet->linkIndex; + j = Link[i].node2; + m = inlet->nodeIndex; + + // --- for on-sag placement under non-DW routing, captured flow + // is limited to inlet's share of bypass node's inflow plus + // any stored volume + if (RouteModel != DW && getInletPlacement(inlet, j) == ON_SAG) + { + q = Node[j].newVolume / tStep; + q += MAX(Node[j].inflow, 0.0); + if (InletFlow[j] > q) + inlet->flowCapture *= q / InletFlow[j]; + } + + // --- adjust lateral flows at bypass and capture nodes + // (subtract captured flow from bypass node, add it to capture + // node, and add any backflow to bypass node) + Node[j].newLatFlow -= (inlet->flowCapture - inlet->backflow); + Node[m].newLatFlow += inlet->flowCapture; + + // --- update inlet's performance if reporting has begun + if (getDateTime(NewRoutingTime) > ReportStart) + updateInletStats(inlet, fabs(Link[i].newFlow)); + } +} + +//============================================================================= + +void inlet_adjustQualInflows() +// +// Input: none +// Output: none +// Purpose: adjusts accumulated flow rates and pollutant mass inflows at each +// inlet's bypass and capture nodes after a flow routing step has +// been taken prior to a quality routing step. +// +{ + int i, j, m, p; + double qNet; + TInlet* inlet; + + if (!UsesInlets) return; + if (IgnoreQuality || Nobjects[POLLUT] == 0) return; + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- identify indexes of inlet's bypass (j) and capture (m) nodes + i = inlet->linkIndex; + j = Link[i].node2; + m = inlet->nodeIndex; + + // --- there's a net flow from the bypass to the capture node + qNet = inlet->flowCapture - inlet->backflow; + if (qNet > 0.0) + { + // --- add net capture flow to capture node's accumulated flow + // inflow for quality routing + Node[m].qualInflow += qNet; + + // --- and do the same for pollutant mass flows + // (Node[m].newQual is the mass inflow accumulator for node m) + for (p = 0; p < Nobjects[POLLUT]; p++) + Node[m].newQual[p] += qNet * Node[j].oldQual[p]; + } + + // --- there's a net backflow from the capture to the bypass node + else + { + // --- add the backflow flow rate and pollutant mass flow to the + // bypass node's accumulated flow and pollutant mass inflow + qNet = -qNet; + Node[j].qualInflow += qNet; + for (p = 0; p < Nobjects[POLLUT]; p++) + Node[j].newQual[p] += qNet * Node[m].oldQual[p]; + } + } +} + +//============================================================================= + +void inlet_adjustQualOutflows() +// +// Input: none +// Output: none +// Purpose: adjusts mass balance totals after a complete routing step has been +// taken so as not to treat inlet transfer flows as system outflows. +// +{ + int j, p; + double q, w; + TInlet* inlet; + + // --- these variables, declared in massbal.c, accumulate system-wide flow and + // pollutant mass fluxes over a time step to use in mass balances + extern TRoutingTotals StepFlowTotals; + extern TRoutingTotals* StepQualTotals; + + // --- examine each node + for (j = 0; j < Nobjects[NODE]; j++) + { + // --- node receives captured flow from an inlet + if (Node[j].inlet == CAPTURE) + { + // --- node also has an overflow (e.g., it's a surcharged sewer node) + q = Node[j].overflow; + if (q > 0.0) + { + // --- remove overflow from system flooding total since it does + // not leave the system (it is sent to inlet's bypass node) + StepFlowTotals.flooding -= q; + + // --- also remove pollutant overflow mass from system totals + if (!IgnoreQuality) + for (p = 0; p < Nobjects[POLLUT]; p++) + { + w = q * Node[j].newQual[p]; + StepQualTotals[p].flooding -= w; + } + } + } + } + + // --- for WQ analysis, examine each inlet's bypass node + if (!IgnoreQuality && Nobjects[POLLUT] > 0) + { + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + j = Link[inlet->linkIndex].node2; + + // --- inlet has net positive flow capture leading to + // node having a net negative lateral inflow + q = inlet->flowCapture - inlet->backflow; + if (q > 0.0 && Node[j].newLatFlow < 0.0) + + // --- remove the pollutant mass in the captured flow from + // the system totals since it does not leave the system + // (it is sent to the inlet's capture node) + for (p = 0; p < Nobjects[POLLUT]; p++) + { + w = q * Node[j].newQual[p]; + StepQualTotals[p].outflow -= w; + } + } + } +} + +//============================================================================= + +void inlet_writeStatsReport() +// +// Input: none +// Output: none +// Purpose: writes table of street & inlet flow statistics to SWMM's report file. +// +{ + int j, header = FALSE; + + if (Nobjects[STREET] == 0) return; + for (j = 0; j < Nobjects[LINK]; j++) + { + if (Link[j].xsect.type == STREET_XSECT) + { + if (!header) + { + writeStreetStatsHeader(); + header = TRUE; + } + writeStreetStats(j); + } + } + report_writeLine(""); +} + +//============================================================================= + +double inlet_capturedFlow(int i) +// +// Input: i = a link index +// Output: returns captured flow rate (cfs) +// Purpose: gets the current flow captured by an inlet. +// +{ + if (Link[i].inlet) return Link[i].inlet->flowCapture; + return 0.0; +} + +//============================================================================= + +int readGrateInletParams(int i, char* tok[], int ntoks) +{ +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts a grate's inlet parameters from a set of string tokens. +// + int grateType; + double width, length, areaRatio = 0.0, vSplash = 0.0; + + // --- check for enough tokens + if (ntoks < 5) return error_setInpError(ERR_ITEMS, ""); + + // --- retrieve length & width + if (!getDouble(tok[2], &length) || length <= 0.0) + return error_setInpError(ERR_NUMBER, tok[2]); + if (!getDouble(tok[3], &width) || width <= 0.0) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- retrieve grate type + grateType = findmatch(tok[4], GrateTypeWords); + if (grateType < 0) return error_setInpError(ERR_KEYWORD, tok[4]); + + // --- only read open area & splash velocity for GENERIC type grate + if (grateType == GENERIC) + { + if (ntoks < 6) return error_setInpError(ERR_ITEMS, ""); + if (!getDouble(tok[5], &areaRatio) || areaRatio <= 0.0 + || areaRatio > 1.0) return error_setInpError(ERR_NUMBER, tok[5]); + if (ntoks > 6) + { + if (!getDouble(tok[6], &vSplash) || vSplash < 0.0) + return error_setInpError(ERR_NUMBER, tok[6]); + } + } + + // --- save grate inlet parameters + InletDesigns[i].grateInlet.length = length / UCF(LENGTH); + InletDesigns[i].grateInlet.width = width / UCF(LENGTH); + InletDesigns[i].grateInlet.type = grateType; + InletDesigns[i].grateInlet.fracOpenArea = areaRatio; + InletDesigns[i].grateInlet.splashVeloc = vSplash / UCF(LENGTH); + + // --- check if grate is part of a combo inlet + if (InletDesigns[i].type == GRATE_INLET && + InletDesigns[i].curbInlet.length > 0.0) + InletDesigns[i].type = COMBO_INLET; + return 0; +} + +//============================================================================= + +int readCurbInletParams(int i, char* tok[], int ntoks) +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts curb opening inlet parameters from a set of string tokens. +// +{ + int throatAngle; + double height, length; + + // --- check for enough tokens + if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); + + // --- retrieve length & width of opening + if (!getDouble(tok[2], &length) || length <= 0.0) + return error_setInpError(ERR_NUMBER, tok[2]); + if (!getDouble(tok[3], &height) || height <= 0.0) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- retrieve type of throat angle for curb inlet + throatAngle = VERTICAL_THROAT; + if (InletDesigns[i].type == CURB_INLET && ntoks > 4) + { + throatAngle = findmatch(tok[4], ThroatAngleWords); + if (throatAngle < 0) return error_setInpError(ERR_KEYWORD, tok[4]); + } + + // ---- save curb opening inlet parameters + InletDesigns[i].curbInlet.length = length / UCF(LENGTH); + InletDesigns[i].curbInlet.height = height / UCF(LENGTH); + InletDesigns[i].curbInlet.throatAngle = throatAngle; + + // --- check if curb inlet is part of a combo inlet + if (InletDesigns[i].type == CURB_INLET && + InletDesigns[i].grateInlet.length > 0.0) + InletDesigns[i].type = COMBO_INLET; + return 0; +} + +//============================================================================= + +int readSlottedInletParams(int i, char* tok[], int ntoks) +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts slotted drain inlet parameters from a set of string tokens. +// +{ + double width, length; + + // --- check for enough tokens + if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); + + // --- retrieve length and width + if (!getDouble(tok[2], &length) || length <= 0.0) + return error_setInpError(ERR_NUMBER, tok[2]); + if (!getDouble(tok[3], &width) || width <= 0.0) + return error_setInpError(ERR_NUMBER, tok[3]); + + // --- save slotted inlet parameters + InletDesigns[i].slottedInlet.length = length / UCF(LENGTH); + InletDesigns[i].slottedInlet.width = width / UCF(LENGTH); + return 0; +} + +//============================================================================= + +int readCustomInletParams(int i, char* tok[], int ntoks) +// +// Input: i = inlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: extracts custom inlet parameters from a set of string tokens. +// +{ + int c; // capture curve index + + if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); + else + { + c = project_findObject(CURVE, tok[2]); + if (c < 0) return error_setInpError(ERR_NAME, tok[2]); + } + InletDesigns[i].customCurve = c; + return 0; +} + +//============================================================================= + +void initInletStats(TInlet* inlet) +// +// Input: inlet = an inlet object placed in a conduit link +// Output: none +// Purpose: initializes the performance statistics of an inlet. +// +{ + if (inlet) + { + inlet->flowCapture = 0.0; + inlet->backflow = 0.0; + inlet->stats.flowPeriods = 0; + inlet->stats.capturePeriods = 0; + inlet->stats.backflowPeriods = 0; + inlet->stats.peakFlow = 0.0; + inlet->stats.peakFlowCapture = 0; + inlet->stats.avgFlowCapture = 0; + inlet->stats.bypassFreq = 0; + } +} + +//============================================================================= + +void updateInletStats(TInlet* inlet, double q) +// +// Input: inlet = an inlet object placed in a conduit link +// q = inlet's approach flow (cfs) +// Output: none +// Purpose: updates the performance statistics of an inlet. +// +{ + double qCapture = inlet->flowCapture, + qBackflow = inlet->backflow, + qNet = qCapture - qBackflow, + qBypass = q - qNet, + fCapture = 0.0; + + // --- check for no flow condition + if (q < MIN_RUNOFF_FLOW && qBackflow <= 0.0) return; + inlet->stats.flowPeriods++; + + // --- there is positive net flow from inlet to capture node + if (qNet > 0.0) + { + inlet->stats.capturePeriods++; + fCapture = qNet / q; + fCapture = MIN(fCapture, 1.0); + inlet->stats.avgFlowCapture += fCapture; + if (qBypass > MIN_RUNOFF_FLOW) inlet->stats.bypassFreq++; + } + + // --- otherwise inlet receives backflow from capture node + else inlet->stats.backflowPeriods++; + + // --- update peak flow stats + if (q > inlet->stats.peakFlow) + { + inlet->stats.peakFlow = q; + inlet->stats.peakFlowCapture = fCapture * 100.0; + } +} + +//============================================================================= + +void writeStreetStatsHeader() +// +// Input: none +// Output: none +// Purpose: writes column headers for Street Flow Summary table to SWMM's report file. +// +{ + report_writeLine(""); + report_writeLine("*******************"); + report_writeLine("Street Flow Summary"); + report_writeLine("*******************"); + report_writeLine(""); + fprintf(Frpt.file, +"\n ---------------------------------------------------------------------------------------------------------------------------------------" +"\n Peak Avg. Bypass Back Peak Peak" +"\n Peak Maximum Maximum Flow Flow Flow Flow Capture Bypass" +"\n Flow Spread Depth Inlet Inlet Inlet Capture Capture Freq Freq / Inlet Flow"); + if (UnitSystem == US) fprintf(Frpt.file, +"\n Street Conduit %3s ft ft Design Location Count Pcnt Pcnt Pcnt Pcnt %3s %3s", + FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits]); + else fprintf(Frpt.file, +"\n Street Conduit %3s m m Design Location Pcnt Pcnt Pcnt Pcnt %3s %3s", + FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits]); + fprintf(Frpt.file, +"\n ---------------------------------------------------------------------------------------------------------------------------------------"); +} + +//============================================================================= + +void writeStreetStats(int link) +// +// Input: link = index of a conduit link containing an inlet +// Output: none +// Purpose: writes flow statistics for a Street conduit and its inlet to +// SWMM's report file. +// +{ + int k, t, placement; + double maxSpread, maxDepth, maxFlow; + double fp, cp, afc = 0.0, bpf = 0.0; + TInlet* inlet; + + // --- retrieve street parameters + k = Link[link].subIndex; + t = Link[link].xsect.transect; + inlet = Link[link].inlet; + + // --- get recorded max flow and depth + maxFlow = LinkStats[link].maxFlow; + maxDepth = LinkStats[link].maxDepth; + + // --- SWMM's spread (flow width) at max depth + maxSpread = xsect_getWofY(&Link[link].xsect, maxDepth) / Street[t].sides; + maxSpread = MIN(maxSpread, Street[t].width); +/* + // HEC-22's spread based on max flow (doesn't account for backwater) + Sx = Street[t].slope; + a = Street[t].gutterDepression; + W = Street[t].gutterWidth; + n = Street[t].roughness; + Qfactor = (0.56 / n) * sqrt(Conduit[k].slope) * pow(Sx, 1.67); + maxSpread = getFlowSpread(maxFlow / Street[t].sides); + maxSpread = MIN(maxSpread, Street[t].width); +*/ + // --- write street stats + fprintf(Frpt.file, "\n %-16s", Link[link].ID); + fprintf(Frpt.file, " %9.3f", maxFlow * UCF(FLOW)); + fprintf(Frpt.file, " %9.3f", maxSpread * UCF(LENGTH)); + fprintf(Frpt.file, " %9.3f", maxDepth * UCF(LENGTH)); + + // --- write inlet stats + if (inlet) + { + fprintf(Frpt.file, " %-16s", InletDesigns[inlet->designIndex].ID); + placement = getInletPlacement(inlet, Link[inlet->linkIndex].node2); + if (placement == ON_GRADE) + fprintf(Frpt.file, " ON-GRADE"); + else + fprintf(Frpt.file, " ON-SAG "); + fprintf(Frpt.file, " %5d", inlet->numInlets); + fp = inlet->stats.flowPeriods / 100.0; + if (fp > 0.0) + { + cp = inlet->stats.capturePeriods / 100.0; + fprintf(Frpt.file, " %7.2f", inlet->stats.peakFlowCapture); + if (cp > 0.0) + { + afc = inlet->stats.avgFlowCapture / cp; + bpf = inlet->stats.bypassFreq / cp; + } + fprintf(Frpt.file, " %7.2f", afc); + fprintf(Frpt.file, " %7.2f", bpf); + fprintf(Frpt.file, " %7.2f", inlet->stats.backflowPeriods / fp); + fprintf(Frpt.file, " %7.2f", (maxFlow / Street[t].sides) * UCF(FLOW) * + 0.01 * inlet->stats.peakFlowCapture / inlet->numInlets); + fprintf(Frpt.file, " %7.2f", maxFlow * UCF(FLOW) * 0.01 * + (100.0 - inlet->stats.peakFlowCapture)); + } + } +} + +//============================================================================= + +int getInletPlacement(TInlet* inlet, int j) +// +// Input: inlet = an inlet object placed in a conduit link +// j = index of inlet's bypass node +// Output: returns type of inlet placement +// Purpose: determines actual placement for an inlet with AUTOMATIC placement. +// +{ + if (inlet->placement == AUTOMATIC) + { + if (Node[j].degree > 0) return ON_GRADE; + else return ON_SAG; + } + else return inlet->placement; +} + +//============================================================================= + +void getConduitGeometry(TInlet* inlet) +// +// Input: inlet = an inlet object placed in a conduit link +// Output: none +// Purpose: assigns properties of an inlet's conduit to +// module-level shared variables used by other functions. +// +{ + int linkIndex = inlet->linkIndex; + int t, k = Link[linkIndex].subIndex; + + SL = Conduit[k].slope; // longitudinal slope + Beta = Conduit[k].beta; // 1.486 * sqrt(SL) / n + xsect = &Link[linkIndex].xsect; + + // --- if conduit has a Street cross section + if (xsect->type == STREET_XSECT) + { + t = xsect->transect; + Sx = Street[t].slope; // street cross slope + a = Street[t].gutterDepression; // gutter depression + W = Street[t].gutterWidth; // gutter width + n = Street[t].roughness; // street roughness + Nsides = Street[t].sides; // 1 or 2 sided street + Tcrown = Street[t].width; // distance from curb to crown + Qfactor = inlet->flowFactor; // factor used in Izzard's eqn. + + // --- add inlet's local depression to street's continuous depression + if (inlet && inlet->localDepress * inlet->localWidth > 0) + { + a += inlet->localDepress; // inlet depression + W = inlet->localWidth; // inlet depressed width + } + + // --- slope of depressed gutter section + if (W * a > 0.0) Sw = Sx + a / W; + else Sw = Sx; + } + + // --- conduit has rectangular or trapezoidal cross section + else + { + a = 0.0; + W = 0.0; + n = Conduit[k].roughness; + Nsides = 1; + Sx = 0.01; + Sw = Sx; + } +} + +//============================================================================= + +double getFlowSpread(double Q) +// +// Input: Q = conduit flow rate (cfs) +// Output: returns width of flow spread (ft) +// Purpose: computes width of flow spread across a Street cross section using +// HEC-22 equations derived from Izzard's form of the Manning eqn. +// +{ + int iter; + double f, f1, Sr, Ts1, Ts2, Tw, Qs, Eo; + + f = Qfactor; // = (0.56/n) * SL^0.5 * Sx^1.67 + + // --- no depressed curb + if (a == 0.0) + { + Ts1 = pow(Q / f, 0.375); //HEC-22 Eq(4-2) + } + else + { + // --- check if spread is within curb width + f1 = f * pow((a / W) / Sx, 1.67); + Tw = pow(Q / f1, 0.375); //HEC-22 Eq(4-2) + if (Tw <= W) Ts1 = Tw; + else + { + // --- spread extends beyond curb width + Sr = (Sx + a / W) / Sx; + iter = 1; + Ts1 = pow(Q / f, 0.375) - W; + if (Ts1 <= 0) Ts1 = Tw - W; + while (iter < 11) + { + Eo = getEo(Sr, Ts1, W); + Qs = (1.0 - Eo) * Q; //HEC-22 Eq(4-6) + Ts2 = pow(Qs / f, 0.375); //HEC-22 Eq(4-2) + if (fabs(Ts2 - Ts1) < 0.01) break; + Ts1 = Ts2; + iter++; + } + Ts1 = Ts2 + W; + } + } + return MIN(Ts1, Tcrown); +} + +//============================================================================= + +double getEo(double Sr, double Ts, double w) +// +// Input: Sr = ratio of gutter slope to street cross slope +// Ts = amount of flow spread outside of gutter width (ft) +// w = gutter width (ft) +// Output: returns ratio of gutter flow to total flow in street cross section +// Purpose: solves HEC-22 Eq. (4-4) for Eo with Ts/w substituted for +// (T/w) - 1 where Ts = T - w. +// +{ + double x; + x = Sr / (Ts / w); + x = pow((1.0 + x), 2.67) - 1.0; + x = 1.0 + Sr / x; + return 1.0 / x; +} + +//============================================================================= + +double getOnGradeCapturedFlow(TInlet* inlet, double q, double d) +// +// Input: inlet = an inlet object placed in a conduit link +// q = flow in link prior to any inlet capture (cfs) +// d = flow depth seen by inlet (ft) +// Output: returns flow captured by the inlet (cfs) +// Purpose: computes flow captured by an inlet placed on-grade. +// +// An inlet object placed in a conduit can have multiple inlets of +// the same type distributed along the conduit's length that all +// send their captured flow to the same sewer node. This function +// finds the total captured flow as each individual inlet is analyzed +// sequentially, where its approach flow has been reduced by the +// amount of flow captured by prior inlets. +{ + int i, + linkIndex; // index of link containing inlets + double qApproach, // single inlet's approach flow (cfs) + qc, // single inlet's captured flow (cfs) + qCaptured, // total flow captured by link's inlets (cfs) + qBypassed, // total flow bypassed by link's inlets (cfs) + qMax; // max. flow that a single inlet can capture (cfs) + + if (inlet->numInlets == 0) return 0.0; + linkIndex = inlet->linkIndex; + + // --- check that link has flow + qApproach = q; + if (qApproach < MIN_RUNOFF_FLOW) return 0.0; + + // --- store conduit geometry in shared variables + getConduitGeometry(inlet); + + // --- adjust flow for 2-sided street + qApproach /= Nsides; + qBypassed = qApproach; + qCaptured = 0.0; + + // --- set limit on max. flow captured per inlet + qMax = BIG; + if (inlet->flowLimit > 0.0) qMax = inlet->flowLimit; + + // --- evaluate each inlet + for (i = 1; i <= inlet->numInlets; i++) + { + qc = getOnGradeInletCapture(inlet->designIndex, qBypassed, d) * + inlet->clogFactor; + qc = MIN(qc, qMax); + qc = MIN(qc, qBypassed); + qCaptured += qc; + qBypassed -= qc; + if (qBypassed < MIN_RUNOFF_FLOW) break; + } + return qCaptured *= Nsides; +} + +//============================================================================= + +double getOnGradeInletCapture(int i, double Q, double d) +// +// Input: i = an InletDesigns index +// Q = flow rate seen by inlet (cfs) +// d = flow depth seen by inlet (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by a single on-grade inlet. +// +{ + double Q1 = Q, Qc = 0.0, Lsweep = 0.0, Lcurb = 0.0, Lgrate = 0.0; + + // --- drop curb inlet (in non-Street conduit) only operates in on sag mode + if (InletDesigns[i].type == DROP_CURB_INLET) + { + Qc = getOnSagInletCapture(i, d); + return MIN(Qc, Q); + } + + // --- drop grate inlet (in non-Street conduit) + if (InletDesigns[i].type == DROP_GRATE_INLET) + { + Qc = getGrateInletCapture(i, Q); + return MIN(Qc, Q); + } + + // --- Remaining inlet types apply to Street conduits + + // --- find flow spread + T = getFlowSpread(Q); + + // --- slotted inlet (behaves as a curb opening inlet per HEC-22) + if (InletDesigns[i].type == SLOTTED_INLET) + { + Qc = getCurbInletCapture(Q, InletDesigns[i].slottedInlet.length); + return MIN(Qc, Q); + } + + Lcurb = InletDesigns[i].curbInlet.length; + Lgrate = InletDesigns[i].grateInlet.length; + + // --- curb opening inlet + if (Lcurb > 0.0) + { + Lsweep = Lcurb - Lgrate; + if (Lsweep > 0.0) + { + Qc = getCurbInletCapture(Q1, Lsweep); + Q1 -= Qc; + } + } + + // --- grate inlet + if (Lgrate > 0.0 && Q1 > 0.0) + { + if (Q1 != Q) T = getFlowSpread(Q1); + Qc += getGrateInletCapture(i, Q1); + } + return Qc; +} + +//============================================================================= + +double getGrateInletCapture(int i, double Q) +// +// Input: i = inlet type index +// Q = flow rate seen by inlet (cfs) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-grade grate inlet. +// +{ + int grateType; + double Lg, // grate length (ft) + Wg, // grate width (ft) + A, // total cross section flow area (ft2) + Y, // flow depth (ft) + Eo, // ratio of gutter to total flow + V, // flow velocity (ft/s) + Vo, // splash-over velocity (ft/s) + Qo = Q, // flow over street area (cfs) + Rf = 1.0, // ratio of intercepted to total frontal flow + Rs = 0.0; // ratio of intercepted to total side flow + +// xsect, a, W, & Sx were from getConduitGeometry(). T was from getFlowSpread(). + + Lg = InletDesigns[i].grateInlet.length; + Wg = InletDesigns[i].grateInlet.width; + + // --- flow ratio for drop inlet + if (xsect->type == TRAPEZOIDAL || xsect->type == RECT_OPEN) + { + A = xsect_getAofS(xsect, Q / Beta); + Y = xsect_getYofA(xsect, A); + T = xsect_getWofY(xsect, Y); + Eo = Beta * pow(Y*Wg, 1.67) / pow(Wg + 2*Y, 0.67) / Q; + if (Wg > 0.99*xsect->yBot && xsect->type == TRAPEZOIDAL && xsect->sBot > 0.0) + { + Wg = xsect->yBot; + Sx = 1.0 / xsect->sBot; + } + } + + // --- flow ratio & area for conventional street gutter + else if (a == 0.0) + { + A = T * T * Sx / 2.0; + Eo = getGutterFlowRatio(Wg); // flow ratio based on grate width + if (T >= Tcrown) Qo = Qfactor * pow(Tcrown, 2.67); + } + + // --- flow ratio & area for composite street gutter + else + { + // --- spread confined to gutter + if (T <= W) A = T * T * Sw / 2.0; + + // --- spread beyond gutter width + else A = (T * T * Sx + a * W) / 2.0; + + // flow ratio based on gutter width corrected for grate width + Eo = getGutterFlowRatio(W); + if (Eo < 1.0) + { + if (T >= Tcrown) + Qo = Qfactor * pow(Tcrown, 2.67) / (1.0 - Eo); + Eo = Eo * getGutterAreaRatio(Wg, A); //HEC-22 Eq(4-20a) + } + } + + // --- flow and splash-over velocities + V = Qo / A; + grateType = InletDesigns[i].grateInlet.type; + if (grateType < 0 || grateType == GENERIC) + Vo = InletDesigns[i].grateInlet.splashVeloc; + else + Vo = getSplashOverVelocity(grateType, Lg); + + // --- frontal flow capture efficiency + if (V > Vo) Rf = 1.0 - 0.09 * (V - Vo); //HEC-22 Eq(4-18) + + // --- side flow capture efficiency + if (Eo < 1.0) + { + Rs = 1.0 / (1.0 + (0.15 * pow(V, 1.8) / + Sx / pow(Lg, 2.3))); //HEC-22 Eq(4-19) + } + + // --- return total flow captured + return Q * (Rf * Eo + Rs * (1.0 - Eo)); //HEC-22 Eq(4-21) +} + +//============================================================================= + +double getCurbInletCapture(double Q, double L) +// +// Input: Q = flow rate seen by inlet (cfs) +// L = length of inlet opening (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-sag inlet. +// +{ + double Se = Sx, // equivalent gutter slope + Lt, // length for full capture + Sr, // ratio of gutter slope to cross slope + Eo = 0.0, // ratio of gutter to total flow + E = 1.0; // capture efficiency + +// a, W, Sx, Sw, SL, & n were from getConduitGeometry(). T was from getFlowSpread(). + + // --- for depressed gutter section + if (a > 0.0) + { + Sr = Sw / Sx; + Eo = getEo(Sr, T-W, W); + Se = Sx + Sw * Eo; //HEC-22 Eq(4-24) + } + + // --- opening length for full capture + Lt = 0.6 * pow(Q, 0.42) * pow(SL, 0.3) * + pow(1.0/(n*Se), 0.6); //HEC-22 Eq(4-22a) + + // --- capture efficiency for actual opening length + if (L < Lt) + { + E = 1.0 - (L/Lt); + E = 1 - pow(E, 1.8); //HEC-22 Eq(4-23) + } + E = MIN(E, 1.0); + E = MAX(E, 0.0); + return E * Q; +} + +//============================================================================= + +double getGutterFlowRatio(double w) +// +// Input: w = gutter width (ft) +// Output: returns a flow ratio +// Purpose: computes the ratio of flow over a width of gutter to the total +// flow in a street cross section. +// +{ + if (T <= w) return 1.0; + else if (a > 0.0) + return getEo(Sw / Sx, T - w, w); + else + return 1.0 - pow((1.0 - w / T), 2.67); //HEC-22 Eq(4-16) +} + +//============================================================================= + +double getGutterAreaRatio(double Wg, double A) +// +// Input: Wg = width of grate inlet (ft) +// A = total flow area (ft2) +// Output: returns an area ratio +// Purpose: computes the ratio of the flow area above a grate to the flow +// area above depressed gutter in a street cross section. +// +{ + double As, // flow area beyond gutter width (ft2) + Ag; // flow area over grate width (ft2) + + if (Wg >= W) return 1.0; + if (T <= Wg) return 1.0; + if (T <= W) return Wg / T; + As = 0.5 * SQR((T - W)) * Sx; + Ag = Wg * ( (T * Sx) + a - (Wg * Sw / 2.) ); + return Ag / (A - As); +} + +//============================================================================= + +double getSplashOverVelocity(int grateType, double L) +// +// Input: grateType = grate inlet type code +// L = length of grate inlet (ft) +// Output: returns a splash over velocity +// Purpose: computes the splash over velocity for a standard type of grate +// inlet as a function of its length. +// +{ + return SplashCoeffs[grateType][0] + + SplashCoeffs[grateType][1] * L - + SplashCoeffs[grateType][2] * L * L + + SplashCoeffs[grateType][3] * L * L * L; +} + +//============================================================================= + +double getOnSagCapturedFlow(TInlet* inlet, double q, double d) +// +// Input: inlet = an inlet object placed in a conduit link +// q = flow in link prior to any inlet capture (cfs) +// d = flow depth seen by inlet (ft) +// Output: returns flow captured by the inlet (cfs) +// Purpose: computes flow captured by an inlet placed on-sag. +// +{ + int linkIndex, designIndex, totalInlets; + double qCaptured = 0.0, qMax = BIG; + + if (inlet->numInlets == 0) return 0.0; + totalInlets = Nsides * inlet->numInlets; + linkIndex = inlet->linkIndex; + designIndex = inlet->designIndex; + + // --- store conduit geometry in shared variables + getConduitGeometry(inlet); + + // --- set flow limit per inlet + if (inlet->flowLimit > 0.0) + qMax = inlet->flowLimit; + + // --- find nominal flow captured by inlet + qCaptured = getOnSagInletCapture(designIndex, fabs(d)); + + // --- find actual flow captured by the inlet + qCaptured *= inlet->clogFactor; + qCaptured = MIN(qCaptured, qMax); + qCaptured *= (double)totalInlets; + return qCaptured; +} + +//============================================================================= + +double getOnSagInletCapture(int i, double d) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-sag inlet. +// +{ + double Lsweep = 0.0, Lcurb = 0.0, Lgrate = 0.0; + double Qsw = 0.0, //Sweeper curb opening weir flow + Qso = 0.0, //Sweeper curb opening orifice flow + Qgw = 0.0, //Grate weir flow + Qgo = 0.0, //Grate orifice flow + Qcw = 0.0, //Curb opening weir flow + Qco = 0.0; //Curb opening orifice flow + + if (InletDesigns[i].slottedInlet.length > 0.0) + return getOnSagSlottedFlow(i, d); + + Lgrate = InletDesigns[i].grateInlet.length; + if (Lgrate > 0.0) findOnSagGrateFlows(i, d, &Qgw, &Qgo); + + Lcurb = InletDesigns[i].curbInlet.length; + if (Lcurb > 0.0) + { + Lsweep = Lcurb - Lgrate; + if (Lsweep > 0.0) findOnSagCurbFlows(i, d, Lsweep, &Qsw, &Qso); + if (Qgo > 0.0) findOnSagCurbFlows(i, d, Lgrate, &Qcw, &Qco); + } + return Qgw + Qgo + Qsw + Qso + Qco; +} + +//============================================================================= + +void findOnSagGrateFlows(int i, double d, double *Qw, double *Qo) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// Output: Qw = flow captured in weir mode (cfs) +// Qo = flow captured in orifice mode (cfs) +// Purpose: finds the flow captured by an on-sag grate inlet. +// +{ + int grateType = InletDesigns[i].grateInlet.type; + double Lg = InletDesigns[i].grateInlet.length; + double Wg = InletDesigns[i].grateInlet.width; + double P, // grate perimeter (ft) + Ao, // grate opening area (ft2) + di; // average flow depth across grate (ft) + + // --- for drop grate inlets + if (InletDesigns[i].type == DROP_GRATE_INLET) + { + di = d; + P = 2.0 * (Lg + Wg); + } + + // --- for gutter grate inlets: + else + { + // --- check for spread within grate width + if (d <= Wg * Sw) + Wg = d / Sw; + + // --- avergage depth over grate + di = d - (Wg / 2.0) * Sw; + + // --- effective grate perimeter + P = Lg + 2.0 * Wg; + } + + if (grateType == GENERIC) + Ao = Lg * Wg * InletDesigns[i].grateInlet.fracOpenArea; + else + Ao = Lg * Wg * GrateOpeningRatios[grateType]; + + // --- weir flow applies (based on depth where result of + // weir eqn. equals result of orifice eqn.) + + if (d <= 1.79 * Ao / P) + { + *Qw = 3.0 * P * pow(di, 1.5); //HEC-22 Eq(4-26) + } + + // --- orifice flow applies + else + { + *Qo = 0.67 * Ao * sqrt(2.0 * 32.16 * di); //HEC-22 Eq(4-27) + } +} + +//============================================================================= + +void findOnSagCurbFlows(int i, double d, double L, double *Qw, double *Qo) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// L = length of curb opening (ft) +// Output: Qw = flow captured in weir mode (cfs) +// Qo = flow captured in orifice mode (cfs) +// Purpose: finds the flow captured by an on-sag curb opening inlet. +// +{ + int throatAngle = InletDesigns[i].curbInlet.throatAngle; + double h = InletDesigns[i].curbInlet.height; + double Qweir, Qorif, P; + double dweir, dorif, r; + + // --- check for orifice flow + if (L <= 0.0) return; + if (InletDesigns[i].type == DROP_CURB_INLET) L = L * 4.0; + dorif = 1.4 * h; + if (d > dorif) + { + *Qo = getCurbOrificeFlow(d, h, L, throatAngle); + return; + } + + // --- for uniform cross slope or very long opening + if (a == 0.0 || L > 12.0) + { + // --- check for weir flow + dweir = h; + if (d < dweir) + { + *Qw = 3.0 * L * pow(d, 1.5); //HEC-22 Eq(4-30) + return; + } + else Qweir = 3.0 * L * pow(dweir, 1.5); + } + + // --- for depressed gutter + else + { + // --- check for weir flow + P = L + 1.8 * W; + dweir = h + a; + if (d < dweir) + { + *Qw = 2.3 * P * pow(d, 1.5); //HEC-22 Eq(4-28) + return; + } + else Qweir = 2.3 * P * pow(dweir, 1.5); + } + + // --- interpolate between Qweir at depth dweir and Qorif at depth dorif + Qorif = getCurbOrificeFlow(dorif, h, L, throatAngle); + r = (d - dweir) / (dorif - dweir); + *Qw = (1.0 -r) * Qweir; + *Qo = r * Qorif; +} + +//============================================================================= + +double getCurbOrificeFlow(double di, double h, double L, int throatAngle) +// +// Input: di = water level at lip of inlet opening (ft) +// h = height of curb opening (ft) +// L = length of curb opening (ft) +// throatAngle = type of throat angle in curb opening +// Output: return flow captured by inlet (cfs) +// Purpose: finds the flow captured by an on-sag curb opening inlet under +// orifice flow conditions. +// +{ + double d = di; + if (throatAngle == HORIZONTAL_THROAT) + d = di - h / 2.0; + else if (throatAngle == INCLINED_THROAT) + d = di + (h / 2.0) * 0.7071; + return 0.67 * h * L * sqrt(2.0 * 32.16 * d); //HEC-22 Eq(4-31a) +} + +//============================================================================= + +double getOnSagSlottedFlow(int i, double d) +// +// Input: i = inlet type index +// d = water level seen by inlet (ft) +// Output: returns captured flow rate (cfs) +// Purpose: finds the flow captured by an on-sag slotted inlet. +// +// Note: weir flow = orifice flow at d = 2.587 * inlet width +{ + double L = InletDesigns[i].slottedInlet.length; + double w = InletDesigns[i].slottedInlet.width; + + if (d <= 2.587 * w) + return 2.48 * L * pow(d, 1.5); //HEC-22 Eq(4-32) + else + return 0.8 * L * w * sqrt(64.32 * d); //HEC-22 Eq(4-33) +} + +//============================================================================= + +void getBackflowRatios() +// +// Input: none +// Output: overflow ratio for each inlet +// Purpose: finds the fraction of the overflow produced by an inlet's capture +// node that becomes backflow into the inlet. +// +// Note: when a capture node receives flow from two or more inlets +// its backflow is divided among the inlets based on: +// i) the fraction of total open area for standard inlets +// ii) the fraction of total number of inlets for custom inlets +{ + TInlet* inlet; + double area; + double f; + int n; + + // --- info for each node receiving flow from an inlet + typedef struct + { + int numInletLinks; // total # inlet links + int numStdInletLinks; // total # standard inlet links + int numCustomInlets; // # custom inlets + double totalInletArea; // open area of standard inlets + } TInletNode; + TInletNode* inletNodes = (TInletNode *) calloc(Nobjects[NODE], sizeof(TInletNode)); + if (inletNodes == NULL) return; + + // --- Finds each inlet's contribution to its capture node + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + n = inlet->nodeIndex; + inletNodes[n].numInletLinks++; + area = getInletArea(inlet); + if (area > 0.0) + { + inletNodes[n].numStdInletLinks++; + inletNodes[n].totalInletArea += area; + } + else + inletNodes[n].numCustomInlets += inlet->numInlets; + } + + // --- find fraction of capture node's overflow that becomes inlet backflow + for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) + { + // --- f is ratio of links with standard inlets to all inlet links + // connected to receptor node n + n = inlet->nodeIndex; + f = (double) inletNodes[n].numStdInletLinks / + (double) inletNodes[n].numInletLinks; + + // --- backflow ratio depends if inlet is standard or custom (area = 0) + area = getInletArea(inlet); + if (area == 0.0) + inlet->backflowRatio = (double)inlet->numInlets / + (double)inletNodes[n].numCustomInlets * (1. - f); + else + inlet->backflowRatio = area / inletNodes[n].totalInletArea * f; + } + free(inletNodes); +} + +//============================================================================= + +double getInletArea(TInlet* inlet) +// +// Input: inlet = an inlet object placed in a conduit link +// Output: returns the unclogged open area of the inlet (ft2) +// Purpose: finds the total open flow area inlets placed in a conduit. +// +{ + double area = 0.0; + double curbLength; + int i = inlet->designIndex; + int grateType = InletDesigns[i].grateInlet.type; + + if (InletDesigns[i].grateInlet.length > 0.0) + { + area = InletDesigns[i].grateInlet.length * InletDesigns[i].grateInlet.width; + if (grateType == GENERIC) + area *= InletDesigns[i].grateInlet.fracOpenArea; + else + area *= GrateOpeningRatios[grateType]; + } + + curbLength = InletDesigns[i].curbInlet.length - InletDesigns[i].grateInlet.length; + if (curbLength > 0.0) + area += curbLength * InletDesigns[i].curbInlet.height; + + if (InletDesigns[i].slottedInlet.length > 0.0) + area = InletDesigns[i].slottedInlet.length * InletDesigns[i].slottedInlet.width; + return area * inlet->numInlets * inlet->clogFactor; +} + +//============================================================================= + +double getCustomCapturedFlow(TInlet* inlet, double q, double d) +{ + int i = inlet->designIndex; // inlet's position in InletDesigns array + int j; // counter for replicate inlets + int sides = 1; // number of sides for inlet's street (1 or 2) + int c; // an index into the Curve array + double qApproach, // inlet's approach flow (cfs) + qBypassed, // inlet's bypassed flow (cfs) + qCaptured, // inlet's captured flow (cfs) + qIncrement, // increment to captured flow (cfs) + qMax = BIG; // user-supplied flow capture limit (cfs) + + if (inlet->numInlets == 0) return 0.0; + + // --- set limit on max. flow captured per inlet + qMax = BIG; + if (inlet->flowLimit > 0.0) qMax = inlet->flowLimit; + + // --- get number of sides to a street xsection + xsect = &Link[inlet->linkIndex].xsect; + if (xsect->type == STREET_XSECT) + sides = Street[xsect->transect].sides; + + // --- adjust flow for 2-sided street + qApproach = q / sides; + qBypassed = qApproach; + qCaptured = 0.0; + + // --- get index of inlet's capture curve + c = InletDesigns[i].customCurve; + if (c >= 0) + { + // --- curve is captured flow v. approach flow + if (Curve[c].curveType == DIVERSION_CURVE) + { + // --- add up incrmental capture of each replicate inlet + for (j = 1; j <= inlet->numInlets; j++) + { + qIncrement = inlet->clogFactor * + table_lookupEx(&Curve[c], qBypassed * UCF(FLOW)) / UCF(FLOW); + qIncrement = MIN(qIncrement, qMax); + qIncrement = MIN(qIncrement, qBypassed); + qCaptured += qIncrement; + qBypassed -= qIncrement; + if (qBypassed < MIN_RUNOFF_FLOW) break; + } + } + + // --- curve is captured flow v. downstream node depth + else if (Curve[c].curveType == RATING_CURVE) + { + qCaptured = inlet->numInlets * inlet->clogFactor * + table_lookupEx(&Curve[c], d * UCF(LENGTH)) / UCF(FLOW); + } + qCaptured *= sides; + } + return qCaptured; +} diff --git a/src/inlet.h b/src/inlet.h new file mode 100644 index 000000000..ca7830725 --- /dev/null +++ b/src/inlet.h @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// inlet.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Street/Channel Inlet Functions +// +//----------------------------------------------------------------------------- +#ifndef INLET_H +#define INLET_H + +typedef struct TInlet TInlet; + +int inlet_create(int nInlets); +void inlet_delete(); +int inlet_readDesignParams(char* tok[], int ntoks); +int inlet_readUsageParams(char* tok[], int ntoks); +void inlet_validate(); + +void inlet_findCapturedFlows(double tStep); +void inlet_adjustQualInflows(); +void inlet_adjustQualOutflows(); + +void inlet_writeStatsReport(); +double inlet_capturedFlow(int link); + +#endif diff --git a/src/input.c b/src/input.c new file mode 100644 index 000000000..c0f7a0d9f --- /dev/null +++ b/src/input.c @@ -0,0 +1,930 @@ +//----------------------------------------------------------------------------- +// input.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 06/01/22 (Build 5.2.1) +// Author: L. Rossman +// +// Input data processing functions. +// +// Update History +// ============== +// Build 5.1.007: +// - Support added for climate adjustment input data. +// Build 5.1.011: +// - Support added for reading hydraulic event dates. +// Build 5.1.015: +// - Support added for multiple infiltration methods within a project. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for named variables & math expressions in control rules. +// Build 5.2.1: +// - Possible integer underflow avoided in getTokens() function. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" +#include "lid.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const int MAXERRS = 100; // Max. input errors reported + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +static char *Tok[MAXTOKS]; // String tokens from line of input +static int Ntokens; // Number of tokens in line of input +static int Mobjects[MAX_OBJ_TYPES]; // Working number of objects of each type +static int Mnodes[MAX_NODE_TYPES]; // Working number of node objects +static int Mlinks[MAX_LINK_TYPES]; // Working number of link objects +static int Mevents; // Working number of event periods + +//----------------------------------------------------------------------------- +// External Functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// input_countObjects (called by swmm_open in swmm5.c) +// input_readData (called by swmm_open in swmm5.c) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int addObject(int objType, char* id); +static int getTokens(char *s); +static int parseLine(int sect, char* line); +static int readOption(char* line); +static int readTitle(char* line); +static int readControl(char* tok[], int ntoks); +static int readNode(int type); +static int readLink(int type); +static int readEvent(char* tok[], int ntoks); + +//============================================================================= + +int input_countObjects() +// +// Input: none +// Output: returns error code +// Purpose: reads input file to determine number of system objects. +// +{ + char line[MAXLINE+1]; // line from input data file + char wLine[MAXLINE+1]; // working copy of input line + char *tok; // first string token of line + int sect = -1, newsect; // input data sections + int errcode = 0; // error code + int errsum = 0; // number of errors found + int i; + long lineCount = 0; + + // --- initialize number of objects & set default values + if ( ErrorCode ) return ErrorCode; + error_setInpError(0, ""); + for (i = 0; i < MAX_OBJ_TYPES; i++) Nobjects[i] = 0; + for (i = 0; i < MAX_NODE_TYPES; i++) Nnodes[i] = 0; + for (i = 0; i < MAX_LINK_TYPES; i++) Nlinks[i] = 0; + controls_init(); + + // --- make pass through data file counting number of each object + while ( fgets(line, MAXLINE, Finp.file) != NULL ) + { + // --- skip blank lines & those beginning with a comment + lineCount++; + sstrncpy(wLine, line, MAXLINE); // make working copy of line + tok = strtok(wLine, SEPSTR); // get first text token on line + if ( tok == NULL ) continue; + if ( *tok == ';' ) continue; + + // --- check if line begins with a new section heading + if ( *tok == '[' ) + { + // --- look for heading in list of section keywords + newsect = findmatch(tok, SectWords); + if ( newsect >= 0 ) + { + sect = newsect; + continue; + } + else + { + sect = -1; + errcode = ERR_KEYWORD; + } + } + + // --- if in OPTIONS section then read the option setting + // otherwise add object and its ID name (tok) to project + if ( sect == s_OPTION ) errcode = readOption(line); + else if ( sect >= 0 ) errcode = addObject(sect, tok); + + // --- report any error found + if ( errcode ) + { + report_writeInputErrorMsg(errcode, sect, line, lineCount); + errsum++; + if (errsum >= MAXERRS ) break; + } + } + + // --- set global error code if input errors were found + if ( errsum > 0 ) ErrorCode = ERR_INPUT; + return ErrorCode; +} + +//============================================================================= + +int input_readData() +// +// Input: none +// Output: returns error code +// Purpose: reads input file to determine input parameters for each object. +// +{ + char line[MAXLINE+1]; // line from input data file + char wLine[MAXLINE+1]; // working copy of input line + char* comment; // ptr. to start of comment in input line + int sect, newsect; // data sections + int inperr, errsum; // error code & total error count + int lineLength; // number of characters in input line + int i; + long lineCount = 0; + + // --- initialize working item count arrays + // (final counts in Mobjects, Mnodes & Mlinks should + // match those in Nobjects, Nnodes and Nlinks). + if ( ErrorCode ) return ErrorCode; + error_setInpError(0, ""); + for (i = 0; i < MAX_OBJ_TYPES; i++) Mobjects[i] = 0; + for (i = 0; i < MAX_NODE_TYPES; i++) Mnodes[i] = 0; + for (i = 0; i < MAX_LINK_TYPES; i++) Mlinks[i] = 0; + Mevents = 0; + + // --- initialize starting date for all time series + for ( i = 0; i < Nobjects[TSERIES]; i++ ) + { + Tseries[i].lastDate = StartDate + StartTime; + } + + // --- read each line from input file + sect = 0; + errsum = 0; + rewind(Finp.file); + while ( fgets(line, MAXLINE, Finp.file) != NULL ) + { + // --- make copy of line and scan for tokens + lineCount++; + sstrncpy(wLine, line, MAXLINE); + Ntokens = getTokens(wLine); + + // --- skip blank lines and comments + if ( Ntokens == 0 ) continue; + if ( *Tok[0] == ';' ) continue; + + // --- check if max. line length exceeded + lineLength = (int)strlen(line); + if ( lineLength >= MAXLINE ) + { + // --- don't count comment if present + comment = strchr(line, ';'); + if ( comment ) lineLength = (int)(comment - line); // Pointer math here + if ( lineLength >= MAXLINE ) + { + inperr = ERR_LINE_LENGTH; + report_writeInputErrorMsg(inperr, sect, line, lineCount); + errsum++; + } + } + + // --- check if at start of a new input section + if (*Tok[0] == '[') + { + // --- match token against list of section keywords + newsect = findmatch(Tok[0], SectWords); + if (newsect >= 0) + { + // --- SPECIAL CASE FOR TRANSECTS + // finish processing the last set of transect data + if ( sect == s_TRANSECT ) + transect_validate(Nobjects[TRANSECT]-1); + + // --- begin a new input section + sect = newsect; + continue; + } + else + { + inperr = error_setInpError(ERR_KEYWORD, Tok[0]); + report_writeInputErrorMsg(inperr, sect, line, lineCount); + errsum++; + break; + } + } + + // --- otherwise parse tokens from input line + else + { + inperr = parseLine(sect, line); + if ( inperr > 0 ) + { + errsum++; + if ( errsum > MAXERRS ) report_writeLine(FMT19); + else report_writeInputErrorMsg(inperr, sect, line, lineCount); + } + } + + // --- stop if reach end of file or max. error count + if (errsum > MAXERRS) break; + } /* End of while */ + + // --- check for errors + if (errsum > 0) ErrorCode = ERR_INPUT; + return ErrorCode; +} + +//============================================================================= + +int addObject(int objType, char* id) +// +// Input: objType = object type index +// id = object's ID string +// Output: returns an error code +// Purpose: adds a new object to the project. +// +{ + int errcode = 0; + switch( objType ) + { + case s_RAINGAGE: + if ( !project_addObject(GAGE, id, Nobjects[GAGE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[GAGE]++; + break; + + case s_SUBCATCH: + if ( !project_addObject(SUBCATCH, id, Nobjects[SUBCATCH]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[SUBCATCH]++; + break; + + case s_AQUIFER: + if ( !project_addObject(AQUIFER, id, Nobjects[AQUIFER]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[AQUIFER]++; + break; + + case s_UNITHYD: + // --- the same Unit Hydrograph can span several lines + if ( project_findObject(UNITHYD, id) < 0 ) + { + if ( !project_addObject(UNITHYD, id, Nobjects[UNITHYD]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[UNITHYD]++; + } + break; + + case s_SNOWMELT: + // --- the same Snowmelt object can appear on several lines + if ( project_findObject(SNOWMELT, id) < 0 ) + { + if ( !project_addObject(SNOWMELT, id, Nobjects[SNOWMELT]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[SNOWMELT]++; + } + break; + + case s_JUNCTION: + if ( !project_addObject(NODE, id, Nobjects[NODE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[NODE]++; + Nnodes[JUNCTION]++; + break; + + case s_OUTFALL: + if ( !project_addObject(NODE, id, Nobjects[NODE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[NODE]++; + Nnodes[OUTFALL]++; + break; + + case s_STORAGE: + if ( !project_addObject(NODE, id, Nobjects[NODE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[NODE]++; + Nnodes[STORAGE]++; + break; + + case s_DIVIDER: + if ( !project_addObject(NODE, id, Nobjects[NODE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[NODE]++; + Nnodes[DIVIDER]++; + break; + + case s_CONDUIT: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[CONDUIT]++; + break; + + case s_PUMP: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[PUMP]++; + break; + + case s_ORIFICE: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[ORIFICE]++; + break; + + case s_WEIR: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[WEIR]++; + break; + + case s_OUTLET: + if ( !project_addObject(LINK, id, Nobjects[LINK]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LINK]++; + Nlinks[OUTLET]++; + break; + + case s_POLLUTANT: + if ( !project_addObject(POLLUT, id, Nobjects[POLLUT]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[POLLUT]++; + break; + + case s_LANDUSE: + if ( !project_addObject(LANDUSE, id, Nobjects[LANDUSE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[LANDUSE]++; + break; + + case s_PATTERN: + // --- a time pattern can span several lines + if ( project_findObject(TIMEPATTERN, id) < 0 ) + { + if ( !project_addObject(TIMEPATTERN, id, Nobjects[TIMEPATTERN]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[TIMEPATTERN]++; + } + break; + + case s_CURVE: + // --- a Curve can span several lines + if ( project_findObject(CURVE, id) < 0 ) + { + if ( !project_addObject(CURVE, id, Nobjects[CURVE]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[CURVE]++; + + // --- check for a conduit shape curve + id = strtok(NULL, SEPSTR); + if ( findmatch(id, CurveTypeWords) == SHAPE_CURVE ) + Nobjects[SHAPE]++; + } + break; + + case s_TIMESERIES: + // --- a Time Series can span several lines + if ( project_findObject(TSERIES, id) < 0 ) + { + if ( !project_addObject(TSERIES, id, Nobjects[TSERIES]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[TSERIES]++; + } + break; + + case s_CONTROL: + if ( match(id, w_RULE) ) Nobjects[CONTROL]++; + else controls_addToCount(id); + break; + + case s_TRANSECT: + // --- for TRANSECTS, ID name appears as second entry on X1 line + if ( match(id, "X1") ) + { + id = strtok(NULL, SEPSTR); + if ( id ) + { + if ( !project_addObject(TRANSECT, id, Nobjects[TRANSECT]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[TRANSECT]++; + } + } + break; + + case s_LID_CONTROL: + // --- an LID object can span several lines + if ( project_findObject(LID, id) < 0 ) + { + if ( !project_addObject(LID, id, Nobjects[LID]) ) + { + errcode = error_setInpError(ERR_DUP_NAME, id); + } + Nobjects[LID]++; + } + break; + + case s_EVENT: NumEvents++; break; + + case s_STREET: + if ( !project_addObject(STREET, id, Nobjects[STREET]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[STREET]++; + break; + + case s_INLET: + // --- an INLET object can span several lines + if (project_findObject(INLET, id) < 0) + { + if ( !project_addObject(INLET, id, Nobjects[INLET]) ) + errcode = error_setInpError(ERR_DUP_NAME, id); + Nobjects[INLET]++; + } + break; + } + return errcode; +} + +//============================================================================= + +int parseLine(int sect, char *line) +// +// Input: sect = current section of input file +// *line = line of text read from input file +// Output: returns error code or 0 if no error found +// Purpose: parses contents of a tokenized line of text read from input file. +// +{ + int j, err; + switch (sect) + { + case s_TITLE: + return readTitle(line); + + case s_RAINGAGE: + j = Mobjects[GAGE]; + err = gage_readParams(j, Tok, Ntokens); + Mobjects[GAGE]++; + return err; + + case s_TEMP: + return climate_readParams(Tok, Ntokens); + + case s_EVAP: + return climate_readEvapParams(Tok, Ntokens); + + case s_ADJUST: + return climate_readAdjustments(Tok, Ntokens); + + case s_SUBCATCH: + j = Mobjects[SUBCATCH]; + err = subcatch_readParams(j, Tok, Ntokens); + Mobjects[SUBCATCH]++; + return err; + + case s_SUBAREA: + return subcatch_readSubareaParams(Tok, Ntokens); + + case s_INFIL: + return infil_readParams(InfilModel, Tok, Ntokens); + + case s_AQUIFER: + j = Mobjects[AQUIFER]; + err = gwater_readAquiferParams(j, Tok, Ntokens); + Mobjects[AQUIFER]++; + return err; + + case s_GROUNDWATER: + return gwater_readGroundwaterParams(Tok, Ntokens); + + case s_GWF: + return gwater_readFlowExpression(Tok, Ntokens); + + case s_SNOWMELT: + return snow_readMeltParams(Tok, Ntokens); + + case s_JUNCTION: + return readNode(JUNCTION); + + case s_OUTFALL: + return readNode(OUTFALL); + + case s_STORAGE: + return readNode(STORAGE); + + case s_DIVIDER: + return readNode(DIVIDER); + + case s_CONDUIT: + return readLink(CONDUIT); + + case s_PUMP: + return readLink(PUMP); + + case s_ORIFICE: + return readLink(ORIFICE); + + case s_WEIR: + return readLink(WEIR); + + case s_OUTLET: + return readLink(OUTLET); + + case s_XSECTION: + return link_readXsectParams(Tok, Ntokens); + + case s_TRANSECT: + return transect_readParams(&Mobjects[TRANSECT], Tok, Ntokens); + + case s_LOSSES: + return link_readLossParams(Tok, Ntokens); + + case s_POLLUTANT: + j = Mobjects[POLLUT]; + err = landuse_readPollutParams(j, Tok, Ntokens); + Mobjects[POLLUT]++; + return err; + + case s_LANDUSE: + j = Mobjects[LANDUSE]; + err = landuse_readParams(j, Tok, Ntokens); + Mobjects[LANDUSE]++; + return err; + + case s_BUILDUP: + return landuse_readBuildupParams(Tok, Ntokens); + + case s_WASHOFF: + return landuse_readWashoffParams(Tok, Ntokens); + + case s_COVERAGE: + return subcatch_readLanduseParams(Tok, Ntokens); + + case s_INFLOW: + return inflow_readExtInflow(Tok, Ntokens); + + case s_DWF: + return inflow_readDwfInflow(Tok, Ntokens); + + case s_PATTERN: + return inflow_readDwfPattern(Tok, Ntokens); + + case s_RDII: + return rdii_readRdiiInflow(Tok, Ntokens); + + case s_UNITHYD: + return rdii_readUnitHydParams(Tok, Ntokens); + + case s_LOADING: + return subcatch_readInitBuildup(Tok, Ntokens); + + case s_TREATMENT: + return treatmnt_readExpression(Tok, Ntokens); + + case s_CURVE: + return table_readCurve(Tok, Ntokens); + + case s_TIMESERIES: + return table_readTimeseries(Tok, Ntokens); + + case s_CONTROL: + return readControl(Tok, Ntokens); + + case s_REPORT: + return report_readOptions(Tok, Ntokens); + + case s_FILE: + return iface_readFileParams(Tok, Ntokens); + + case s_LID_CONTROL: + return lid_readProcParams(Tok, Ntokens); + + case s_LID_USAGE: + return lid_readGroupParams(Tok, Ntokens); + + case s_EVENT: + return readEvent(Tok, Ntokens); + + case s_STREET: + return street_readParams(Tok, Ntokens); + + case s_INLET: + return inlet_readDesignParams(Tok, Ntokens); + + case s_INLET_USAGE: + return inlet_readUsageParams(Tok, Ntokens); + + default: return 0; + } +} + +//============================================================================= + +int readControl(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// Purpose: reads a line of input for a control rule. +// +{ + int index; + int keyword; + + // --- check for minimum number of tokens + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + + if (match(tok[0], w_VARIABLE)) + return controls_addVariable(tok, ntoks); + if (match(tok[0], w_EXPRESSION)) + return controls_addExpression(tok, ntoks); + + // --- get index of control rule keyword + keyword = findmatch(tok[0], RuleKeyWords); + if ( keyword < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); + + // --- if line begins a new control rule, add rule ID to the database + if ( keyword == 0 ) + { + if ( !project_addObject(CONTROL, tok[1], Mobjects[CONTROL]) ) + { + return error_setInpError(ERR_DUP_NAME, Tok[1]); + } + Mobjects[CONTROL]++; + } + + // --- get index of last control rule processed + index = Mobjects[CONTROL] - 1; + if ( index < 0 ) return error_setInpError(ERR_RULE, ""); + + // --- add current line as a new clause to the control rule + return controls_addRuleClause(index, keyword, Tok, Ntokens); +} + +//============================================================================= + +int readOption(char* line) +// +// Input: line = line of input data +// Output: returns error code +// Purpose: reads an input line containing a project option. +// +{ + Ntokens = getTokens(line); + if ( Ntokens < 2 ) return 0; + return project_readOption(Tok[0], Tok[1]); +} + +//============================================================================= + +int readTitle(char* line) +// +// Input: line = line from input file +// Output: returns error code +// Purpose: reads project title from line of input. +// +{ + int i, n; + for (i = 0; i < MAXTITLE; i++) + { + // --- find next empty Title entry + if ( strlen(Title[i]) == 0 ) + { + // --- strip line feed character from input line + n = (int)strlen(line); + if (line[n-1] == 10) line[n-1] = ' '; + + // --- copy input line into Title entry + sstrncpy(Title[i], line, MAXMSG); + break; + } + } + return 0; +} + +//============================================================================= + +int readNode(int type) +// +// Input: type = type of node +// Output: returns error code +// Purpose: reads data for a node from a line of input. +// +{ + int j = Mobjects[NODE]; + int k = Mnodes[type]; + int err = node_readParams(j, type, k, Tok, Ntokens); + Mobjects[NODE]++; + Mnodes[type]++; + return err; +} + +//============================================================================= + +int readLink(int type) +// +// Input: type = type of link +// Output: returns error code +// Purpose: reads data for a link from a line of input. +// +{ + int j = Mobjects[LINK]; + int k = Mlinks[type]; + int err = link_readParams(j, type, k, Tok, Ntokens); + Mobjects[LINK]++; + Mlinks[type]++; + return err; +} + +//============================================================================= + +int readEvent(char* tok[], int ntoks) +{ + DateTime x[4]; + + if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); + if ( !datetime_strToDate(tok[0], &x[0]) ) + return error_setInpError(ERR_DATETIME, tok[0]); + if ( !datetime_strToTime(tok[1], &x[1]) ) + return error_setInpError(ERR_DATETIME, tok[1]); + if ( !datetime_strToDate(tok[2], &x[2]) ) + return error_setInpError(ERR_DATETIME, tok[2]); + if ( !datetime_strToTime(tok[3], &x[3]) ) + return error_setInpError(ERR_DATETIME, tok[3]); + + Event[Mevents].start = x[0] + x[1]; + Event[Mevents].end = x[2] + x[3]; + if ( Event[Mevents].start >= Event[Mevents].end ) + return error_setInpError(ERR_DATETIME, " - start date exceeds end date"); + Mevents++; + return 0; +} + +//============================================================================= + +int findmatch(char *s, char *keyword[]) +// +// Input: s = character string +// keyword = array of keyword strings +// Output: returns index of matching keyword or -1 if no match found +// Purpose: finds match between string and array of keyword strings. +// +{ + int i = 0; + while (keyword[i] != NULL) + { + if (match(s, keyword[i])) return(i); + i++; + } + return(-1); +} + +//============================================================================= + +int match(char *str, char *substr) +// +// Input: str = character string being searched +// substr = sub-string being searched for +// Output: returns 1 if sub-string found, 0 if not +// Purpose: sees if a sub-string of characters appears in a string +// (not case sensitive). +// +{ + int i,j,k; + + // --- fail if substring is empty + if (!substr[0]) return(0); + + // --- skip leading blanks of str + for (k = 0; str[k]; k++) + { + if (str[k] != ' ') break; + } + + // --- check if substr matches remainder of str + for (i = k,j = 0; substr[j]; i++,j++) + { + if (!str[i] || UCHAR(str[i]) != UCHAR(substr[j])) return(0); + } + return(1); +} + +//============================================================================= + +int getInt(char *s, int *y) +// +// Input: s = a character string +// Output: y = converted value of s, +// returns 1 if conversion successful, 0 if not +// Purpose: converts a string to an integer number. +// +{ + double x; + if ( getDouble(s, &x) ) + { + if ( x < 0.0 ) x -= 0.01; + else x += 0.01; + *y = (int)x; + return 1; + } + *y = 0; + return 0; +} + +//============================================================================= + +int getFloat(char *s, float *y) +// +// Input: s = a character string +// Output: y = converted value of s, +// returns 1 if conversion successful, 0 if not +// Purpose: converts a string to a single precision floating point number. +// +{ + char *endptr; + *y = (float) strtod(s, &endptr); + if (*endptr > 0) return(0); + return(1); +} + +//============================================================================= + +int getDouble(char *s, double *y) +// +// Input: s = a character string +// Output: y = converted value of s, +// returns 1 if conversion successful, 0 if not +// Purpose: converts a string to a double precision floating point number. +// +{ + char *endptr; + *y = strtod(s, &endptr); + if (*endptr > 0) return(0); + return(1); +} + +//============================================================================= + +int getTokens(char *s) +// +// Input: s = a character string +// Output: returns number of tokens found in s +// Purpose: scans a string for tokens, saving pointers to them +// in shared variable Tok[]. +// +// Notes: Tokens can be separated by the characters listed in SEPSTR +// (spaces, tabs, newline, carriage return) which is defined +// in CONSTS.H. Text between quotes is treated as a single token. +// +{ + int len, n; + int m; + char *c; + + // --- begin with no tokens + for (n = 0; n < MAXTOKS; n++) Tok[n] = NULL; + n = 0; + + // --- truncate s at start of comment + c = strchr(s,';'); + if (c) *c = '\0'; + len = (int)strlen(s); + + // --- scan s for tokens until nothing left + while (len > 0 && n < MAXTOKS) + { + m = (int)strcspn(s,SEPSTR); // find token length + if (m == 0) s++; // no token found + else + { + if (*s == '"') // token begins with quote + { + s++; // start token after quote + len--; // reduce length of s + m = (int)strcspn(s,"\"\n"); // find end quote or new line + } + s[m] = '\0'; // null-terminate the token + Tok[n] = s; // save pointer to token + n++; // update token count + s += m+1; // begin next token + } + len -= m+1; // update length of s + } + return n; +} + +//============================================================================= diff --git a/src/inputrpt.c b/src/inputrpt.c new file mode 100644 index 000000000..46a6f952f --- /dev/null +++ b/src/inputrpt.c @@ -0,0 +1,354 @@ +//----------------------------------------------------------------------------- +// inputrpt.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Report writing functions for input data summary. +// +// Update History +// ============== +// Build 5.2.0: +// - Support added for reporting Street geometry tables. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" +#include "lid.h" + +#define WRITE(x) (report_writeLine((x))) + +//============================================================================= + +void inputrpt_writeInput() +// +// Input: none +// Output: none +// Purpose: writes summary of input data to report file. +// +{ + int m; + int i, k; + int lidCount = 0; + if ( ErrorCode ) return; + + WRITE(""); + WRITE("*************"); + WRITE("Element Count"); + WRITE("*************"); + fprintf(Frpt.file, "\n Number of rain gages ...... %d", Nobjects[GAGE]); + fprintf(Frpt.file, "\n Number of subcatchments ... %d", Nobjects[SUBCATCH]); + fprintf(Frpt.file, "\n Number of nodes ........... %d", Nobjects[NODE]); + fprintf(Frpt.file, "\n Number of links ........... %d", Nobjects[LINK]); + fprintf(Frpt.file, "\n Number of pollutants ...... %d", Nobjects[POLLUT]); + fprintf(Frpt.file, "\n Number of land uses ....... %d", Nobjects[LANDUSE]); + + if ( Nobjects[POLLUT] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("*****************"); + WRITE("Pollutant Summary"); + WRITE("*****************"); + fprintf(Frpt.file, + "\n Ppt. GW Kdecay"); + fprintf(Frpt.file, + "\n Name Units Concen. Concen. 1/days CoPollutant"); + fprintf(Frpt.file, + "\n -----------------------------------------------------------------------"); + for (i = 0; i < Nobjects[POLLUT]; i++) + { + fprintf(Frpt.file, "\n %-20s %5s%10.2f%10.2f%10.2f", Pollut[i].ID, + QualUnitsWords[Pollut[i].units], Pollut[i].pptConcen, + Pollut[i].gwConcen, Pollut[i].kDecay*SECperDAY); + if ( Pollut[i].coPollut >= 0 ) + fprintf(Frpt.file, " %-s (%.2f)", + Pollut[Pollut[i].coPollut].ID, Pollut[i].coFraction); + } + } + + if ( Nobjects[LANDUSE] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("***************"); + WRITE("Landuse Summary"); + WRITE("***************"); + fprintf(Frpt.file, + "\n Sweeping Maximum Last"); + fprintf(Frpt.file, + "\n Name Interval Removal Swept"); + fprintf(Frpt.file, + "\n ---------------------------------------------------"); + for (i=0; i 0 ) + { + WRITE(""); + WRITE(""); + WRITE("****************"); + WRITE("Raingage Summary"); + WRITE("****************"); + fprintf(Frpt.file, +"\n Data Recording"); + fprintf(Frpt.file, +"\n Name Data Source Type Interval "); + fprintf(Frpt.file, +"\n ------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[GAGE]; i++) + { + if ( Gage[i].tSeries >= 0 ) + { + fprintf(Frpt.file, "\n %-20s %-30s ", + Gage[i].ID, Tseries[Gage[i].tSeries].ID); + fprintf(Frpt.file, "%-10s %3d min.", + RainTypeWords[Gage[i].rainType], + (Gage[i].rainInterval)/60); + } + else fprintf(Frpt.file, "\n %-20s %-30s", + Gage[i].ID, Gage[i].fname); + } + } + + if ( Nobjects[SUBCATCH] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("********************"); + WRITE("Subcatchment Summary"); + WRITE("********************"); + fprintf(Frpt.file, +"\n Name Area Width %%Imperv %%Slope Rain Gage Outlet "); + fprintf(Frpt.file, +"\n -----------------------------------------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[SUBCATCH]; i++) + { + fprintf(Frpt.file,"\n %-20s %10.2f%10.2f%10.2f%10.4f %-20s ", + Subcatch[i].ID, Subcatch[i].area*UCF(LANDAREA), + Subcatch[i].width*UCF(LENGTH), Subcatch[i].fracImperv*100.0, + Subcatch[i].slope*100.0, Gage[Subcatch[i].gage].ID); + if ( Subcatch[i].outNode >= 0 ) + { + fprintf(Frpt.file, "%-20s", Node[Subcatch[i].outNode].ID); + } + else if ( Subcatch[i].outSubcatch >= 0 ) + { + fprintf(Frpt.file, "%-20s", Subcatch[Subcatch[i].outSubcatch].ID); + } + if ( Subcatch[i].lidArea ) lidCount++; + } + } + if ( lidCount > 0 ) lid_writeSummary(); + + if ( Nobjects[NODE] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("************"); + WRITE("Node Summary"); + WRITE("************"); + fprintf(Frpt.file, +"\n Invert Max. Ponded External"); + fprintf(Frpt.file, +"\n Name Type Elev. Depth Area Inflow "); + fprintf(Frpt.file, +"\n -------------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[NODE]; i++) + { + fprintf(Frpt.file, "\n %-20s %-16s%10.2f%10.2f%10.1f", Node[i].ID, + NodeTypeWords[Node[i].type-JUNCTION], + Node[i].invertElev*UCF(LENGTH), + Node[i].fullDepth*UCF(LENGTH), + Node[i].pondedArea*UCF(LENGTH)*UCF(LENGTH)); + if ( Node[i].extInflow || Node[i].dwfInflow || Node[i].rdiiInflow ) + { + fprintf(Frpt.file, " Yes"); + } + } + } + + if ( Nobjects[LINK] > 0 ) + { + WRITE(""); + WRITE(""); + WRITE("************"); + WRITE("Link Summary"); + WRITE("************"); + fprintf(Frpt.file, +"\n Name From Node To Node Type Length %%Slope Roughness"); + fprintf(Frpt.file, +"\n ---------------------------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[LINK]; i++) + { + // --- list end nodes in their original orientation + if ( Link[i].direction == 1 ) + fprintf(Frpt.file, "\n %-16s %-16s %-16s ", + Link[i].ID, Node[Link[i].node1].ID, Node[Link[i].node2].ID); + else + fprintf(Frpt.file, "\n %-16s %-16s %-16s ", + Link[i].ID, Node[Link[i].node2].ID, Node[Link[i].node1].ID); + + // --- list link type + if ( Link[i].type == PUMP ) + { + k = Link[i].subIndex; + fprintf(Frpt.file, "%-5s PUMP ", + PumpTypeWords[Pump[k].type]); + } + else fprintf(Frpt.file, "%-12s", + LinkTypeWords[Link[i].type-CONDUIT]); + + // --- list length, slope and roughness for conduit links + if (Link[i].type == CONDUIT) + { + k = Link[i].subIndex; + fprintf(Frpt.file, "%10.1f%10.4f%10.4f", + Conduit[k].length*UCF(LENGTH), + Conduit[k].slope*100.0*Link[i].direction, + Conduit[k].roughness); + } + } + + WRITE(""); + WRITE(""); + WRITE("*********************"); + WRITE("Cross Section Summary"); + WRITE("*********************"); + fprintf(Frpt.file, +"\n Full Full Hyd. Max. No. of Full"); + fprintf(Frpt.file, +"\n Conduit Shape Depth Area Rad. Width Barrels Flow"); + fprintf(Frpt.file, +"\n ---------------------------------------------------------------------------------------"); + for (i = 0; i < Nobjects[LINK]; i++) + { + if (Link[i].type == CONDUIT) + { + k = Link[i].subIndex; + fprintf(Frpt.file, "\n %-16s ", Link[i].ID); + if ( Link[i].xsect.type == CUSTOM ) + fprintf(Frpt.file, "%-16s ", Curve[Link[i].xsect.transect].ID); + else if ( Link[i].xsect.type == IRREGULAR ) + fprintf(Frpt.file, "%-16s ", + Transect[Link[i].xsect.transect].ID); + else if ( Link[i].xsect.type == STREET_XSECT ) + fprintf(Frpt.file, "%-16s ", + Street[Link[i].xsect.transect].ID); + else fprintf(Frpt.file, "%-16s ", + XsectTypeWords[Link[i].xsect.type]); + fprintf(Frpt.file, "%8.2f %8.2f %8.2f %8.2f %3d %8.2f", + Link[i].xsect.yFull*UCF(LENGTH), + Link[i].xsect.aFull*UCF(LENGTH)*UCF(LENGTH), + Link[i].xsect.rFull*UCF(LENGTH), + Link[i].xsect.wMax*UCF(LENGTH), + Conduit[k].barrels, + Link[i].qFull*UCF(FLOW)); + } + } + } + + if (Nobjects[SHAPE] > 0) + { + WRITE(""); + WRITE(""); + WRITE("*************"); + WRITE("Shape Summary"); + WRITE("*************"); + for (i = 0; i < Nobjects[SHAPE]; i++) + { + k = Shape[i].curve; + fprintf(Frpt.file, "\n\n Shape %s", Curve[k].ID); + fprintf(Frpt.file, "\n Area: "); + for ( m = 1; m < N_SHAPE_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Shape[i].areaTbl[m]); + } + fprintf(Frpt.file, "\n Hrad: "); + for ( m = 1; m < N_SHAPE_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Shape[i].hradTbl[m]); + } + fprintf(Frpt.file, "\n Width: "); + for ( m = 1; m < N_SHAPE_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Shape[i].widthTbl[m]); + } + } + } + + if (Nobjects[TRANSECT] > 0) + { + WRITE(""); + WRITE(""); + WRITE("****************"); + WRITE("Transect Summary"); + WRITE("****************"); + for (i = 0; i < Nobjects[TRANSECT]; i++) + { + fprintf(Frpt.file, "\n\n Transect %s", Transect[i].ID); + fprintf(Frpt.file, "\n Area: "); + for ( m = 1; m < N_TRANSECT_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Transect[i].areaTbl[m]); + } + fprintf(Frpt.file, "\n Hrad: "); + for ( m = 1; m < N_TRANSECT_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Transect[i].hradTbl[m]); + } + fprintf(Frpt.file, "\n Width: "); + for ( m = 1; m < N_TRANSECT_TBL; m++) + { + if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); + fprintf(Frpt.file, "%10.4f ", Transect[i].widthTbl[m]); + } + } + } + + if (Nobjects[STREET] > 0) + { + WRITE(""); + WRITE(""); + WRITE("**************"); + WRITE("Street Summary"); + WRITE("**************"); + for (i = 0; i < Nobjects[STREET]; i++) + { + fprintf(Frpt.file, "\n\n Street %s", Street[i].ID); + fprintf(Frpt.file, "\n Area: "); + for (m = 1; m < Street[i].transect.nTbl; m++) + { + if (m % 5 == 1) fprintf(Frpt.file, "\n "); + fprintf(Frpt.file, "%10.4f ", Street[i].transect.areaTbl[m]); + } + fprintf(Frpt.file, "\n Hrad: "); + for (m = 1; m < Street[i].transect.nTbl; m++) + { + if (m % 5 == 1) fprintf(Frpt.file, "\n "); + fprintf(Frpt.file, "%10.4f ", Street[i].transect.hradTbl[m]); + } + fprintf(Frpt.file, "\n Width: "); + for (m = 1; m < Street[i].transect.nTbl; m++) + { + if (m % 5 == 1) fprintf(Frpt.file, "\n "); + fprintf(Frpt.file, "%10.4f ", Street[i].transect.widthTbl[m]); + } + } + } + WRITE(""); +} diff --git a/src/keywords.c b/src/keywords.c new file mode 100644 index 000000000..5f4b4e443 --- /dev/null +++ b/src/keywords.c @@ -0,0 +1,172 @@ +//----------------------------------------------------------------------------- +// keywords.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 06/01/22 (Build 5.2.1) +// Author: L. Rossman +// +// Exportable keyword dictionary +// +// NOTE: the keywords in each list must appear in same order used +// by its complementary enumerated variable in enums.h and +// must be terminated by NULL. The actual text of each keyword +// is defined in text.h. +// +// Update History +// ============== +// Build 5.1.007: +// - Keywords for Ignore RDII option and groundwater flow equation +// and climate adjustment input sections added. +// Build 5.1.008: +// - Keyword arrays placed in alphabetical order for better readability. +// - Keywords added for Minimum Routing Step and Number of Threads options. +// Build 5.1.010: +// - New Modified Green Ampt keyword added to InfilModelWords. +// - New Roadway weir keyword added to WeirTypeWords. +// Build 5.1.011: +// - New section keyword for [EVENTS] added. +// Build 5.1.013: +// - New option keywords w_SURCHARGE_METHOD, w_RULE_STEP, w_AVERAGES +// and w_WEIR added. +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for variable speed pumps. +// - Support added for analytical storage shapes. +// - Support added for RptFlags.disabled option. +// Build 5.2.1: +// - Adds NONE to the list of NormalFlowWords. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include // need this to define NULL +#include "text.h" + +char* BuildupTypeWords[] = { w_NONE, w_POW, w_EXP, w_SAT, w_EXT, NULL}; +char* CurveTypeWords[] = { w_STORAGE, w_DIVERSION, w_TIDAL, w_RATING, + w_CONTROLS, w_SHAPE, w_WEIR, + w_PUMP1, w_PUMP2, w_PUMP3, w_PUMP4, + w_PUMP5, NULL}; +char* DividerTypeWords[] = { w_CUTOFF, w_TABULAR, w_WEIR, w_OVERFLOW, NULL}; +char* EvapTypeWords[] = { w_CONSTANT, w_MONTHLY, w_TIMESERIES, + w_TEMPERATURE, w_FILE, w_RECOVERY, + w_DRYONLY, NULL}; +char* FileTypeWords[] = { w_RAINFALL, w_RUNOFF, w_HOTSTART, w_RDII, + w_INFLOWS, w_OUTFLOWS, NULL}; +char* FileModeWords[] = { w_NO, w_SCRATCH, w_USE, w_SAVE, NULL}; +char* FlowUnitWords[] = { w_CFS, w_GPM, w_MGD, w_CMS, w_LPS, w_MLD, NULL}; +char* ForceMainEqnWords[] = { w_H_W, w_D_W, NULL}; +char* GageDataWords[] = { w_TIMESERIES, w_FILE, NULL}; +char* InfilModelWords[] = { w_HORTON, w_MOD_HORTON, w_GREEN_AMPT, + w_MOD_GREEN_AMPT, w_CURVE_NUMEBR, NULL}; +char* InertDampingWords[] = { w_NONE, w_PARTIAL, w_FULL, NULL}; +char* LinkOffsetWords[] = { w_DEPTH, w_ELEVATION, NULL}; +char* LinkTypeWords[] = { w_CONDUIT, w_PUMP, w_ORIFICE, + w_WEIR, w_OUTLET }; +char* LoadUnitsWords[] = { w_LBS, w_KG, w_LOGN }; +char* NodeTypeWords[] = { w_JUNCTION, w_OUTFALL, + w_STORAGE, w_DIVIDER }; +char* NoneAllWords[] = { w_NONE, w_ALL, NULL}; +char* NormalFlowWords[] = { w_SLOPE, w_FROUDE, w_BOTH, w_NONE, NULL}; +char* NormalizerWords[] = { w_PER_AREA, w_PER_CURB, NULL}; +char* NoYesWords[] = { w_NO, w_YES, NULL}; +char* OffOnWords[] = { w_OFF, w_ON, NULL}; +char* OldRouteModelWords[] = { w_NONE, w_NF, w_KW, w_EKW, w_DW, NULL}; +char* OptionWords[] = { w_FLOW_UNITS, w_INFIL_MODEL, + w_ROUTE_MODEL, w_START_DATE, + w_START_TIME, w_END_DATE, + w_END_TIME, w_REPORT_START_DATE, + w_REPORT_START_TIME, w_SWEEP_START, + w_SWEEP_END, w_START_DRY_DAYS, + w_WET_STEP, w_DRY_STEP, + w_ROUTE_STEP, w_RULE_STEP, + w_REPORT_STEP, + w_ALLOW_PONDING, w_INERT_DAMPING, + w_SLOPE_WEIGHTING, w_VARIABLE_STEP, + w_NORMAL_FLOW_LTD, w_LENGTHENING_STEP, + w_MIN_SURFAREA, w_COMPATIBILITY, + w_SKIP_STEADY_STATE, w_TEMPDIR, + w_IGNORE_RAINFALL, w_FORCE_MAIN_EQN, + w_LINK_OFFSETS, w_MIN_SLOPE, + w_IGNORE_SNOWMELT, w_IGNORE_GWATER, + w_IGNORE_ROUTING, w_IGNORE_QUALITY, + w_MAX_TRIALS, w_HEAD_TOL, + w_SYS_FLOW_TOL, w_LAT_FLOW_TOL, + w_IGNORE_RDII, w_MIN_ROUTE_STEP, + w_NUM_THREADS, w_SURCHARGE_METHOD, + NULL }; +char* OrificeTypeWords[] = { w_SIDE, w_BOTTOM, NULL}; +char* OutfallTypeWords[] = { w_FREE, w_NORMAL, w_FIXED, w_TIDAL, + w_TIMESERIES, NULL}; +char* PatternTypeWords[] = { w_MONTHLY, w_DAILY, w_HOURLY, w_WEEKEND, NULL}; +char* PondingUnitsWords[] = { w_PONDED_FEET, w_PONDED_METERS }; +char* ProcessVarWords[] = { w_HRT, w_DT, w_FLOW, w_DEPTH, w_AREA, NULL}; +char* PumpTypeWords[] = { w_TYPE1, w_TYPE2, w_TYPE3, w_TYPE4, w_TYPE5, w_IDEAL }; +char* QualUnitsWords[] = { w_MGperL, w_UGperL, w_COUNTperL, NULL}; +char* RainTypeWords[] = { w_INTENSITY, w_VOLUME, w_CUMULATIVE, NULL}; +char* RainUnitsWords[] = { w_INCHES, w_MMETER, NULL}; +char* RelationWords[] = { w_TABULAR, w_FUNCTIONAL, + w_CYLINDRICAL, w_CONICAL, w_PARABOLIC, + w_PYRAMIDAL, NULL}; +char* ReportWords[] = { w_DISABLED, w_INPUT, w_SUBCATCH, w_NODE, w_LINK, + w_CONTINUITY, w_FLOWSTATS,w_CONTROLS, + w_AVERAGES, w_NODESTATS, NULL}; +char* RouteModelWords[] = { w_NONE, w_STEADY, w_KINWAVE, w_XKINWAVE, + w_DYNWAVE, NULL}; +char* RuleKeyWords[] = { w_RULE, w_IF, w_AND, w_OR, w_THEN, w_ELSE, + w_PRIORITY, NULL}; +char* SectWords[] = { ws_TITLE, ws_OPTION, + ws_FILE, ws_RAINGAGE, + ws_TEMP, ws_EVAP, + ws_SUBCATCH, ws_SUBAREA, + ws_INFIL, ws_AQUIFER, + ws_GROUNDWATER, ws_SNOWMELT, + ws_JUNCTION, ws_OUTFALL, + ws_STORAGE, ws_DIVIDER, + ws_CONDUIT, ws_PUMP, + ws_ORIFICE, ws_WEIR, + ws_OUTLET, ws_XSECTION, + ws_TRANSECT, ws_LOSS, + ws_CONTROL, ws_POLLUTANT, + ws_LANDUSE, ws_BUILDUP, + ws_WASHOFF, ws_COVERAGE, + ws_INFLOW, ws_DWF, + ws_PATTERN, ws_RDII, + ws_UNITHYD, ws_LOADING, + ws_TREATMENT, ws_CURVE, + ws_TIMESERIES, ws_REPORT, + ws_COORDINATE, ws_VERTICES, + ws_POLYGON, ws_LABEL, + ws_SYMBOL, ws_BACKDROP, + ws_TAG, ws_PROFILE, + ws_MAP, ws_LID_CONTROL, + ws_LID_USAGE, ws_GWF, + ws_ADJUST, ws_EVENT, + ws_STREET, ws_INLET_USAGE, + ws_INLET, NULL}; +char* SnowmeltWords[] = { w_PLOWABLE, w_IMPERV, w_PERV, w_REMOVAL, NULL}; +char* SurchargeWords[] = { w_EXTRAN, w_SLOT, NULL}; +char* TempKeyWords[] = { w_TIMESERIES, w_FILE, w_WINDSPEED, w_SNOWMELT, + w_ADC, NULL}; +char* TransectKeyWords[] = { w_NC, w_X1, w_GR, NULL}; +char* TreatTypeWords[] = { w_REMOVAL, w_CONCEN, NULL}; +char* UHTypeWords[] = { w_SHORT, w_MEDIUM, w_LONG, NULL}; +char* VolUnitsWords[] = { w_MGAL, w_MLTRS }; +char* VolUnitsWords2[] = { w_GAL, w_LTR }; +char* WashoffTypeWords[] = { w_NONE, w_EXP, w_RC, w_EMC, NULL}; +char* WeirTypeWords[] = { w_TRANSVERSE, w_SIDEFLOW, w_VNOTCH, + w_TRAPEZOIDAL, w_ROADWAY, NULL}; +char* XsectTypeWords[] = { w_DUMMY, w_CIRCULAR, + w_FILLED_CIRCULAR, w_RECT_CLOSED, + w_RECT_OPEN, w_TRAPEZOIDAL, + w_TRIANGULAR, w_PARABOLIC, + w_POWERFUNC, w_RECT_TRIANG, + w_RECT_ROUND, w_MOD_BASKET, + w_HORIZELLIPSE, w_VERTELLIPSE, + w_ARCH, w_EGGSHAPED, + w_HORSESHOE, w_GOTHIC, + w_CATENARY, w_SEMIELLIPTICAL, + w_BASKETHANDLE, w_SEMICIRCULAR, + w_IRREGULAR, w_CUSTOM, + w_FORCE_MAIN, w_STREET, + NULL}; diff --git a/src/keywords.h b/src/keywords.h new file mode 100644 index 000000000..e330c59bc --- /dev/null +++ b/src/keywords.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// keywords.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Exportable keyword dictionary +// +// Update History +// ============== +// Build 5.1.008: +// - Keyword arrays listed in alphabetical order. +// Build 5.1.013: +// - New keyword array defined for surcharge method. +//----------------------------------------------------------------------------- + +#ifndef KEYWORDS_H +#define KEYWORDS_H + + +extern char* BuildupTypeWords[]; +extern char* CurveTypeWords[]; +extern char* DividerTypeWords[]; +extern char* DynWaveMethodWords[]; +extern char* EvapTypeWords[]; +extern char* FileModeWords[]; +extern char* FileTypeWords[]; +extern char* FlowUnitWords[]; +extern char* ForceMainEqnWords[]; +extern char* GageDataWords[]; +extern char* InertDampingWords[]; +extern char* InfilModelWords[]; +extern char* LinkOffsetWords[]; +extern char* LinkTypeWords[]; +extern char* LoadUnitsWords[]; +extern char* NodeTypeWords[]; +extern char* NoneAllWords[]; +extern char* NormalFlowWords[]; +extern char* NormalizerWords[]; +extern char* NoYesWords[]; +extern char* OldRouteModelWords[]; +extern char* OffOnWords[]; +extern char* OptionWords[]; +extern char* OrificeTypeWords[]; +extern char* OutfallTypeWords[]; +extern char* PatternTypeWords[]; +extern char* PondingUnitsWords[]; +extern char* ProcessVarWords[]; +extern char* PumpTypeWords[]; +extern char* QualUnitsWords[]; +extern char* RainTypeWords[]; +extern char* RainUnitsWords[]; +extern char* ReportWords[]; +extern char* RelationWords[]; +extern char* RouteModelWords[]; +extern char* RuleKeyWords[]; +extern char* SectWords[]; +extern char* SnowmeltWords[]; +extern char* SurchargeWords[]; +extern char* TempKeyWords[]; +extern char* TransectKeyWords[]; +extern char* TreatTypeWords[]; +extern char* UHTypeWords[]; +extern char* VolUnitsWords[]; +extern char* VolUnitsWords2[]; +extern char* WashoffTypeWords[]; +extern char* WeirTypeWords[]; +extern char* XsectTypeWords[]; + + +#endif //KEYWORDS_H diff --git a/src/kinwave.c b/src/kinwave.c new file mode 100644 index 000000000..b137e4ce9 --- /dev/null +++ b/src/kinwave.c @@ -0,0 +1,272 @@ +//----------------------------------------------------------------------------- +// kinwave.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// M. Tryby (EPA) +// +// Kinematic wave flow routing functions. +// +// Update History +// ============== +// Build 5.1.008: +// - Conduit inflow passed to function that computes conduit losses. +// Build 5.1.014: +// - Arguments to function link_getLossRate changed. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "headers.h" +#include "findroot.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double WX = 0.6; // distance weighting +static const double WT = 0.6; // time weighting +static const double EPSIL = 0.001; // convergence criterion + +//----------------------------------------------------------------------------- +// Shared variables +//----------------------------------------------------------------------------- +static double Beta1; +static double C1; +static double C2; +static double Afull; +static double Qfull; +static TXsect* pXsect; + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// kinwave_execute (called by flowrout_execute) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static int solveContinuity(double qin, double ain, double* aout); +static void evalContinuity(double a, double* f, double* df, void* p); + +//============================================================================= + +int kinwave_execute(int j, double* qinflow, double* qoutflow, double tStep) +// +// Input: j = link index +// qinflow = inflow at current time (cfs) +// tStep = time step (sec) +// Output: qoutflow = outflow at current time (cfs), +// returns number of iterations used +// Purpose: finds outflow over time step tStep given flow entering a +// conduit using Kinematic Wave flow routing. +// +// +// ^ q3 +// t | +// | qin, ain |-------------------| qout, aout +// | | Flow ---> | +// |----> x q1, a1 |-------------------| q2, a2 +// +// +{ + int k; + int result = 1; + double dxdt, dq; + double ain, aout; + double qin, qout; + double a1, a2, q1, q2, q3; + + // --- no routing for non-conduit link + (*qoutflow) = (*qinflow); + if ( Link[j].type != CONDUIT ) return result; + + // --- no routing for dummy xsection + if ( Link[j].xsect.type == DUMMY ) return result; + + // --- assign module-level variables + pXsect = &Link[j].xsect; + Qfull = Link[j].qFull; + Afull = Link[j].xsect.aFull; + k = Link[j].subIndex; + Beta1 = Conduit[k].beta / Qfull; + + // --- normalize previous flows + q1 = Conduit[k].q1 / Qfull; + q2 = Conduit[k].q2 / Qfull; + + // --- normalize inflow + qin = (*qinflow) / Conduit[k].barrels / Qfull; + + // --- compute evaporation and infiltration loss rate + q3 = link_getLossRate(j, qin*Qfull) / Qfull; + + // --- normalize previous areas + a1 = Conduit[k].a1 / Afull; + a2 = Conduit[k].a2 / Afull; + + // --- use full area when inlet flow >= full flow + if ( qin >= 1.0 ) ain = 1.0; + + // --- get normalized inlet area corresponding to inlet flow + else ain = xsect_getAofS(pXsect, qin/Beta1) / Afull; + + // --- check for no flow + if ( qin <= TINY && q2 <= TINY ) + { + qout = 0.0; + aout = 0.0; + } + + // --- otherwise solve finite difference form of continuity eqn. + else + { + // --- compute constant factors + dxdt = link_getLength(j) / tStep * Afull / Qfull; + dq = q2 - q1; + C1 = dxdt * WT / WX; + C2 = (1.0 - WT) * (ain - a1); + C2 = C2 - WT * a2; + C2 = C2 * dxdt / WX; + C2 = C2 + (1.0 - WX) / WX * dq - qin; + C2 = C2 + q3 / WX; + + // --- starting guess for aout is value from previous time step + aout = a2; + + // --- solve continuity equation for aout + result = solveContinuity(qin, ain, &aout); + + // --- report error if continuity eqn. not solved + if ( result == -1 ) + { + report_writeErrorMsg(ERR_KINWAVE, Link[j].ID); + return 1; + } + if ( result <= 0 ) result = 1; + + // --- compute normalized outlet flow from outlet area + qout = Beta1 * xsect_getSofA(pXsect, aout*Afull); + if ( qin > 1.0 ) qin = 1.0; + } + + // --- save new flows and areas + Conduit[k].q1 = qin * Qfull; + Conduit[k].a1 = ain * Afull; + Conduit[k].q2 = qout * Qfull; + Conduit[k].a2 = aout * Afull; + Conduit[k].fullState = + link_getFullState(Conduit[k].a1, Conduit[k].a2, Afull); + (*qinflow) = Conduit[k].q1 * Conduit[k].barrels; + (*qoutflow) = Conduit[k].q2 * Conduit[k].barrels; + return result; +} + +//============================================================================= + +int solveContinuity(double qin, double ain, double* aout) +// +// Input: qin = upstream normalized flow +// ain = upstream normalized area +// aout = downstream normalized area +// Output: new value for aout; returns an error code +// Purpose: solves continuity equation f(a) = Beta1*S(a) + C1*a + C2 = 0 +// for 'a' using the Newton-Raphson root finder function. +// Return code has the following meanings: +// >= 0 number of function evaluations used +// -1 Newton function failed +// -2 flow always above max. flow +// -3 flow always below zero +// +// Note: pXsect (pointer to conduit's cross-section), and constants Beta1, +// C1, and C2 are module-level shared variables assigned values +// in kinwave_execute(). +// +{ + int n; // # evaluations or error code + double aLo, aHi, aTmp; // lower/upper bounds on a + double fLo, fHi; // lower/upper bounds on f + double tol = EPSIL; // absolute convergence tol. + + // --- first determine bounds on 'a' so that f(a) passes through 0. + + // --- set upper bound to area at full flow + aHi = 1.0; + fHi = 1.0 + C1 + C2; + + // --- try setting lower bound to area where section factor is maximum + aLo = xsect_getAmax(pXsect) / Afull; + if ( aLo < aHi ) + { + fLo = ( Beta1 * pXsect->sMax ) + (C1 * aLo) + C2; + } + else fLo = fHi; + + // --- if fLo and fHi have same sign then set lower bound to 0 + if ( fHi*fLo > 0.0 ) + { + aHi = aLo; + fHi = fLo; + aLo = 0.0; + fLo = C2; + } + + // --- proceed with search for root if fLo and fHi have different signs + if ( fHi*fLo <= 0.0 ) + { + // --- start search at midpoint of lower/upper bounds + // if initial value outside of these bounds + if ( *aout < aLo || *aout > aHi ) *aout = 0.5*(aLo + aHi); + + // --- if fLo > fHi then switch aLo and aHi + if ( fLo > fHi ) + { + aTmp = aLo; + aLo = aHi; + aHi = aTmp; + } + + // --- call the Newton root finder method passing it the + // evalContinuity function to evaluate the function + // and its derivatives + n = findroot_Newton(aLo, aHi, aout, tol, evalContinuity, NULL); + + // --- check if root finder succeeded + if ( n <= 0 ) n = -1; + } + + // --- if lower/upper bound functions both negative then use full flow + else if ( fLo < 0.0 ) + { + if ( qin > 1.0 ) *aout = ain; + else *aout = 1.0; + n = -2; + } + + // --- if lower/upper bound functions both positive then use no flow + else if ( fLo > 0 ) + { + *aout = 0.0; + n = -3; + } + else n = -1; + return n; +} + +//============================================================================= + +void evalContinuity(double a, double* f, double* df, void* p) +// +// Input: a = outlet normalized area +// Output: f = value of continuity eqn. +// df = derivative of continuity eqn. +// Purpose: computes value of continuity equation (f) and its derivative (df) +// w.r.t. normalized area for link with normalized outlet area 'a'. +// +{ + *f = (Beta1 * xsect_getSofA(pXsect, a*Afull)) + (C1 * a) + C2; + *df = (Beta1 * Afull * xsect_getdSdA(pXsect, a*Afull)) + C1; +} + +//============================================================================= diff --git a/src/landuse.c b/src/landuse.c new file mode 100644 index 000000000..4c8402790 --- /dev/null +++ b/src/landuse.c @@ -0,0 +1,723 @@ +//----------------------------------------------------------------------------- +// landuse.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Pollutant buildup and washoff functions. +// +// Update History +// ============== +// Build 5.1.008: +// - landuse_getWashoffMass() re-named to landuse_getWashoffQual() and +// modified to return concentration instead of mass load. +// - landuse_getRunoffLoad() re-named to landuse_getWashoffLoad() and +// modified to work with landuse_getWashoffQual(). +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include "headers.h" + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// landuse_readParams (called by parseLine in input.c) +// landuse_readPollutParams (called by parseLine in input.c) +// landuse_readBuildupParams (called by parseLine in input.c) +// landuse_readWashoffParams (called by parseLine in input.c) + +// landuse_getInitBuildup (called by subcatch_initState) +// landuse_getBuildup (called by surfqual_getBuildup) +// landuse_getWashoffLoad (called by surfqual_getWashoff) +// landuse_getCoPollutLoad (called by surfqual_getwashoff)); +// landuse_getAvgBMPEffic (called by updatePondedQual in surfqual.c) + +//----------------------------------------------------------------------------- +// Function declarations +//----------------------------------------------------------------------------- +static double landuse_getBuildupDays(int landuse, int pollut, double buildup); +static double landuse_getBuildupMass(int landuse, int pollut, double days); +static double landuse_getWashoffQual(int landuse, int pollut, double buildup, + double runoff, double area); +static double landuse_getExternalBuildup(int i, int p, double buildup, + double tStep); + +//============================================================================= + +int landuse_readParams(int j, char* tok[], int ntoks) +// +// Input: j = land use index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads landuse parameters from a tokenized line of input. +// +// Data format is: +// landuseID (sweepInterval sweepRemoval sweepDays0) +// +{ + char *id; + if ( ntoks < 1 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LANDUSE, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + Landuse[j].ID = id; + if ( ntoks > 1 ) + { + if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); + if ( ! getDouble(tok[1], &Landuse[j].sweepInterval) ) + return error_setInpError(ERR_NUMBER, tok[1]); + if ( ! getDouble(tok[2], &Landuse[j].sweepRemoval) ) + return error_setInpError(ERR_NUMBER, tok[2]); + if ( ! getDouble(tok[3], &Landuse[j].sweepDays0) ) + return error_setInpError(ERR_NUMBER, tok[3]); + } + else + { + Landuse[j].sweepInterval = 0.0; + Landuse[j].sweepRemoval = 0.0; + Landuse[j].sweepDays0 = 0.0; + } + if ( Landuse[j].sweepRemoval < 0.0 + || Landuse[j].sweepRemoval > 1.0 ) + return error_setInpError(ERR_NUMBER, tok[2]); + return 0; +} + +//============================================================================= + +int landuse_readPollutParams(int j, char* tok[], int ntoks) +// +// Input: j = pollutant index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads pollutant parameters from a tokenized line of input. +// +// Data format is: +// ID Units cRain cGW cRDII kDecay (snowOnly coPollut coFrac cDWF cInit) +// +{ + int i, k, coPollut, snowFlag; + double x[4], coFrac, cDWF, cInit; + char *id; + + // --- extract pollutant name & units + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(POLLUT, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + k = findmatch(tok[1], QualUnitsWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- extract concen. in rain, gwater, & I&I + for ( i = 2; i <= 4; i++ ) + { + if ( ! getDouble(tok[i], &x[i-2]) || x[i-2] < 0.0 ) + { + return error_setInpError(ERR_NUMBER, tok[i]); + } + } + + // --- extract decay coeff. (which can be negative for growth) + if ( ! getDouble(tok[5], &x[3]) ) + { + return error_setInpError(ERR_NUMBER, tok[5]); + } + + // --- set defaults for snow only flag & co-pollut. parameters + snowFlag = 0; + coPollut = -1; + coFrac = 0.0; + cDWF = 0.0; + cInit = 0.0; + + // --- check for snow only flag + if ( ntoks >= 7 ) + { + snowFlag = findmatch(tok[6], NoYesWords); + if ( snowFlag < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); + } + + // --- check for co-pollutant + if ( ntoks >= 9 ) + { + if ( !strcomp(tok[7], "*") ) + { + coPollut = project_findObject(POLLUT, tok[7]); + if ( coPollut < 0 ) return error_setInpError(ERR_NAME, tok[7]); + if ( ! getDouble(tok[8], &coFrac) || coFrac < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[8]); + } + } + + // --- check for DWF concen. + if ( ntoks >= 10 ) + { + if ( ! getDouble(tok[9], &cDWF) || cDWF < 0.0) + return error_setInpError(ERR_NUMBER, tok[9]); + } + + // --- check for initial concen. + if ( ntoks >= 11 ) + { + if ( ! getDouble(tok[10], &cInit) || cInit < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[9]); + } + + // --- save values for pollutant object + Pollut[j].ID = id; + Pollut[j].units = k; + if ( Pollut[j].units == MG ) Pollut[j].mcf = UCF(MASS); + else if ( Pollut[j].units == UG ) Pollut[j].mcf = UCF(MASS) / 1000.0; + else Pollut[j].mcf = 1.0; + Pollut[j].pptConcen = x[0]; + Pollut[j].gwConcen = x[1]; + Pollut[j].rdiiConcen = x[2]; + Pollut[j].kDecay = x[3]/SECperDAY; + Pollut[j].snowOnly = snowFlag; + Pollut[j].coPollut = coPollut; + Pollut[j].coFraction = coFrac; + Pollut[j].dwfConcen = cDWF; + Pollut[j].initConcen = cInit; + return 0; +} + +//============================================================================= + +int landuse_readBuildupParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads pollutant buildup parameters from a tokenized line of input. +// +// Data format is: +// landuseID pollutID buildupType c1 c2 c3 normalizerType +// +{ + int i, j, k, n, p; + double c[3] = {0, 0, 0}, tmax; + + if ( ntoks < 3 ) return 0; + j = project_findObject(LANDUSE, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + p = project_findObject(POLLUT, tok[1]); + if ( p < 0 ) return error_setInpError(ERR_NAME, tok[1]); + k = findmatch(tok[2], BuildupTypeWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[2]); + Landuse[j].buildupFunc[p].funcType = k; + if ( k > NO_BUILDUP ) + { + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + if ( k != EXTERNAL_BUILDUP ) for (i=0; i<3; i++) + { + if ( ! getDouble(tok[i+3], &c[i]) || c[i] < 0.0 ) + { + return error_setInpError(ERR_NUMBER, tok[i+3]); + } + } + n = findmatch(tok[6], NormalizerWords); + if (n < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); + Landuse[j].buildupFunc[p].normalizer = n; + } + + // Find time until max. buildup (or time series for external buildup) + switch (Landuse[j].buildupFunc[p].funcType) + { + case POWER_BUILDUP: + // --- check for too small or large an exponent + if ( c[2] > 0.0 && (c[2] < 0.01 || c[2] > 10.0) ) + return error_setInpError(ERR_KEYWORD, tok[5]); + + // --- find time to reach max. buildup + // --- use zero if coeffs. are 0 + if ( c[1]*c[2] == 0.0 ) tmax = 0.0; + + // --- use 10 years if inverse power function tends to blow up + else if ( log10(c[0]) / c[2] > 3.5 ) tmax = 3650.0; + + // --- otherwise use inverse power function + else tmax = pow(c[0]/c[1], 1.0/c[2]); + break; + + case EXPON_BUILDUP: + if ( c[1] == 0.0 ) tmax = 0.0; + else tmax = -log(0.001)/c[1]; + break; + + case SATUR_BUILDUP: + tmax = 1000.0*c[2]; + break; + + case EXTERNAL_BUILDUP: + if ( !getDouble(tok[3], &c[0]) || c[0] < 0.0 ) //max. buildup + return error_setInpError(ERR_NUMBER, tok[3]); + if ( !getDouble(tok[4], &c[1]) || c[1] < 0.0 ) //scaling factor + return error_setInpError(ERR_NUMBER, tok[3]); + n = project_findObject(TSERIES, tok[5]); //time series + if ( n < 0 ) return error_setInpError(ERR_NAME, tok[4]); + Tseries[n].refersTo = EXTERNAL_BUILDUP; + c[2] = n; + tmax = 0.0; + break; + + default: + tmax = 0.0; + } + + // Assign parameters to buildup object + Landuse[j].buildupFunc[p].coeff[0] = c[0]; + Landuse[j].buildupFunc[p].coeff[1] = c[1]; + Landuse[j].buildupFunc[p].coeff[2] = c[2]; + Landuse[j].buildupFunc[p].maxDays = tmax; + return 0; +} + +//============================================================================= + +int landuse_readWashoffParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads pollutant washoff parameters from a tokenized line of input. +// +// Data format is: +// landuseID pollutID washoffType c1 c2 sweepEffic bmpRemoval +{ + int i, j, p; + int func; + double x[4]; + + if ( ntoks < 3 ) return 0; + for (i=0; i<4; i++) x[i] = 0.0; + func = NO_WASHOFF; + j = project_findObject(LANDUSE, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + p = project_findObject(POLLUT, tok[1]); + if ( p < 0 ) return error_setInpError(ERR_NAME, tok[1]); + if ( ntoks > 2 ) + { + func = findmatch(tok[2], WashoffTypeWords); + if ( func < 0 ) return error_setInpError(ERR_KEYWORD, tok[2]); + if ( func != NO_WASHOFF ) + { + if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); + if ( ! getDouble(tok[3], &x[0]) ) + return error_setInpError(ERR_NUMBER, tok[3]); + if ( ! getDouble(tok[4], &x[1]) ) + return error_setInpError(ERR_NUMBER, tok[4]); + if ( ntoks >= 6 ) + { + if ( ! getDouble(tok[5], &x[2]) ) + return error_setInpError(ERR_NUMBER, tok[5]); + } + if ( ntoks >= 7 ) + { + if ( ! getDouble(tok[6], &x[3]) ) + return error_setInpError(ERR_NUMBER, tok[6]); + } + } + } + + // --- check for valid parameter values + // x[0] = washoff coeff. + // x[1] = washoff expon. + // x[2] = sweep effic. + // x[3] = BMP effic. + if ( x[0] < 0.0 ) return error_setInpError(ERR_NUMBER, tok[3]); + if ( x[1] < -10.0 || x[1] > 10.0 ) + return error_setInpError(ERR_NUMBER, tok[4]);; + if ( x[2] < 0.0 || x[2] > 100.0 ) + return error_setInpError(ERR_NUMBER, tok[5]); + if ( x[3] < 0.0 || x[3] > 100.0 ) + return error_setInpError(ERR_NUMBER, tok[6]); + + // --- convert units of washoff coeff. + if ( func == EXPON_WASHOFF ) x[0] /= 3600.0; + if ( func == RATING_WASHOFF ) x[0] *= pow(UCF(FLOW), x[1]); + if ( func == EMC_WASHOFF ) x[0] *= LperFT3; + + // --- assign washoff parameters to washoff object + Landuse[j].washoffFunc[p].funcType = func; + Landuse[j].washoffFunc[p].coeff = x[0]; + Landuse[j].washoffFunc[p].expon = x[1]; + Landuse[j].washoffFunc[p].sweepEffic = x[2] / 100.0; + Landuse[j].washoffFunc[p].bmpEffic = x[3] / 100.0; + return 0; +} + +//============================================================================= + +void landuse_getInitBuildup(TLandFactor* landFactor, double* initBuildup, + double area, double curb) +// +// Input: landFactor = array of land use factors +// initBuildup = total initial buildup of each pollutant +// area = subcatchment's area (ft2) +// curb = subcatchment's curb length (users units) +// Output: modifies each land use factor's initial pollutant buildup +// Purpose: determines the initial buildup of each pollutant on +// each land use for a given subcatchment. +// +// Notes: Contributions from co-pollutants to initial buildup are not +// included since the co-pollutant mechanism only applies to +// washoff. +// +{ + int i, p; + double startDrySeconds; // antecedent dry period (sec) + double f; // faction of total land area + double fArea; // area of land use (ft2) + double fCurb; // curb length of land use + double buildup; // pollutant mass buildup + + // --- convert antecedent dry days into seconds + startDrySeconds = StartDryDays*SECperDAY; + + // --- examine each land use + for (i = 0; i < Nobjects[LANDUSE]; i++) + { + // --- initialize date when last swept + landFactor[i].lastSwept = StartDateTime - Landuse[i].sweepDays0; + + // --- determine area and curb length covered by land use + f = landFactor[i].fraction; + fArea = f * area * UCF(LANDAREA); + fCurb = f * curb; + + // --- determine buildup of each pollutant + for (p = 0; p < Nobjects[POLLUT]; p++) + { + // --- if an initial loading was supplied, then use it to + // find the starting buildup over the land use + buildup = 0.0; + if ( initBuildup[p] > 0.0 ) buildup = initBuildup[p] * fArea; + + // --- otherwise use the land use's buildup function to + // compute a buildup over the antecedent dry period + else buildup = landuse_getBuildup(i, p, fArea, fCurb, buildup, + startDrySeconds); + landFactor[i].buildup[p] = buildup; + } + } +} + +//============================================================================= + +double landuse_getBuildup(int i, int p, double area, double curb, double buildup, + double tStep) +// +// Input: i = land use index +// p = pollutant index +// area = land use area (ac or ha) +// curb = land use curb length (users units) +// buildup = current pollutant buildup (lbs or kg) +// tStep = time increment for buildup (sec) +// Output: returns new buildup mass (lbs or kg) +// Purpose: computes new pollutant buildup on a landuse after a time increment. +// +{ + int n; // normalizer code + double days; // accumulated days of buildup + double perUnit; // normalizer value (area or curb length) + + // --- return current buildup if no buildup function or time increment + if ( Landuse[i].buildupFunc[p].funcType == NO_BUILDUP || tStep == 0.0 ) + { + return buildup; + } + + // --- see what buildup is normalized to + n = Landuse[i].buildupFunc[p].normalizer; + perUnit = 1.0; + if ( n == PER_AREA ) perUnit = area; + if ( n == PER_CURB ) perUnit = curb; + if ( perUnit == 0.0 ) return 0.0; + + // --- buildup determined by loading time series + if ( Landuse[i].buildupFunc[p].funcType == EXTERNAL_BUILDUP ) + { + return landuse_getExternalBuildup(i, p, buildup/perUnit, tStep) * + perUnit; + } + + // --- determine equivalent days of current buildup + days = landuse_getBuildupDays(i, p, buildup/perUnit); + + // --- compute buildup after adding on time increment + days += tStep / SECperDAY; + return landuse_getBuildupMass(i, p, days) * perUnit; +} + +//============================================================================= + +double landuse_getBuildupDays(int i, int p, double buildup) +// +// Input: i = land use index +// p = pollutant index +// buildup = amount of pollutant buildup +// Output: returns number of days it takes for buildup to reach a given level +// Purpose: finds the number of days corresponding to a pollutant buildup. +// +{ + double c0 = Landuse[i].buildupFunc[p].coeff[0]; + double c1 = Landuse[i].buildupFunc[p].coeff[1]; + double c2 = Landuse[i].buildupFunc[p].coeff[2]; + + if ( buildup == 0.0 ) return 0.0; + if ( buildup >= c0 ) return Landuse[i].buildupFunc[p].maxDays; + switch (Landuse[i].buildupFunc[p].funcType) + { + case POWER_BUILDUP: + if ( c1*c2 == 0.0 ) return 0.0; + else return pow( (buildup/c1), (1.0/c2) ); + + case EXPON_BUILDUP: + if ( c0*c1 == 0.0 ) return 0.0; + else return -log(1. - buildup/c0) / c1; + + case SATUR_BUILDUP: + if ( c0 == 0.0 ) return 0.0; + else return buildup*c2 / (c0 - buildup); + + default: + return 0.0; + } +} + +//============================================================================= + +double landuse_getBuildupMass(int i, int p, double days) +// +// Input: i = land use index +// p = pollutant index +// days = time over which buildup has occurred (days) +// Output: returns mass of pollutant buildup (lbs or kg per area or curblength) +// Purpose: finds amount of buildup of pollutant on a land use. +// +{ + double b; + double c0 = Landuse[i].buildupFunc[p].coeff[0]; + double c1 = Landuse[i].buildupFunc[p].coeff[1]; + double c2 = Landuse[i].buildupFunc[p].coeff[2]; + + if ( days == 0.0 ) return 0.0; + if ( days >= Landuse[i].buildupFunc[p].maxDays ) return c0; + switch (Landuse[i].buildupFunc[p].funcType) + { + case POWER_BUILDUP: + b = c1 * pow(days, c2); + if ( b > c0 ) b = c0; + break; + + case EXPON_BUILDUP: + b = c0*(1.0 - exp(-days*c1)); + break; + + case SATUR_BUILDUP: + b = days*c0/(c2 + days); + break; + + default: b = 0.0; + } + return b; +} + +//============================================================================= + +double landuse_getAvgBmpEffic(int j, int p) +// +// Input: j = subcatchment index +// p = pollutant index +// Output: returns a BMP removal fraction for pollutant p +// Purpose: finds the overall average BMP removal achieved for pollutant p +// treated in subcatchment j. +// +{ + int i; + double r = 0.0; + for (i = 0; i < Nobjects[LANDUSE]; i++) + { + r += Subcatch[j].landFactor[i].fraction * + Landuse[i].washoffFunc[p].bmpEffic; + } + return r; +} + +//============================================================================= + +double landuse_getWashoffLoad(int i, int p, double area, + TLandFactor landFactor[], double runoff, double vOutflow) +// +// Input: i = land use index +// p = pollut. index +// area = sucatchment area (ft2) +// landFactor[] = array of land use data for subcatchment +// runoff = runoff flow generated by subcatchment (ft/sec) +// vOutflow = runoff volume leaving the subcatchment (ft3) +// Output: returns pollutant runoff load (mass) +// Purpose: computes pollutant load generated by a land use over a time step. +// +{ + double landuseArea; // area of current land use (ft2) + double buildup; // current pollutant buildup (lb or kg) + double washoffQual; // pollutant concentration in washoff (mass/ft3) + double washoffLoad; // pollutant washoff load over time step (lb or kg) + double bmpRemoval; // pollutant load removed by BMP treatment (lb or kg) + + // --- compute concen. of pollutant in washoff (mass/ft3) + buildup = landFactor[i].buildup[p]; + landuseArea = landFactor[i].fraction * area; + washoffQual = landuse_getWashoffQual(i, p, buildup, runoff, landuseArea); + + // --- compute washoff load exported (lbs or kg) from landuse + // (Pollut[].mcf converts from mg (or ug) mass units to lbs (or kg) + washoffLoad = washoffQual * vOutflow * landuseArea / area * Pollut[p].mcf; + + // --- if buildup modelled, reduce it by amount of washoff + if ( Landuse[i].buildupFunc[p].funcType != NO_BUILDUP || + buildup > washoffLoad ) + { + washoffLoad = MIN(washoffLoad, buildup); + buildup -= washoffLoad; + landFactor[i].buildup[p] = buildup; + } + + // --- otherwise add washoff to buildup mass balance totals + // so that things will balance + else + { + massbal_updateLoadingTotals(BUILDUP_LOAD, p, washoffLoad); + landFactor[i].buildup[p] = 0.0; + } + + // --- apply any BMP removal to washoff + bmpRemoval = Landuse[i].washoffFunc[p].bmpEffic * washoffLoad; + if ( bmpRemoval > 0.0 ) + { + massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, bmpRemoval); + washoffLoad -= bmpRemoval; + } + + // --- return washoff load converted back to mass (mg or ug) + return washoffLoad / Pollut[p].mcf; +} + +//============================================================================= + +double landuse_getWashoffQual(int i, int p, double buildup, double runoff, + double area) +// +// Input: i = land use index +// p = pollutant index +// buildup = current buildup over land use (lbs or kg) +// runoff = current runoff on subcatchment (ft/sec) +// area = area devoted to land use (ft2) +// Output: returns pollutant concentration in washoff (mass/ft3) +// Purpose: finds concentration of pollutant washed off a land use. +// +// Notes: "coeff" for each washoff function was previously adjusted to +// result in units of mass/sec +// +{ + double cWashoff = 0.0; + double coeff = Landuse[i].washoffFunc[p].coeff; + double expon = Landuse[i].washoffFunc[p].expon; + int func = Landuse[i].washoffFunc[p].funcType; + + // --- if no washoff function or no runoff, return 0 + if ( func == NO_WASHOFF || runoff == 0.0 ) return 0.0; + + // --- if buildup function exists but no current buildup, return 0 + if ( Landuse[i].buildupFunc[p].funcType != NO_BUILDUP && buildup == 0.0 ) + return 0.0; + + // --- Exponential Washoff function + if ( func == EXPON_WASHOFF ) + { + // --- evaluate washoff eqn. with runoff in in/hr (or mm/hr) + // and buildup converted from lbs (or kg) to concen. mass units + cWashoff = coeff * pow(runoff * UCF(RAINFALL), expon) * + buildup / Pollut[p].mcf; + cWashoff /= runoff * area; + } + + // --- Rating Curve Washoff function + else if ( func == RATING_WASHOFF ) + { + cWashoff = coeff * pow(runoff * area, expon-1.0); + } + + // --- Event Mean Concentration Washoff + else if ( func == EMC_WASHOFF ) + { + cWashoff = coeff; // coeff includes LperFT3 factor + } + return cWashoff; +} + +//============================================================================= + +double landuse_getCoPollutLoad(int p, double washoff[]) +// +// Input: p = pollutant index +// washoff = pollut. washoff rate (mass/sec) +// Output: returns washoff mass added by co-pollutant relation (mass) +// Purpose: finds washoff mass added by a co-pollutant of a given pollutant. +// +{ + int k; + double w; + + // --- check if pollutant p has a co-pollutant k + k = Pollut[p].coPollut; + if ( k >= 0 ) + { + // --- compute addition to washoff from co-pollutant + w = Pollut[p].coFraction * washoff[k]; + + // --- add washoff to buildup mass balance totals + // so that things will balance + massbal_updateLoadingTotals(BUILDUP_LOAD, p, w * Pollut[p].mcf); + return w; + } + return 0.0; +} + +//============================================================================= + +double landuse_getExternalBuildup(int i, int p, double buildup, double tStep) +// +// Input: i = landuse index +// p = pollutant index +// buildup = buildup at start of time step (mass/unit) +// tStep = time step (sec) +// Output: returns pollutant buildup at end of time interval (mass/unit) +// Purpose: finds pollutant buildup contributed by external loading over a +// given time step. +// +{ + double maxBuildup = Landuse[i].buildupFunc[p].coeff[0]; + double sf = Landuse[i].buildupFunc[p].coeff[1]; // scaling factor + int ts = (int)floor(Landuse[i].buildupFunc[p].coeff[2]); // time series index + double rate = 0.0; + + // --- no buildup increment at start of simulation + if (NewRunoffTime == 0.0) return 0.0; + + // --- get buildup rate (mass/unit/day) over the interval + if ( ts >= 0 ) + { + rate = sf * table_tseriesLookup(&Tseries[ts], + getDateTime(NewRunoffTime), FALSE); + } + + // --- compute buildup at end of time interval + buildup = buildup + rate * tStep / SECperDAY; + buildup = MIN(buildup, maxBuildup); + return buildup; +} diff --git a/src/lid.c b/src/lid.c new file mode 100644 index 000000000..7bb69dcef --- /dev/null +++ b/src/lid.c @@ -0,0 +1,2031 @@ +//----------------------------------------------------------------------------- +// lid.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// This module handles all data processing involving LID (Low Impact +// Development) practices used to treat runoff for individual subcatchments +// within a project. The actual computation of LID performance is made by +// functions within the lidproc.c module. See LidTypes below for the types +// of LIDs that can be modeled. +// +// An LID process is described by the TLidProc data structure and consists of +// size-independent design data for the different vertical layers that make +// up a specific type of LID. The collection of these LID process designs is +// stored in the LidProcs array. +// +// When a member of LidProcs is to be deployed in a particular subcatchment, +// its sizing and treatment data are stored in a TLidUnit data structure. +// The collection of all TLidUnits deployed in a subcatchment is held in a +// TLidGroup list data structure. The LidGroups array contains a TLidGroup +// list for each subcatchment in the project. +// +// During a runoff time step, each subcatchment calls the lid_getRunoff() +// function to compute flux rates and a water balance through each layer +// of each LID unit in the subcatchment. The resulting outflows (runoff, +// drain flow, evaporation and infiltration) are added to those computed +// for the non-LID portion of the subcatchment. +// +// An option exists for the detailed time series of flux rates and storage +// levels for a specific LID unit to be written to a text file named by the +// user for viewing outside of the SWMM program. +// +// Update History +// ============== +// Build 5.1.008: +// - More input error reporting added. +// - Rooftop Disconnection added to the types of LIDs. +// - LID drain flows are now tracked separately. +// - LID drain flows can now be routed to separate outlets. +// - Check added to insure LID flows not returned to nonexistent pervious area. +// Build 5.1.009: +// - Fixed bug where LID's could return outflow to non-LID area when LIDs +// make up entire subcatchment. +// Build 5.1.010: +// - Support for new Modified Green Ampt infiltration model added. +// - Imported variable HasWetLids now properly initialized. +// - Initial state of reporting (lidUnit->rptFile->wasDry) changed to +// prevent duplicate printing of first line of detailed report file. +// Build 5.1.011: +// - The top of the storage layer is no longer used as a limit for an +// underdrain offset thus allowing upturned drains to be modeled. +// - Column headings for the detailed LID report file were modified. +// Build 5.1.012: +// - Redefined initialization of wasDry for LID reporting. +// Build 5.1.013: +// - Support added for LID units treating pervious area runoff. +// - Support added for open/closed head levels and multiplier v. head +// control curve for underdrain flow. +// - Support added for unclogging permeable pavement at fixed intervals. +// - Support added for pollutant removal in underdrain flow. +// Build 5.1.014: +// - Fixed bug in creating LidProcs when there are no subcatchments. +// - Fixed bug in adding underdrain pollutant loads to mass balances. +// Build 5.1.015: +// - Support added for mutiple infiltration methods within a project. +// Build 5.2.0: +// - Covered property added to RAIN_BARREL parameters +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include "headers.h" +#include "lid.h" + +#define ERR_PAVE_LAYER " - check pavement layer parameters" +#define ERR_SOIL_LAYER " - check soil layer parameters" +#define ERR_STOR_LAYER " - check storage layer parameters" +#define ERR_SWALE_SURF " - check swale surface parameters" +#define ERR_GREEN_AMPT " - check subcatchment Green-Ampt parameters" +#define ERR_DRAIN_OFFSET " - drain offset exceeds storage height" +#define ERR_DRAIN_HEADS " - invalid drain open/closed heads" +#define ERR_SWALE_WIDTH " - invalid swale width" + +//----------------------------------------------------------------------------- +// Enumerations +//----------------------------------------------------------------------------- +enum LidLayerTypes { + SURF, // surface layer + SOIL, // soil layer + STOR, // storage layer + PAVE, // pavement layer + DRAINMAT, // drainage mat layer + DRAIN, // underdrain system + REMOVALS}; // pollutant removals + +//// Note: DRAINMAT must be placed before DRAIN so the two keywords can +/// be distinguished from one another when parsing a line of input. + +char* LidLayerWords[] = + {"SURFACE", "SOIL", "STORAGE", "PAVEMENT", "DRAINMAT", "DRAIN", + "REMOVALS", NULL}; + +char* LidTypeWords[] = + {"BC", //bio-retention cell + "RG", //rain garden + "GR", //green roof + "IT", //infiltration trench + "PP", //porous pavement + "RB", //rain barrel + "VS", //vegetative swale + "RD", //rooftop disconnection + NULL}; + +//----------------------------------------------------------------------------- +// Data Structures +//----------------------------------------------------------------------------- + +// LID List - list of LID units contained in an LID group +struct LidList +{ + TLidUnit* lidUnit; // ptr. to a LID unit + struct LidList* nextLidUnit; +}; +typedef struct LidList TLidList; + +// LID Group - collection of LID units applied to a specific subcatchment +struct LidGroup +{ + double pervArea; // amount of pervious area in group (ft2) + double flowToPerv; // total flow sent to pervious area (cfs) + double oldDrainFlow; // total drain flow in previous period (cfs) + double newDrainFlow; // total drain flow in current period (cfs) + TLidList* lidList; // list of LID units in the group +}; +typedef struct LidGroup* TLidGroup; + + +//----------------------------------------------------------------------------- +// Shared Variables +//----------------------------------------------------------------------------- +static TLidProc* LidProcs; // array of LID processes +static int LidCount; // number of LID processes +static TLidGroup* LidGroups; // array of LID process groups +static int GroupCount; // number of LID groups (subcatchments) + +static double EvapRate; // evaporation rate (ft/s) +static double NativeInfil; // native soil infil. rate (ft/s) +static double MaxNativeInfil; // native soil infil. rate limit (ft/s) + +//----------------------------------------------------------------------------- +// Imported Variables (from SUBCATCH.C) +//----------------------------------------------------------------------------- +// Volumes (ft3) for a subcatchment over a time step +extern double Vevap; // evaporation +extern double Vpevap; // pervious area evaporation +extern double Vinfil; // non-LID infiltration +extern double VlidInfil; // infiltration from LID units +extern double VlidIn; // impervious area flow to LID units +extern double VlidOut; // surface outflow from LID units +extern double VlidDrain; // drain outflow from LID units +extern double VlidReturn; // LID outflow returned to pervious area +extern char HasWetLids; // TRUE if any LIDs are wet + // (from RUNOFF.C) + +//----------------------------------------------------------------------------- +// External Functions (prototyped in lid.h) +//----------------------------------------------------------------------------- +// lid_create called by createObjects in project.c +// lid_delete called by deleteObjects in project.c +// lid_validate called by project_validate +// lid_initState called by project_init + +// lid_readProcParams called by parseLine in input.c +// lid_readGroupParams called by parseLine in input.c + +// lid_setOldGroupState called by subcatch_setOldState +// lid_setReturnQual called by findLidLoads in surfqual.c +// lid_getReturnQual called by subcatch_getRunon + +// lid_getPervArea called by subcatch_getFracPerv +// lid_getFlowToPerv called by subcatch_getRunon +// lid_getSurfaceDepth called by subcatch_getDepth +// lid_getDepthOnPavement called by sweptSurfacesDry in subcatch.c +// lid_getStoredVolume called by subcatch_getStorage +// lid_getRunon called by subcatch_getRunon +// lid_getRunoff called by subcatch_getRunoff + +// lid_addDrainRunon called by subcatch_getRunon +// lid_addDrainLoads called by surfqual_getWashoff +// lid_addDrainInflow called by addLidDrainInflows in routing.c + +// lid_writeSummary called by inputrpt_writeInput +// lid_writeWaterBalance called by statsrpt_writeReport + + +//----------------------------------------------------------------------------- +// Local Functions +//----------------------------------------------------------------------------- +static void freeLidGroup(int j); +static int readSurfaceData(int j, char* tok[], int ntoks); +static int readPavementData(int j, char* tok[], int ntoks); +static int readSoilData(int j, char* tok[], int ntoks); +static int readStorageData(int j, char* tok[], int ntoks); +static int readDrainData(int j, char* tok[], int ntoks); +static int readDrainMatData(int j, char* toks[], int ntoks); +static int readRemovalsData(int j, char* toks[], int ntoks); + +static int addLidUnit(int j, int k, int n, double x[], char* fname, + int drainSubcatch, int drainNode); +static int createLidRptFile(TLidUnit* lidUnit, char* fname); +static void initLidRptFile(char* title, char* lidID, char* subcatchID, + TLidUnit* lidUnit); +static void validateLidProc(int j); +static void validateLidGroup(int j); + +static int isLidPervious(int k); +static double getImpervAreaRunoff(int j); +static double getPervAreaRunoff(int j); +static double getSurfaceDepth(int subcatch); +static double getRainInflow(int j, TLidUnit* lidUnit); +static void findNativeInfil(int j, double tStep); + + +static void evalLidUnit(int j, TLidUnit* lidUnit, double lidArea, + double lidInflow, double tStep, double *qRunoff, + double *qDrain, double *qReturn); + +//============================================================================= + +void lid_create(int lidCount, int subcatchCount) +// +// Purpose: creates an array of LID objects. +// Input: n = number of LID processes +// Output: none +// +{ + int j; + + //... assign NULL values to LID arrays + LidProcs = NULL; + LidGroups = NULL; + LidCount = lidCount; + + //... create LID groups + GroupCount = subcatchCount; + if ( GroupCount > 0 ) + { + LidGroups = (TLidGroup *) calloc(GroupCount, sizeof(TLidGroup)); + if ( LidGroups == NULL ) + { + ErrorCode = ERR_MEMORY; + return; + } + } + + //... initialize LID groups + for (j = 0; j < GroupCount; j++) LidGroups[j] = NULL; + + //... create LID objects + if ( LidCount == 0 ) return; + LidProcs = (TLidProc *) calloc(LidCount, sizeof(TLidProc)); + if ( LidProcs == NULL ) + { + ErrorCode = ERR_MEMORY; + return; + } + + //... initialize LID objects + for (j = 0; j < LidCount; j++) + { + LidProcs[j].lidType = -1; + LidProcs[j].surface.thickness = 0.0; + LidProcs[j].surface.voidFrac = 1.0; + LidProcs[j].surface.roughness = 0.0; + LidProcs[j].surface.surfSlope = 0.0; + LidProcs[j].pavement.thickness = 0.0; + LidProcs[j].soil.thickness = 0.0; + LidProcs[j].storage.thickness = 0.0; + LidProcs[j].storage.kSat = 0.0; + LidProcs[j].drain.coeff = 0.0; + LidProcs[j].drain.offset = 0.0; + LidProcs[j].drainMat.thickness = 0.0; + LidProcs[j].drainMat.roughness = 0.0; + LidProcs[j].drainRmvl = NULL; + LidProcs[j].drainRmvl = (double *) + calloc(Nobjects[POLLUT], sizeof(double)); + if (LidProcs[j].drainRmvl == NULL) + { + ErrorCode = ERR_MEMORY; + return; + } + } +} + +//============================================================================= + +void lid_delete() +// +// Purpose: deletes all LID objects +// Input: none +// Output: none +// +{ + int j; + for (j = 0; j < GroupCount; j++) freeLidGroup(j); + FREE(LidGroups); + for (j = 0; j < LidCount; j++) FREE(LidProcs[j].drainRmvl); + FREE(LidProcs); + GroupCount = 0; + LidCount = 0; +} + +//============================================================================= + +void freeLidGroup(int j) +// +// Purpose: frees all LID units associated with a subcatchment. +// Input: j = group (or subcatchment) index +// Output: none +// +{ + TLidGroup lidGroup = LidGroups[j]; + TLidList* lidList; + TLidUnit* lidUnit; + TLidList* nextLidUnit; + + if ( lidGroup == NULL ) return; + lidList = lidGroup->lidList; + while (lidList) + { + lidUnit = lidList->lidUnit; + if ( lidUnit->rptFile ) + { + if ( lidUnit->rptFile->file ) fclose(lidUnit->rptFile->file); + free(lidUnit->rptFile); + } + nextLidUnit = lidList->nextLidUnit; + free(lidUnit); + free(lidList); + lidList = nextLidUnit; + } + free(lidGroup); + LidGroups[j] = NULL; +} + +//============================================================================= + +int lid_readProcParams(char* toks[], int ntoks) +// +// Purpose: reads LID process information from line of input data file +// Input: toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format for first line that defines a LID process is: +// LID_ID LID_Type +// +// Followed by some combination of lines below depending on LID_Type: +// LID_ID SURFACE +// LID_ID PAVEMENT +// LID_ID SOIL +// LID_ID STORAGE +// LID_ID DRAIN +// LID_ID DRAINMAT +// LID_ID REMOVALS +// +{ + int j, m; + + // --- check for minimum number of tokens + if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); + + // --- check that LID exists in database + j = project_findObject(LID, toks[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, toks[0]); + + // --- assign ID if not done yet + if ( LidProcs[j].ID == NULL ) + LidProcs[j].ID = project_findID(LID, toks[0]); + + // --- check if second token is the type of LID + m = findmatch(toks[1], LidTypeWords); + if ( m >= 0 ) + { + LidProcs[j].lidType = m; + return 0; + } + + // --- check if second token is name of LID layer + else m = findmatch(toks[1], LidLayerWords); + + // --- read input parameters for the identified layer + switch (m) + { + case SURF: return readSurfaceData(j, toks, ntoks); + case SOIL: return readSoilData(j, toks, ntoks); + case STOR: return readStorageData(j, toks, ntoks); + case PAVE: return readPavementData(j, toks, ntoks); + case DRAIN: return readDrainData(j, toks, ntoks); + case DRAINMAT: return readDrainMatData(j, toks, ntoks); + case REMOVALS: return readRemovalsData(j, toks, ntoks); + } + return error_setInpError(ERR_KEYWORD, toks[1]); +} + +//============================================================================= + +int lid_readGroupParams(char* toks[], int ntoks) +// +// Purpose: reads input data for a LID unit placed in a subcatchment. +// Input: toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of input data line is: +// Subcatch_ID LID_ID Number Area Width InitSat FromImp ToPerv +// (RptFile DrainTo FromPerv) +// where: +// Subcatch_ID = name of subcatchment +// LID_ID = name of LID process +// Number (n) = number of replicate units +// Area (x[0]) = area of each unit +// Width (x[1]) = outflow width of each unit +// InitSat (x[2]) = % that LID is initially saturated +// FromImp (x[3]) = % of impervious runoff sent to LID +// ToPerv (x[4]) = 1 if outflow goes to pervious sub-area; 0 if not +// RptFile = name of detailed results file (optional) +// DrainTo = name of subcatch/node for drain flow (optional) +// FromPerv (x[5]) = % of pervious runoff sent to LID +// +{ + int i, j, k, n; + double x[6]; + char* fname = NULL; + int drainSubcatch = -1, drainNode = -1; + + //... check for valid number of input tokens + if ( ntoks < 8 ) return error_setInpError(ERR_ITEMS, ""); + + //... find subcatchment + j = project_findObject(SUBCATCH, toks[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, toks[0]); + + //... find LID process in list of LID processes + k = project_findObject(LID, toks[1]); + if ( k < 0 ) return error_setInpError(ERR_NAME, toks[1]); + + //... get number of replicates + n = atoi(toks[2]); + if ( n < 0 ) return error_setInpError(ERR_NUMBER, toks[2]); + if ( n == 0 ) return 0; + + //... convert next 4 tokens to doubles + for (i = 3; i <= 7; i++) + { + if ( ! getDouble(toks[i], &x[i-3]) || x[i-3] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + //... check for valid percentages on tokens 5 & 6 (x[2] & x[3]) + for (i = 2; i <= 3; i++) if ( x[i] > 100.0 ) + return error_setInpError(ERR_NUMBER, toks[i+3]); + + //... read optional report file name + if ( ntoks >= 9 && strcmp(toks[8], "*") != 0 ) fname = toks[8]; + + //... read optional underdrain outlet + if ( ntoks >= 10 && strcmp(toks[9], "*") != 0 ) + { + drainSubcatch = project_findObject(SUBCATCH, toks[9]); + if ( drainSubcatch < 0 ) + { + drainNode = project_findObject(NODE, toks[9]); + if ( drainNode < 0 ) return error_setInpError(ERR_NAME, toks[9]); + } + } + + //... read percent of pervious area treated by LID unit + x[5] = 0.0; + if (ntoks >= 11) + { + if (!getDouble(toks[10], &x[5]) || x[5] < 0.0 || x[5] > 100.0) + return error_setInpError(ERR_NUMBER, toks[10]); + } + + //... create a new LID unit and add it to the subcatchment's LID group + return addLidUnit(j, k, n, x, fname, drainSubcatch, drainNode); +} + +//============================================================================= + +int addLidUnit(int j, int k, int n, double x[], char* fname, + int drainSubcatch, int drainNode) +// +// Purpose: adds an LID unit to a subcatchment's LID group. +// Input: j = subcatchment index +// k = LID control index +// n = number of replicate units +// x = LID unit's parameters +// fname = name of detailed performance report file +// drainSubcatch = index of subcatchment receiving underdrain flow +// drainNode = index of node receiving underdrain flow +// Output: returns an error code +// +{ + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... create a LID group (pointer to an LidGroup struct) + // if one doesn't already exist + lidGroup = LidGroups[j]; + if ( !lidGroup ) + { + lidGroup = (struct LidGroup *) malloc(sizeof(struct LidGroup)); + if ( !lidGroup ) return error_setInpError(ERR_MEMORY, ""); + lidGroup->lidList = NULL; + LidGroups[j] = lidGroup; + } + + //... create a new LID unit to add to the group + lidUnit = (TLidUnit *) malloc(sizeof(TLidUnit)); + if ( !lidUnit ) return error_setInpError(ERR_MEMORY, ""); + lidUnit->rptFile = NULL; + + //... add the LID unit to the group + lidList = (TLidList *) malloc(sizeof(TLidList)); + if ( !lidList ) + { + free(lidUnit); + return error_setInpError(ERR_MEMORY, ""); + } + lidList->lidUnit = lidUnit; + lidList->nextLidUnit = lidGroup->lidList; + lidGroup->lidList = lidList; + + //... assign parameter values to LID unit + lidUnit->lidIndex = k; + lidUnit->number = n; + lidUnit->area = x[0] / SQR(UCF(LENGTH)); + lidUnit->fullWidth = x[1] / UCF(LENGTH); + lidUnit->initSat = x[2] / 100.0; + lidUnit->fromImperv = x[3] / 100.0; + lidUnit->toPerv = (x[4] > 0.0); + lidUnit->fromPerv = x[5] / 100.0; + lidUnit->drainSubcatch = drainSubcatch; + lidUnit->drainNode = drainNode; + + //... open report file if it was supplied + if ( fname != NULL ) + { + if ( !createLidRptFile(lidUnit, fname) ) + return error_setInpError(ERR_RPT_FILE, fname); + } + return 0; +} + +//============================================================================= + +int createLidRptFile(TLidUnit* lidUnit, char* fname) +{ + TLidRptFile* rptFile; + + rptFile = (TLidRptFile *) malloc(sizeof(TLidRptFile)); + if ( rptFile == NULL ) return 0; + lidUnit->rptFile = rptFile; + rptFile->file = fopen(fname, "wt"); + if ( rptFile->file == NULL ) return 0; + return 1; +} + +//============================================================================= + +int readSurfaceData(int j, char* toks[], int ntoks) +// +// Purpose: reads surface layer data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID SURFACE StorageHt VegVolFrac Roughness SurfSlope SideSlope +// +{ + int i; + double x[5]; + + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 2; i < 7; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + if ( x[1] >= 1.0 ) return error_setInpError(ERR_NUMBER, toks[3]); + if ( x[0] == 0.0 ) x[1] = 0.0; + + LidProcs[j].surface.thickness = x[0] / UCF(RAINDEPTH); + LidProcs[j].surface.voidFrac = 1.0 - x[1]; + LidProcs[j].surface.roughness = x[2]; + LidProcs[j].surface.surfSlope = x[3] / 100.0; + LidProcs[j].surface.sideSlope = x[4]; + return 0; +} + +//============================================================================= + +int readPavementData(int j, char* toks[], int ntoks) +// +// Purpose: reads pavement layer data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID PAVEMENT Thickness VoidRatio FracImperv Permeability ClogFactor +// (RegenDays RegenDegree) +// +{ + int i; + double x[7]; + + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 2; i < 7; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + // ... read optional clogging regeneration properties + x[5] = 0.0; + if (ntoks > 7) + { + if (!getDouble(toks[7], &x[5]) || x[5] < 0.0) + return error_setInpError(ERR_NUMBER, toks[7]); + } + x[6] = 0.0; + if (ntoks > 8) + { + if (!getDouble(toks[8], &x[6]) || x[6] < 0.0 || x[6] > 1.0) + return error_setInpError(ERR_NUMBER, toks[8]); + } + + //... convert void ratio to void fraction + x[1] = x[1]/(x[1] + 1.0); + + LidProcs[j].pavement.thickness = x[0] / UCF(RAINDEPTH); + LidProcs[j].pavement.voidFrac = x[1]; + LidProcs[j].pavement.impervFrac = x[2]; + LidProcs[j].pavement.kSat = x[3] / UCF(RAINFALL); + LidProcs[j].pavement.clogFactor = x[4]; + LidProcs[j].pavement.regenDays = x[5]; + LidProcs[j].pavement.regenDegree = x[6]; + return 0; +} + +//============================================================================= + +int readSoilData(int j, char* toks[], int ntoks) +// +// Purpose: reads soil layer data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID SOIL Thickness Porosity FieldCap WiltPt Ksat Kslope Suction +// +{ + int i; + double x[7]; + + if ( ntoks < 9 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 2; i < 9; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + LidProcs[j].soil.thickness = x[0] / UCF(RAINDEPTH); + LidProcs[j].soil.porosity = x[1]; + LidProcs[j].soil.fieldCap = x[2]; + LidProcs[j].soil.wiltPoint = x[3]; + LidProcs[j].soil.kSat = x[4] / UCF(RAINFALL); + LidProcs[j].soil.kSlope = x[5]; + LidProcs[j].soil.suction = x[6] / UCF(RAINDEPTH); + return 0; +} + +//============================================================================= + +int readStorageData(int j, char* toks[], int ntoks) +// +// Purpose: reads drainage layer data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID STORAGE Thickness VoidRatio Ksat ClogFactor (YES/NO) +// +{ + int i; + int covered = FALSE; + double x[6]; + + //... read numerical parameters + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 2; i < 6; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + //... check if rain barrel is covered + if (ntoks > 6) + { + if (match(toks[6], w_YES)) + covered = TRUE; + } + + //... convert void ratio to void fraction + x[1] = x[1]/(x[1] + 1.0); + + //... save parameters to LID storage layer structure + LidProcs[j].storage.thickness = x[0] / UCF(RAINDEPTH); + LidProcs[j].storage.voidFrac = x[1]; + LidProcs[j].storage.kSat = x[2] / UCF(RAINFALL); + LidProcs[j].storage.clogFactor = x[3]; + LidProcs[j].storage.covered = covered; + return 0; +} + +//============================================================================= + +int readDrainData(int j, char* toks[], int ntoks) +// +// Purpose: reads underdrain data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID DRAIN coeff expon offset delay hOpen hClose curve +// +{ + int i; + double x[6]; + + //... read numerical parameters + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + for (i = 0; i < 6; i++) x[i] = 0.0; + for (i = 2; i < 8; i++) + { + if ( (ntoks > i) && (! getDouble(toks[i], &x[i-2]) || x[i-2]) < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + i = -1; + if ( ntoks >= 9 ) + { + i = project_findObject(CURVE, toks[8]); + if (i < 0) return error_setInpError(ERR_NAME, toks[8]); + } + + //... save parameters to LID drain layer structure + LidProcs[j].drain.coeff = x[0]; + LidProcs[j].drain.expon = x[1]; + LidProcs[j].drain.offset = x[2] / UCF(RAINDEPTH); + LidProcs[j].drain.delay = x[3] * 3600.0; + LidProcs[j].drain.hOpen = x[4] / UCF(RAINDEPTH); + LidProcs[j].drain.hClose = x[5] / UCF(RAINDEPTH); + LidProcs[j].drain.qCurve = i; + return 0; +} + +//============================================================================= + +int readDrainMatData(int j, char* toks[], int ntoks) +// +// Purpose: reads drainage mat data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID DRAINMAT thickness voidRatio roughness +// +{ + int i; + double x[3]; + + //... read numerical parameters + if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); + if ( LidProcs[j].lidType != GREEN_ROOF ) return 0; + for (i = 2; i < 5; i++) + { + if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) + return error_setInpError(ERR_NUMBER, toks[i]); + } + + //... save parameters to LID drain layer structure + LidProcs[j].drainMat.thickness = x[0] / UCF(RAINDEPTH);; + LidProcs[j].drainMat.voidFrac = x[1]; + LidProcs[j].drainMat.roughness = x[2]; + return 0; +} + +//============================================================================= + +int readRemovalsData(int j, char* toks[], int ntoks) +// +// Purpose: reads pollutant removal data for a LID process from line of input +// data file +// Input: j = LID process index +// toks = array of string tokens +// ntoks = number of tokens +// Output: returns error code +// +// Format of data is: +// LID_ID REMOVALS pollut1 %removal1 pollut2 %removal2 ... +// +{ + int i = 2; + int p; + double rmvl; + + //... start with 3rd token + if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); + while (ntoks > i) + { + //... find pollutant index from its name + p = project_findObject(POLLUT, toks[i]); + if (p < 0) return error_setInpError(ERR_NAME, toks[i]); + + //... check that a next token exists + i++; + if (ntoks == i) return error_setInpError(ERR_ITEMS, ""); + + //... get the % removal value from the next token + if (!getDouble(toks[i], &rmvl) || rmvl < 0.0 || rmvl > 100.0) + return error_setInpError(ERR_NUMBER, toks[i]); + + //... save the pollutant removal for the LID process as a fraction + LidProcs[j].drainRmvl[p] = rmvl / 100.0; + i++; + } + return 0; +} +//============================================================================= + +void lid_writeSummary() +// +// Purpose: writes summary of LID processes used to report file. +// Input: none +// Output: none +// +{ + int j, k; + double pctArea; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + fprintf(Frpt.file, "\n"); + fprintf(Frpt.file, "\n"); + fprintf(Frpt.file, "\n *******************"); + fprintf(Frpt.file, "\n LID Control Summary"); + fprintf(Frpt.file, "\n *******************"); + + + fprintf(Frpt.file, +"\n No. of Unit Unit %% Area %% Imperv %% Perv"); //(5.1.013) + fprintf(Frpt.file, // +"\n Subcatchment LID Control Units Area Width Covered Treated Treated"); // + fprintf(Frpt.file, // +"\n ---------------------------------------------------------------------------------------------------"); // + + for (j = 0; j < GroupCount; j++) + { + lidGroup = LidGroups[j]; + if ( lidGroup == NULL ) continue; + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + pctArea = lidUnit->area * lidUnit->number / Subcatch[j].area * 100.0; + fprintf(Frpt.file, "\n %-16s %-16s", Subcatch[j].ID, LidProcs[k].ID); + fprintf(Frpt.file, "%6d %10.2f %10.2f %10.2f %10.2f %10.2f", + lidUnit->number, lidUnit->area * SQR(UCF(LENGTH)), + lidUnit->fullWidth * UCF(LENGTH), pctArea, + lidUnit->fromImperv*100.0, lidUnit->fromPerv*100.0); + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_validate() +// +// Purpose: validates LID process and group parameters. +// Input: none +// Output: none +// +{ + int j; + for (j = 0; j < LidCount; j++) validateLidProc(j); + for (j = 0; j < GroupCount; j++) validateLidGroup(j); +} + +//============================================================================= + +void validateLidProc(int j) +// +// Purpose: validates LID process parameters. +// Input: j = LID process index +// Output: none +// +{ + int layerMissing = FALSE; + + //... check that LID type was supplied + if ( LidProcs[j].lidType < 0 ) + { + report_writeErrorMsg(ERR_LID_TYPE, LidProcs[j].ID); + return; + } + + //... check that required layers were defined + switch (LidProcs[j].lidType) + { + case BIO_CELL: + case RAIN_GARDEN: + if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; + break; + case GREEN_ROOF: + if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; + if ( LidProcs[j].drainMat.thickness <= 0.0) layerMissing = TRUE; + break; + case POROUS_PAVEMENT: + if ( LidProcs[j].pavement.thickness <= 0.0 ) layerMissing = TRUE; + break; + case INFIL_TRENCH: + if ( LidProcs[j].storage.thickness <= 0.0 ) layerMissing = TRUE; + break; + } + if ( layerMissing ) + { + report_writeErrorMsg(ERR_LID_LAYER, LidProcs[j].ID); + return; + } + + //... check pavement layer parameters + if ( LidProcs[j].lidType == POROUS_PAVEMENT ) + { + if ( LidProcs[j].pavement.thickness <= 0.0 + || LidProcs[j].pavement.kSat <= 0.0 + || LidProcs[j].pavement.voidFrac <= 0.0 + || LidProcs[j].pavement.voidFrac > 1.0 + || LidProcs[j].pavement.impervFrac > 1.0 ) + + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_PAVE_LAYER, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... check soil layer parameters + if ( LidProcs[j].soil.thickness > 0.0 ) + { + if ( LidProcs[j].soil.porosity <= 0.0 + || LidProcs[j].soil.fieldCap >= LidProcs[j].soil.porosity + || LidProcs[j].soil.wiltPoint >= LidProcs[j].soil.fieldCap + || LidProcs[j].soil.kSat <= 0.0 + || LidProcs[j].soil.kSlope < 0.0 ) + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_SOIL_LAYER, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... check storage layer parameters + if ( LidProcs[j].storage.thickness > 0.0 ) + { + if ( LidProcs[j].storage.voidFrac <= 0.0 || + LidProcs[j].storage.voidFrac > 1.0 ) + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_STOR_LAYER, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... if no storage layer adjust void fraction and drain offset + else + { + LidProcs[j].storage.voidFrac = 1.0; + LidProcs[j].drain.offset = 0.0; + } + + //... check for invalid drain open/closed heads + if (LidProcs[j].drain.hOpen > 0.0 && + LidProcs[j].drain.hOpen <= LidProcs[j].drain.hClose) + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_DRAIN_HEADS, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + + //... compute the surface layer's overland flow constant (alpha) + if ( LidProcs[j].lidType == VEG_SWALE ) + { + if ( LidProcs[j].surface.roughness * + LidProcs[j].surface.surfSlope <= 0.0 || + LidProcs[j].surface.thickness == 0.0 + ) + { + sstrncpy(Msg, LidProcs[j].ID, MAXMSG); + sstrcat(Msg, ERR_SWALE_SURF, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + else LidProcs[j].surface.alpha = + 1.49 * sqrt(LidProcs[j].surface.surfSlope) / + LidProcs[j].surface.roughness; + } + else + { + //... compute surface overland flow coeff. + if ( LidProcs[j].surface.roughness > 0.0 ) + LidProcs[j].surface.alpha = 1.49 / LidProcs[j].surface.roughness * + sqrt(LidProcs[j].surface.surfSlope); + else LidProcs[j].surface.alpha = 0.0; + } + + //... compute drainage mat layer's flow coeff. + if ( LidProcs[j].drainMat.roughness > 0.0 ) + { + LidProcs[j].drainMat.alpha = 1.49 / LidProcs[j].drainMat.roughness * + sqrt(LidProcs[j].surface.surfSlope); + } + else LidProcs[j].drainMat.alpha = 0.0; + + + //... convert clogging factors to void volume basis + if ( LidProcs[j].pavement.thickness > 0.0 ) + { + LidProcs[j].pavement.clogFactor *= + LidProcs[j].pavement.thickness * LidProcs[j].pavement.voidFrac * + (1.0 - LidProcs[j].pavement.impervFrac); + } + if ( LidProcs[j].storage.thickness > 0.0 ) + { + LidProcs[j].storage.clogFactor *= + LidProcs[j].storage.thickness * LidProcs[j].storage.voidFrac; + } + else LidProcs[j].storage.clogFactor = 0.0; + + //... for certain LID types, immediate overflow of excess surface water + // occurs if either the surface roughness or slope is zero + LidProcs[j].surface.canOverflow = TRUE; + switch (LidProcs[j].lidType) + { + case ROOF_DISCON: LidProcs[j].surface.canOverflow = FALSE; break; + case INFIL_TRENCH: + case POROUS_PAVEMENT: + case BIO_CELL: + case RAIN_GARDEN: + case GREEN_ROOF: + if ( LidProcs[j].surface.alpha > 0.0 ) + LidProcs[j].surface.canOverflow = FALSE; + } + + //... rain barrels have 100% void space and impermeable bottom + if ( LidProcs[j].lidType == RAIN_BARREL ) + { + LidProcs[j].storage.voidFrac = 1.0; + LidProcs[j].storage.kSat = 0.0; + } + + //... set storage layer parameters of a green roof + if ( LidProcs[j].lidType == GREEN_ROOF ) + { + LidProcs[j].storage.thickness = LidProcs[j].drainMat.thickness; + LidProcs[j].storage.voidFrac = LidProcs[j].drainMat.voidFrac; + LidProcs[j].storage.clogFactor = 0.0; + LidProcs[j].storage.kSat = 0.0; + } +} + +//============================================================================= + +void validateLidGroup(int j) +// +// Purpose: validates properties of LID units grouped in a subcatchment. +// Input: j = subcatchment index +// Output: returns 1 if data are valid, 0 if not +// +{ + int k; + double p[3]; + double totalArea = Subcatch[j].area; + double totalLidArea = 0.0; + double fromImperv = 0.0; + double fromPerv = 0.0; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + lidGroup = LidGroups[j]; + if ( lidGroup == NULL ) return; + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + + //... update contributing fractions + totalLidArea += (lidUnit->area * lidUnit->number); + fromImperv += lidUnit->fromImperv; + fromPerv += lidUnit->fromPerv; + + //... assign biocell soil layer infiltration parameters + lidUnit->soilInfil.Ks = 0.0; + if ( LidProcs[k].soil.thickness > 0.0 ) + { + p[0] = LidProcs[k].soil.suction * UCF(RAINDEPTH); + p[1] = LidProcs[k].soil.kSat * UCF(RAINFALL); + p[2] = (LidProcs[k].soil.porosity - LidProcs[k].soil.wiltPoint) * + (1.0 - lidUnit->initSat); + if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) + { + sstrncpy(Msg, LidProcs[k].ID, MAXMSG); + sstrcat(Msg, ERR_SOIL_LAYER, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... assign vegetative swale infiltration parameters + if ( LidProcs[k].lidType == VEG_SWALE ) + { + if ( Subcatch[j].infilModel == GREEN_AMPT || + Subcatch[j].infilModel == MOD_GREEN_AMPT ) + { + grnampt_getParams(j, p); + if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) + { + sstrncpy(Msg, LidProcs[k].ID, MAXMSG); + sstrcat(Msg, ERR_GREEN_AMPT, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + if ( lidUnit->fullWidth <= 0.0 ) + { + sstrncpy(Msg, LidProcs[k].ID, MAXMSG); + sstrcat(Msg, ERR_SWALE_WIDTH, MAXMSG); + report_writeErrorMsg(ERR_LID_PARAMS, Msg); + } + } + + //... LID unit cannot send outflow back to subcatchment's + // pervious area if none exists + if ( Subcatch[j].fracImperv >= 0.999 ) lidUnit->toPerv = 0; + + //... assign drain outlet if not set by user + if ( lidUnit->drainNode == -1 && lidUnit->drainSubcatch == -1 ) + { + lidUnit->drainNode = Subcatch[j].outNode; + lidUnit->drainSubcatch = Subcatch[j].outSubcatch; + } + lidList = lidList->nextLidUnit; + } + + //... check contributing area fractions + if ( totalLidArea > 1.001 * totalArea ) + { + report_writeErrorMsg(ERR_LID_AREAS, Subcatch[j].ID); + } + if ( fromImperv > 1.001 || fromPerv > 1.001 ) + { + report_writeErrorMsg(ERR_LID_CAPTURE_AREA, Subcatch[j].ID); + } + + //... Make subcatchment LID area equal total area if the two are close + if ( totalLidArea > 0.999 * totalArea ) totalLidArea = totalArea; + Subcatch[j].lidArea = totalLidArea; +} + +//============================================================================= + +void lid_initState() +// +// Purpose: initializes the internal state of each LID in a subcatchment. +// Input: none +// Output: none +// +{ + int i, j, k; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + double initVol; + double initDryTime = StartDryDays * SECperDAY; + + HasWetLids = FALSE; + for (j = 0; j < GroupCount; j++) + { + //... check if group exists + lidGroup = LidGroups[j]; + if ( lidGroup == NULL ) continue; + + //... initialize group variables + lidGroup->pervArea = 0.0; + lidGroup->flowToPerv = 0.0; + lidGroup->oldDrainFlow = 0.0; + lidGroup->newDrainFlow = 0.0; + + //... examine each LID in the group + lidList = lidGroup->lidList; + while ( lidList ) + { + //... initialize depth & moisture content + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + lidUnit->surfaceDepth = 0.0; + lidUnit->storageDepth = 0.0; + lidUnit->soilMoisture = 0.0; + lidUnit->paveDepth = 0.0; + lidUnit->dryTime = initDryTime; + lidUnit->volTreated = 0.0; + lidUnit->nextRegenDay = LidProcs[k].pavement.regenDays; + initVol = 0.0; + if ( LidProcs[k].soil.thickness > 0.0 ) + { + lidUnit->soilMoisture = LidProcs[k].soil.wiltPoint + + lidUnit->initSat * (LidProcs[k].soil.porosity - + LidProcs[k].soil.wiltPoint); + initVol += lidUnit->soilMoisture * LidProcs[k].soil.thickness; + } + if ( LidProcs[k].storage.thickness > 0.0 ) + { + lidUnit->storageDepth = lidUnit->initSat * + LidProcs[k].storage.thickness; + initVol += lidUnit->storageDepth * LidProcs[k].storage.voidFrac; + } + if ( LidProcs[k].drainMat.thickness > 0.0 ) + { + lidUnit->storageDepth = lidUnit->initSat * + LidProcs[k].drainMat.thickness; + initVol += lidUnit->storageDepth * LidProcs[k].drainMat.voidFrac; + } + if ( lidUnit->initSat > 0.0 ) HasWetLids = TRUE; + + //... initialize water balance totals + lidproc_initWaterBalance(lidUnit, initVol); + lidUnit->volTreated = 0.0; + + //... initialize report file for the LID + if ( lidUnit->rptFile ) + { + initLidRptFile(Title[0], LidProcs[k].ID, Subcatch[j].ID, lidUnit); + } + + //... initialize drain flows + lidUnit->oldDrainFlow = 0.0; + lidUnit->newDrainFlow = 0.0; + + //... set previous flux rates to 0 + for (i = 0; i < MAX_LAYERS; i++) + { + lidUnit->oldFluxRates[i] = 0.0; + } + + //... initialize infiltration state variables + if ( lidUnit->soilInfil.Ks > 0.0 ) + grnampt_initState(&(lidUnit->soilInfil)); + + //... add contribution to pervious LID area + if ( isLidPervious(lidUnit->lidIndex) ) + lidGroup->pervArea += (lidUnit->area * lidUnit->number); + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_setOldGroupState(int j) +// +// Purpose: saves the current drain flow rate for the LIDs in a subcatchment. +// Input: j = subcatchment index +// Output: none +// +{ + TLidList* lidList; + if ( LidGroups[j] != NULL ) + { + LidGroups[j]->oldDrainFlow = LidGroups[j]->newDrainFlow; + LidGroups[j]->newDrainFlow = 0.0; + lidList = LidGroups[j]->lidList; + while (lidList) + { + lidList->lidUnit->oldDrainFlow = lidList->lidUnit->newDrainFlow; + lidList->lidUnit->newDrainFlow = 0.0; + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +int isLidPervious(int k) +// +// Purpose: determines if a LID process allows infiltration or not. +// Input: k = LID process index +// Output: returns 1 if process is pervious or 0 if not +// +{ + return ( LidProcs[k].storage.thickness == 0.0 || + LidProcs[k].storage.kSat > 0.0 ); +} + +//============================================================================= + +double getSurfaceDepth(int j) +// +// Purpose: computes the depth (volume per unit area) of ponded water on the +// surface of all LIDs within a subcatchment. +// Input: j = subcatchment index +// Output: returns volumetric depth of ponded water (ft) +// +{ + int k; + double depth = 0.0; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + lidGroup = LidGroups[j]; + if ( lidGroup == NULL ) return 0.0; + if ( Subcatch[j].lidArea == 0.0 ) return 0.0; + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + depth += lidUnit->surfaceDepth * LidProcs[k].surface.voidFrac * + lidUnit->area * lidUnit->number; + lidList = lidList->nextLidUnit; + } + return depth / Subcatch[j].lidArea; +} + +//============================================================================= + +double lid_getPervArea(int j) +// +// Purpose: retrieves amount of pervious LID area in a subcatchment. +// Input: j = subcatchment index +// Output: returns amount of pervious LID area (ft2) +// +{ + if ( LidGroups[j] ) return LidGroups[j]->pervArea; + else return 0.0; +} + +//============================================================================= + +double lid_getFlowToPerv(int j) +// +// Purpose: retrieves flow returned from LID treatment to pervious area of +// a subcatchment. +// Input: j = subcatchment index +// Output: returns flow returned to pervious area (cfs) +// +{ + if ( LidGroups[j] != NULL ) return LidGroups[j]->flowToPerv; + return 0.0; +} + +//============================================================================= + +double lid_getStoredVolume(int j) +// +// Purpose: computes stored volume of water for all LIDs +// grouped within a subcatchment. +// Input: j = subcatchment index +// Output: returns stored volume of water (ft3) +// +{ + double total = 0.0; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + lidGroup = LidGroups[j]; + if ( lidGroup == NULL || Subcatch[j].lidArea == 0.0 ) return 0.0; + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + total += lidUnit->waterBalance.finalVol * lidUnit->area * lidUnit->number; + lidList = lidList->nextLidUnit; + } + return total; +} + +//============================================================================= + +double lid_getDrainFlow(int j, int timePeriod) +// +// Purpose: returns flow from all of a subcatchment's LID drains for +// a designated time period +// Input: j = subcatchment index +// timePeriod = either PREVIOUS or CURRENT +// Output: total drain flow (cfs) from the subcatchment. +{ + if ( LidGroups[j] != NULL ) + { + if ( timePeriod == PREVIOUS ) return LidGroups[j]->oldDrainFlow; + else return LidGroups[j]->newDrainFlow; + } + return 0.0; +} + +//============================================================================= + +void lid_addDrainLoads(int j, double c[], double tStep) +// +// Purpose: adds pollutant loads routed from drains to system +// mass balance totals. +// Input: j = subcatchment index +// c = array of pollutant washoff concentrations (mass/L) +// tStep = time step (sec) +// Output: none. +// +{ + int isRunoffLoad; // true if drain becomes external runoff load + int p; // pollutant index + double r; // pollutant fractional removal + double w; // pollutant mass load (lb or kg) + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... check if LID group exists + lidGroup = LidGroups[j]; + if ( lidGroup != NULL ) + { + //... examine each LID unit in the group + lidList = lidGroup->lidList; + while ( lidList ) + { + lidUnit = lidList->lidUnit; + + //... see if unit's drain flow becomes external runoff + isRunoffLoad = (lidUnit->drainNode >= 0 || + lidUnit->drainSubcatch == j); + + //... for each pollutant not routed back on to subcatchment surface + if (!lidUnit->toPerv) for (p = 0; p < Nobjects[POLLUT]; p++) + { + //... get mass load flowing through the drain + w = lidUnit->newDrainFlow * c[p] * tStep * LperFT3 * Pollut[p].mcf; + + //... get fractional removal for this load + r = LidProcs[lidUnit->lidIndex].drainRmvl[p]; + + //... update system mass balance totals + massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, r*w); + if (isRunoffLoad) + massbal_updateLoadingTotals(RUNOFF_LOAD, p, w*(1.0 - r)); + } + + // process next LID unit in the group + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_addDrainRunon(int j) +// +// Purpose: adds drain flows from LIDs in a given subcatchment to the +// subcatchments that were designated to receive them +// Input: j = index of subcatchment contributing underdrain flows +// Output: none. +// +{ + int i; // index of an LID unit's LID process + int k; // index of subcatchment receiving LID drain flow + int p; // pollutant index + double q; // drain flow rate (cfs) + double w; // mass of polllutant from drain flow + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... check if LID group exists + lidGroup = LidGroups[j]; + if ( lidGroup != NULL ) + { + //... examine each LID in the group + lidList = lidGroup->lidList; + while ( lidList ) + { + //... see if LID's drain discharges to another subcatchment + lidUnit = lidList->lidUnit; + i = lidUnit->lidIndex; + k = lidUnit->drainSubcatch; + if ( k >= 0 && k != j ) + { + //... distribute drain flow across subcatchment's areas + q = lidUnit->oldDrainFlow; + subcatch_addRunonFlow(k, q); + + //... add pollutant loads from drain to subcatchment + // (newQual[] contains loading rate (mass/sec) at this + // point which is converted later on to a concentration) + for (p = 0; p < Nobjects[POLLUT]; p++) + { + w = q * Subcatch[j].oldQual[p] * LperFT3; + w = w * (1.0 - LidProcs[i].drainRmvl[p]); + Subcatch[k].newQual[p] += w; + } + } + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_addDrainInflow(int j, double f) +// +// Purpose: adds LID drain flow to conveyance system nodes +// Input: j = subcatchment index +// f = time interval weighting factor +// Output: none. +// +// Note: this function updates the total lateral flow (Node[].newLatFlow) +// and pollutant mass (Node[].newQual[]) inflow seen by nodes that +// receive drain flow from the LID units in subcatchment j. +{ + int i, // LID process index + k, // node index + p; // pollutant index + double q, // drain flow (cfs) + w, w1, w2; // pollutant mass loads (mass/sec) + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... check if LID group exists + lidGroup = LidGroups[j]; + if ( lidGroup != NULL ) + { + //... examine each LID in the group + lidList = lidGroup->lidList; + while ( lidList ) + { + //... see if LID's drain discharges to conveyance system node + lidUnit = lidList->lidUnit; + i = lidUnit->lidIndex; + k = lidUnit->drainNode; + if ( k >= 0 ) + { + //... add drain flow to node's wet weather inflow + q = (1.0 - f) * lidUnit->oldDrainFlow + f * lidUnit->newDrainFlow; + Node[k].newLatFlow += q; + massbal_addInflowFlow(WET_WEATHER_INFLOW, q); + + //... add pollutant load, based on parent subcatchment quality + for (p = 0; p < Nobjects[POLLUT]; p++) + { + //... get previous & current drain loads + w1 = lidUnit->oldDrainFlow * Subcatch[j].oldQual[p]; + w2 = lidUnit->newDrainFlow * Subcatch[j].newQual[p]; + + //... add interpolated load to node's wet weather loading + w = (1.0 - f) * w1 + f * w2; + w = w * (1.0 - LidProcs[i].drainRmvl[p]); + Node[k].newQual[p] += w; + massbal_addInflowQual(WET_WEATHER_INFLOW, p, w); + } + } + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void lid_getRunoff(int j, double tStep) +// +// Purpose: computes runoff and drain flows from the LIDs in a subcatchment. +// Input: j = subcatchment index +// tStep = time step (sec) +// Output: updates following global quantities after LID treatment applied: +// Vevap, Vpevap, VlidInfil, VlidIn, VlidOut, VlidDrain. +// +{ + TLidGroup theLidGroup; // group of LIDs placed in the subcatchment + TLidList* lidList; // list of LID units in the group + TLidUnit* lidUnit; // a member of the list of LID units + double lidArea; // area of an LID unit + double qImperv = 0.0; // runoff from impervious areas (cfs) + double qPerv = 0.0; // runoff from pervious areas (cfs) + double lidInflow = 0.0; // inflow to an LID unit (ft/s) + double qRunoff = 0.0; // surface runoff from all LID units (cfs) + double qDrain = 0.0; // drain flow from all LID units (cfs) + double qReturn = 0.0; // LID outflow returned to pervious area (cfs) + + //... return if there are no LID's + theLidGroup = LidGroups[j]; + if ( !theLidGroup ) return; + lidList = theLidGroup->lidList; + if ( !lidList ) return; + + //... determine if evaporation can occur + EvapRate = Evap.rate; + if ( Evap.dryOnly && Subcatch[j].rainfall > 0.0 ) EvapRate = 0.0; + + //... find subcatchment's infiltration rate into native soil + findNativeInfil(j, tStep); + + //... get impervious and pervious area runoff from non-LID + // portion of subcatchment (cfs) + if ( Subcatch[j].area > Subcatch[j].lidArea ) + { + qImperv = getImpervAreaRunoff(j); + qPerv = getPervAreaRunoff(j); + } + + //... evaluate performance of each LID unit placed in the subcatchment + while ( lidList ) + { + //... find area of the LID unit + lidUnit = lidList->lidUnit; + lidArea = lidUnit->area * lidUnit->number; + + //... if LID unit has area, evaluate its performance + if ( lidArea > 0.0 ) + { + //... find runoff from non-LID area treated by LID area (ft/sec) + lidInflow = (qImperv * lidUnit->fromImperv + + qPerv * lidUnit->fromPerv) / lidArea; + + //... update total runoff volume treated + VlidIn += lidInflow * lidArea * tStep; + + //... add rainfall onto LID inflow (ft/s) + lidInflow = lidInflow + getRainInflow(j, lidUnit); + + // ... add upstream runon only if LID occupies full subcatchment + if ( Subcatch[j].area == Subcatch[j].lidArea ) + { + lidInflow += Subcatch[j].runon; + } + + //... evaluate the LID unit's performance, updating the LID group's + // total surface runoff, drain flow, and flow returned to + // pervious area + evalLidUnit(j, lidUnit, lidArea, lidInflow, tStep, + &qRunoff, &qDrain, &qReturn); + } + lidList = lidList->nextLidUnit; + } + + //... save the LID group's total drain & return flows + theLidGroup->newDrainFlow = qDrain; + theLidGroup->flowToPerv = qReturn; + + //... save the LID group's total surface, drain and return flow volumes + VlidOut = qRunoff * tStep; + VlidDrain = qDrain * tStep; + VlidReturn = qReturn * tStep; +} + +//============================================================================= + +void findNativeInfil(int j, double tStep) +// +// Purpose: determines a subcatchment's current infiltration rate into +// its native soil. +// Input: j = subcatchment index +// tStep = time step (sec) +// Output: sets values for module-level variables NativeInfil +// +{ + double nonLidArea; + + //... subcatchment has non-LID pervious area + nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; + if ( nonLidArea > 0.0 && Subcatch[j].fracImperv < 1.0 ) + { + NativeInfil = Vinfil / nonLidArea / tStep; + } + + //... otherwise find infil. rate for the subcatchment's rainfall + runon + else + { + NativeInfil = infil_getInfil(j, tStep, + Subcatch[j].rainfall, + Subcatch[j].runon, + getSurfaceDepth(j)); + } + + //... see if there is any groundwater-imposed limit on infil. + if ( !IgnoreGwater && Subcatch[j].groundwater ) + { + MaxNativeInfil = Subcatch[j].groundwater->maxInfilVol / tStep; + } + else MaxNativeInfil = BIG; +} + +//============================================================================= + +double getRainInflow(int j, TLidUnit* lidUnit) +// +// Purpose: gets rainfall inflow to an LID unit. +// Input: j = subcatchment index +// lidUnit = ptr. to an LID unit +// Output: returns rainfall rate over the LID unit (ft/sec) +// +{ + TLidProc* lidProc = &LidProcs[lidUnit->lidIndex]; + + if (lidProc->lidType == RAIN_BARREL && + lidProc->storage.covered == TRUE) return 0.0; + return Subcatch[j].rainfall; +} + +//============================================================================= + +double getImpervAreaRunoff(int j) +// +// Purpose: computes runoff from impervious area of a subcatchment that +// is available for LID treatment. +// Input: j = subcatchment index +// Output: returns runoff flow rate (cfs) +// +{ + int i; + double q = 0.0, // runoff rate (ft/sec) + nonLidArea; // non-LID area (ft2) + + // --- runoff from impervious area w/ & w/o depression storage + for (i = IMPERV0; i <= IMPERV1; i++) + { + q += Subcatch[j].subArea[i].runoff * Subcatch[j].subArea[i].fArea; + } + + // --- adjust for any fraction of runoff sent to pervious area + if ( Subcatch[j].subArea[IMPERV0].routeTo == TO_PERV && + Subcatch[j].fracImperv < 1.0 ) + { + q *= Subcatch[j].subArea[IMPERV0].fOutlet; + } + nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; + return q * nonLidArea; +} + +//============================================================================= + +double getPervAreaRunoff(int j) +// +// Purpose: computes runoff from pervious area of a subcatchment that +// is available for LID treatment. +// Input: j = subcatchment index +// Output: returns runoff flow rate (cfs) +// +{ + double q = 0.0, // runoff rate (ft/sec) + nonLidArea; // non-LID area (ft2) + + // --- runoff from pervious area + q = Subcatch[j].subArea[PERV].runoff * Subcatch[j].subArea[PERV].fArea; + + // --- adjust for any fraction of runoff sent to impervious area + if (Subcatch[j].subArea[PERV].routeTo == TO_IMPERV && + Subcatch[j].fracImperv > 0.0) + { + q *= Subcatch[j].subArea[PERV].fOutlet; + } + nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; + return q * nonLidArea; +} + +//============================================================================= + +void evalLidUnit(int j, TLidUnit* lidUnit, double lidArea, double lidInflow, + double tStep, double *qRunoff, double *qDrain, double *qReturn) +// +// Purpose: evaluates performance of a specific LID unit over current time step. +// Input: j = subcatchment index +// lidUnit = ptr. to LID unit being evaluated +// lidArea = area of LID unit +// lidInflow = inflow to LID unit (ft/s) +// tStep = time step (sec) +// Output: qRunoff = sum of surface runoff from all LIDs (cfs) +// qDrain = sum of drain flows from all LIDs (cfs) +// qReturn = sum of LID flows returned to pervious area (cfs) +// +{ + TLidProc* lidProc; // LID process associated with lidUnit + double lidRunoff, // surface runoff from LID unit (cfs) + lidEvap, // evaporation rate from LID unit (ft/s) + lidInfil, // infiltration rate from LID unit (ft/s) + lidDrain; // drain flow rate from LID unit (ft/s & cfs) + + //... identify the LID process of the LID unit being analyzed + lidProc = &LidProcs[lidUnit->lidIndex]; + + //... initialize evap and infil losses + lidEvap = 0.0; + lidInfil = 0.0; + + //... find surface runoff from the LID unit (in cfs) + lidRunoff = lidproc_getOutflow(lidUnit, lidProc, lidInflow, EvapRate, + NativeInfil, MaxNativeInfil, tStep, + &lidEvap, &lidInfil, &lidDrain) * lidArea; + + //... convert drain flow to CFS + lidDrain *= lidArea; + + //... revise flows if LID outflow returned to pervious area + if ( lidUnit->toPerv && Subcatch[j].area > Subcatch[j].lidArea ) + { + //... surface runoff is always returned + *qReturn += lidRunoff; + lidRunoff = 0.0; + + //... drain flow returned if it has same outlet as subcatchment + if ( lidUnit->drainNode == Subcatch[j].outNode && + lidUnit->drainSubcatch == Subcatch[j].outSubcatch ) + { + *qReturn += lidDrain; + lidDrain = 0.0; + } + } + + //... update system flow balance if drain flow goes to a + // conveyance system node + if ( lidUnit->drainNode >= 0 ) + { + massbal_updateRunoffTotals(RUNOFF_DRAINS, lidDrain * tStep); + } + + //... save new drain outflow + lidUnit->newDrainFlow = lidDrain; + + //... update moisture losses (ft3) + Vevap += lidEvap * tStep * lidArea; + VlidInfil += lidInfil * tStep * lidArea; + if ( isLidPervious(lidUnit->lidIndex) ) + { + Vpevap += lidEvap * tStep * lidArea; + } + + //... update time since last rainfall (for Rain Barrel emptying) + if ( Subcatch[j].rainfall > MIN_RUNOFF ) lidUnit->dryTime = 0.0; + else lidUnit->dryTime += tStep; + + //... update LID water balance and save results + lidproc_saveResults(lidUnit, UCF(RAINFALL), UCF(RAINDEPTH)); + + //... update LID group totals + *qRunoff += lidRunoff; + *qDrain += lidDrain; +} + +//============================================================================= + +void lid_writeWaterBalance() +// +// Purpose: writes a LID performance summary table to the project's report file. +// Input: none +// Output: none +// +{ + int j; + int k = 0; + double ucf = UCF(RAINDEPTH); + double inflow; + double outflow; + double err; + TLidUnit* lidUnit; + TLidList* lidList; + TLidGroup lidGroup; + + //... check that project has LIDs + for ( j = 0; j < GroupCount; j++ ) + { + if ( LidGroups[j] ) k++; + } + if ( k == 0 ) return; + + //... write table header + fprintf(Frpt.file, + "\n" + "\n ***********************" + "\n LID Performance Summary" + "\n ***********************\n"); + + fprintf(Frpt.file, +"\n --------------------------------------------------------------------------------------------------------------------" +"\n Total Evap Infil Surface Drain Initial Final Continuity" +"\n Inflow Loss Loss Outflow Outflow Storage Storage Error"); + if ( UnitSystem == US ) fprintf(Frpt.file, +"\n Subcatchment LID Control in in in in in in in %%"); + else fprintf(Frpt.file, +"\n Subcatchment LID Control mm mm mm mm mm mm mm %%"); + fprintf(Frpt.file, +"\n --------------------------------------------------------------------------------------------------------------------"); + + //... examine each LID unit in each subcatchment + for ( j = 0; j < GroupCount; j++ ) + { + lidGroup = LidGroups[j]; + if ( !lidGroup || Subcatch[j].lidArea == 0.0 ) continue; + lidList = lidGroup->lidList; + while ( lidList ) + { + //... write water balance components to report file + lidUnit = lidList->lidUnit; + k = lidUnit->lidIndex; + fprintf(Frpt.file, "\n %-16s %-16s", Subcatch[j].ID, + LidProcs[k].ID); + fprintf(Frpt.file, "%10.2f%10.2f%10.2f%10.2f%10.2f%10.2f%10.2f", + lidUnit->waterBalance.inflow*ucf, + lidUnit->waterBalance.evap*ucf, + lidUnit->waterBalance.infil*ucf, + lidUnit->waterBalance.surfFlow*ucf, + lidUnit->waterBalance.drainFlow*ucf, + lidUnit->waterBalance.initVol*ucf, + lidUnit->waterBalance.finalVol*ucf); + + //... compute flow balance error + inflow = lidUnit->waterBalance.initVol + + lidUnit->waterBalance.inflow; + outflow = lidUnit->waterBalance.finalVol + + lidUnit->waterBalance.evap + + lidUnit->waterBalance.infil + + lidUnit->waterBalance.surfFlow + + lidUnit->waterBalance.drainFlow; + if ( inflow > 0.0 ) err = (inflow - outflow) / inflow; + else err = 1.0; + fprintf(Frpt.file, " %10.2f", err*100.0); + lidList = lidList->nextLidUnit; + } + } +} + +//============================================================================= + +void initLidRptFile(char* title, char* lidID, char* subcatchID, TLidUnit* lidUnit) +// +// Purpose: initializes the report file used for a specific LID unit +// Input: title = project's title +// lidID = LID process name +// subcatchID = subcatchment ID name +// lidUnit = ptr. to LID unit +// Output: none +// +{ + static int colCount = 14; + static char* head1[] = { + "\n \t", " Elapsed\t", + " Total\t", " Total\t", " Surface\t", " Pavement\t", " Soil\t", + " Storage\t", " Surface\t", " Drain\t", " Surface\t", " Pavement\t", + " Soil\t", " Storage"}; + static char* head2[] = { + "\n \t", " Time\t", + " Inflow\t", " Evap\t", " Infil\t", " Perc\t", " Perc\t", + " Exfil\t", " Runoff\t", " OutFlow\t", " Level\t", " Level\t", + " Moisture\t", " Level"}; + static char* units1[] = { + "\nDate Time \t", " Hours\t", + " in/hr\t", " in/hr\t", " in/hr\t", " in/hr\t", " in/hr\t", + " in/hr\t", " in/hr\t", " in/hr\t", " inches\t", " inches\t", + " Content\t", " inches"}; + static char* units2[] = { + "\nDate Time \t", " Hours\t", + " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm/hr\t", + " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm\t", " mm\t", + " Content\t", " mm"}; + static char line9[] = " ---------"; + int i; + FILE* f = lidUnit->rptFile->file; + + //... check that file was opened + if ( f == NULL ) return; + + //... write title lines + fprintf(f, "SWMM5 LID Report File\n"); + fprintf(f, "\nProject: %s", title); + fprintf(f, "\nLID Unit: %s in Subcatchment %s\n", lidID, subcatchID); + + //... write column headings + for ( i = 0; i < colCount; i++) fprintf(f, "%s", head1[i]); + for ( i = 0; i < colCount; i++) fprintf(f, "%s", head2[i]); + if ( UnitSystem == US ) + { + for ( i = 0; i < colCount; i++) fprintf(f, "%s", units1[i]); + } + else for ( i = 0; i < colCount; i++) fprintf(f, "%s", units2[i]); + fprintf(f, "\n----------- --------"); + for ( i = 1; i < colCount; i++) fprintf(f, "\t%s", line9); + + //... initialize LID dryness state + lidUnit->rptFile->wasDry = 1; + sstrncpy(lidUnit->rptFile->results, "", 0); +} diff --git a/src/lid.h b/src/lid.h new file mode 100644 index 000000000..b250fcf3a --- /dev/null +++ b/src/lid.h @@ -0,0 +1,236 @@ +//----------------------------------------------------------------------------- +// lid.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// Public interface for LID functions. +// +// Update History +// ============== +// Build 5.1.008: +// - Support added for Roof Disconnection LID. +// - Support added for separate routing of LID drain flows. +// - Detailed LID reporting modified. +// Build 5.1.011: +// - Water depth replaces moisture content for LID's pavement layer. +// - Arguments for lidproc_saveResults() modified. +// Build 5.1.012: +// - Redefined meaning of wasDry in TLidRptFile structure. +// Build 5.1.013: +// - New member fromPerv added to TLidUnit structure to allow LID +// units to also treat pervious area runoff. +// - New members hOpen and hClose addded to TDrainLayer to open/close +// drain when certain heads are reached. +// - New member qCurve added to TDrainLayer to allow underdrain flow to +// be adjusted by a curve of multiplier v. head. +// - New array drainRmvl added to TLidProc to allow for underdrain +// pollutant removal values. +// - New members added to TPavementLayer and TLidUnit to support +// unclogging permeable pavement at fixed intervals. +// Build 5.2.0: +// - Covered property added to RAIN_BARREL parameters +//----------------------------------------------------------------------------- + +#ifndef LID_H +#define LID_H + +#include +#include +#include +#include "infil.h" + +//----------------------------------------------------------------------------- +// Enumerations +//----------------------------------------------------------------------------- +enum LidTypes { + BIO_CELL, // bio-retention cell + RAIN_GARDEN, // rain garden + GREEN_ROOF, // green roof + INFIL_TRENCH, // infiltration trench + POROUS_PAVEMENT, // porous pavement + RAIN_BARREL, // rain barrel + VEG_SWALE, // vegetative swale + ROOF_DISCON}; // roof disconnection + +enum TimePeriod { + PREVIOUS, // previous time period + CURRENT}; // current time period + +//----------------------------------------------------------------------------- +// Data Structures +//----------------------------------------------------------------------------- +#define MAX_LAYERS 4 + +// LID Surface Layer +typedef struct +{ + double thickness; // depression storage or berm ht. (ft) + double voidFrac; // available fraction of storage volume + double roughness; // surface Mannings n + double surfSlope; // land surface slope (fraction) + double sideSlope; // swale side slope (run/rise) + double alpha; // slope/roughness term in Manning eqn. + char canOverflow; // 1 if immediate outflow of excess water +} TSurfaceLayer; + +// LID Pavement Layer +typedef struct +{ + double thickness; // layer thickness (ft) + double voidFrac; // void volume / total volume + double impervFrac; // impervious area fraction + double kSat; // permeability (ft/sec) + double clogFactor; // clogging factor + double regenDays; // clogging regeneration interval (days) + double regenDegree; // degree of clogging regeneration +} TPavementLayer; + +// LID Soil Layer +typedef struct +{ + double thickness; // layer thickness (ft) + double porosity; // void volume / total volume + double fieldCap; // field capacity + double wiltPoint; // wilting point + double suction; // suction head at wetting front (ft) + double kSat; // saturated hydraulic conductivity (ft/sec) + double kSlope; // slope of log(K) v. moisture content curve +} TSoilLayer; + +// LID Storage Layer +typedef struct +{ + double thickness; // layer thickness (ft) + double voidFrac; // void volume / total volume + double kSat; // saturated hydraulic conductivity (ft/sec) + double clogFactor; // clogging factor + int covered; // TRUE if rain barrel is covered +} TStorageLayer; + +// Underdrain System (part of Storage Layer) +typedef struct +{ + double coeff; // underdrain flow coeff. (in/hr or mm/hr) + double expon; // underdrain head exponent (for in or mm) + double offset; // offset height of underdrain (ft) + double delay; // rain barrel drain delay time (sec) + double hOpen; // head when drain opens (ft) + double hClose; // head when drain closes (ft) + int qCurve; // curve controlling flow rate (optional) +} TDrainLayer; + +// Drainage Mat Layer (for green roofs) +typedef struct +{ + double thickness; // layer thickness (ft) + double voidFrac; // void volume / total volume + double roughness; // Mannings n for green roof drainage mats + double alpha; // slope/roughness term in Manning equation +} TDrainMatLayer; + +// LID Process - generic LID design per unit of area +typedef struct +{ + char* ID; // identifying name + int lidType; // type of LID + TSurfaceLayer surface; // surface layer parameters + TPavementLayer pavement; // pavement layer parameters + TSoilLayer soil; // soil layer parameters + TStorageLayer storage; // storage layer parameters + TDrainLayer drain; // underdrain system parameters + TDrainMatLayer drainMat; // drainage mat layer + double* drainRmvl; // underdrain pollutant removals +} TLidProc; + +// Water Balance Statistics +typedef struct +{ + double inflow; // total inflow (ft) + double evap; // total evaporation (ft) + double infil; // total infiltration (ft) + double surfFlow; // total surface runoff (ft) + double drainFlow; // total underdrain flow (ft) + double initVol; // initial stored volume (ft) + double finalVol; // final stored volume (ft) +} TWaterBalance; + +// LID Report File +typedef struct +{ + FILE* file; // file pointer + int wasDry; // number of successive dry periods + char results[256]; // results for current time period +} TLidRptFile; + +// LID Unit - specific LID process applied over a given area +typedef struct +{ + int lidIndex; // index of LID process + int number; // number of replicate units + double area; // area of single replicate unit (ft2) + double fullWidth; // full top width of single unit (ft) + double botWidth; // bottom width of single unit (ft) + double initSat; // initial saturation of soil & storage layers + double fromImperv; // fraction of impervious area runoff treated + double fromPerv; // fraction of pervious area runoff treated + int toPerv; // 1 if outflow sent to pervious area; 0 if not + int drainSubcatch; // subcatchment receiving drain flow + int drainNode; // node receiving drain flow + TLidRptFile* rptFile; // pointer to detailed report file + + TGrnAmpt soilInfil; // infil. object for biocell soil layer + double surfaceDepth; // depth of ponded water on surface layer (ft) + double paveDepth; // depth of water in porous pavement layer + double soilMoisture; // moisture content of biocell soil layer + double storageDepth; // depth of water in storage layer (ft) + + // net inflow - outflow from previous time step for each LID layer (ft/s) + double oldFluxRates[MAX_LAYERS]; + + double dryTime; // time since last rainfall (sec) + double oldDrainFlow; // previous drain flow (cfs) + double newDrainFlow; // current drain flow (cfs) + double volTreated; // total volume treated (ft) + double nextRegenDay; // next day when unit regenerated + TWaterBalance waterBalance; // water balance quantites +} TLidUnit; + +//----------------------------------------------------------------------------- +// LID Methods +//----------------------------------------------------------------------------- +void lid_create(int lidCount, int subcatchCount); +void lid_delete(void); + +int lid_readProcParams(char* tok[], int ntoks); +int lid_readGroupParams(char* tok[], int ntoks); + +void lid_validate(void); +void lid_initState(void); +void lid_setOldGroupState(int subcatch); + +double lid_getPervArea(int subcatch); +double lid_getFlowToPerv(int subcatch); +double lid_getDrainFlow(int subcatch, int timePeriod); +double lid_getStoredVolume(int subcatch); +void lid_addDrainLoads(int subcatch, double c[], double tStep); +void lid_addDrainRunon(int subcatch); +void lid_addDrainInflow(int subcatch, double f); +void lid_getRunoff(int subcatch, double tStep); +void lid_writeSummary(void); +void lid_writeWaterBalance(void); + +//----------------------------------------------------------------------------- + +void lidproc_initWaterBalance(TLidUnit *lidUnit, double initVol); + +double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, + double inflow, double evap, double infil, double maxInfil, + double tStep, double* lidEvap, double* lidInfil, double* lidDrain); + +void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, + double ucfRainDepth); + +#endif diff --git a/src/lidproc.c b/src/lidproc.c new file mode 100644 index 000000000..aae0ac9bd --- /dev/null +++ b/src/lidproc.c @@ -0,0 +1,1592 @@ +//----------------------------------------------------------------------------- +// lidproc.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +// +// This module computes the hydrologic performance of an LID (Low Impact +// Development) unit at a given point in time. +// +// Update History +// ============== +// Build 5.1.007: +// - Euler integration now applied to all LID types except Vegetative +// Swale which continues to use successive approximation. +// - LID layer flux routines were re-written to more accurately model +// flooded conditions. +// Build 5.1.008: +// - MAX_STATE_VARS replaced with MAX_LAYERS. +// - Optional soil layer added to Porous Pavement LID. +// - Rooftop Disconnection added to types of LIDs. +// - Separate accounting of drain flows added. +// - Indicator for currently wet LIDs added. +// - Detailed reporting procedure fixed. +// - Possibile negative head on Bioretention Cell drain avoided. +// - Bug in computing flow through Green Roof drainage mat fixed. +// Build 5.1.009: +// - Fixed typo in net flux rate for vegetative swale LID. +// Build 5.1.010: +// - New modified version of Green-Ampt used for surface layer infiltration. +// Build 5.1.011: +// - Re-named STOR_INFIL to STOR_EXFIL and StorageInfil to StorageExfil to +// better reflect their meaning. +// - Evaporation rates from sub-surface layers reduced by fraction of +// surface that is pervious (applies to block paver systems) +// - Flux rate routines for LIDs with underdrains modified to produce more +// physically meaningful results. +// - Reporting of detailed results re-written. +// Build 5.1.012: +// - Modified upper limit for soil layer percolation. +// - Modified upper limit on surface infiltration into rain gardens. +// - Modified upper limit on drain flow for LIDs with storage layers. +// - Used re-defined wasDry variable for LID reports to fix duplicate lines. +// Build 5.1.013: +// - Support added for open/closed head levels and multiplier v. head curve +// to control underdrain flow. +// - Support added for regenerating pavement permeability at fixed intervals. +// Build 5.1.014: +// - Fixed failure to initialize all LID layer moisture volumes to 0 before +// computing LID unit performance in lidproc_getOutflow. +// Build 5.2.0: +// - Fixed failure to account for effect of Impervious Surface Fraction on +// pavement permeability for Permeable Pavement LID +// - Fixed units conversion for pavement depth in detailed report file. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "lid.h" +#include "headers.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +#define STOPTOL 0.00328 // integration error tolerance in ft (= 1 mm) +#define MINFLOW 2.3e-8 // flow cutoff for dry conditions (= 0.001 in/hr) + +//----------------------------------------------------------------------------- +// Enumerations +//----------------------------------------------------------------------------- +enum LidLayerTypes { + SURF, // surface layer + SOIL, // soil layer + STOR, // storage layer + PAVE, // pavement layer + DRAIN}; // underdrain system + +enum LidRptVars { + SURF_INFLOW, // inflow to surface layer + TOTAL_EVAP, // evaporation rate from all layers + SURF_INFIL, // infiltration into surface layer + PAVE_PERC, // percolation through pavement layer + SOIL_PERC, // percolation through soil layer + STOR_EXFIL, // exfiltration out of storage layer + SURF_OUTFLOW, // outflow from surface layer + STOR_DRAIN, // outflow from storage layer + SURF_DEPTH, // ponded depth on surface layer + PAVE_DEPTH, // water level in pavement layer + SOIL_MOIST, // moisture content of soil layer + STOR_DEPTH, // water level in storage layer + MAX_RPT_VARS}; + +//----------------------------------------------------------------------------- +// Imported variables +//----------------------------------------------------------------------------- +extern char HasWetLids; // TRUE if any LIDs are wet (declared in runoff.c) + +//----------------------------------------------------------------------------- +// Local Variables +//----------------------------------------------------------------------------- +static TLidUnit* theLidUnit; // ptr. to a subcatchment's LID unit +static TLidProc* theLidProc; // ptr. to a LID process + +static double Tstep; // current time step (sec) +static double EvapRate; // evaporation rate (ft/s) +static double MaxNativeInfil; // native soil infil. rate limit (ft/s) + +static double SurfaceInflow; // precip. + runon to LID unit (ft/s) +static double SurfaceInfil; // infil. rate from surface layer (ft/s) +static double SurfaceEvap; // evap. rate from surface layer (ft/s) +static double SurfaceOutflow; // outflow from surface layer (ft/s) +static double SurfaceVolume; // volume in surface storage (ft) + +static double PaveEvap; // evap. from pavement layer (ft/s) +static double PavePerc; // percolation from pavement layer (ft/s) +static double PaveVolume; // volume stored in pavement layer (ft) + +static double SoilEvap; // evap. from soil layer (ft/s) +static double SoilPerc; // percolation from soil layer (ft/s) +static double SoilVolume; // volume in soil/pavement storage (ft) + +static double StorageInflow; // inflow rate to storage layer (ft/s) +static double StorageExfil; // exfil. rate from storage layer (ft/s) +static double StorageEvap; // evap.rate from storage layer (ft/s) +static double StorageDrain; // underdrain flow rate layer (ft/s) +static double StorageVolume; // volume in storage layer (ft) + +static double Xold[MAX_LAYERS]; // previous moisture level in LID layers + +//----------------------------------------------------------------------------- +// External Functions (declared in lid.h) +//----------------------------------------------------------------------------- +// lidproc_initWaterBalance (called by lid_initState) +// lidproc_getOutflow (called by evalLidUnit in lid.c) +// lidproc_saveResults (called by evalLidUnit in lid.c) + +//----------------------------------------------------------------------------- +// Local Functions +//----------------------------------------------------------------------------- +static void barrelFluxRates(double x[], double f[]); +static void biocellFluxRates(double x[], double f[]); +static void greenRoofFluxRates(double x[], double f[]); +static void pavementFluxRates(double x[], double f[]); +static void trenchFluxRates(double x[], double f[]); +static void swaleFluxRates(double x[], double f[]); +static void roofFluxRates(double x[], double f[]); + +static double getSurfaceOutflowRate(double depth); +static double getSurfaceOverflowRate(double* surfaceDepth); +static double getPavementPermRate(void); +static double getSoilPercRate(double theta); +static double getStorageExfilRate(void); +static double getStorageDrainRate(double storageDepth, double soilTheta, + double paveDepth, double surfaceDepth); +static double getDrainMatOutflow(double depth); +static void getEvapRates(double surfaceVol, double paveVol, + double soilVol, double storageVol, double pervFrac); + +static void updateWaterBalance(TLidUnit *lidUnit, double inflow, + double evap, double infil, double surfFlow, + double drainFlow, double storage); + +static int modpuls_solve(int n, double* x, double* xOld, double* xPrev, + double* xMin, double* xMax, double* xTol, + double* qOld, double* q, double dt, double omega, + void (*derivs)(double*, double*)); + +//============================================================================= + +void lidproc_initWaterBalance(TLidUnit *lidUnit, double initVol) +// +// Purpose: initializes the water balance components of a LID unit. +// Input: lidUnit = a particular LID unit +// initVol = initial water volume stored in the unit (ft) +// Output: none +// +{ + lidUnit->waterBalance.inflow = 0.0; + lidUnit->waterBalance.evap = 0.0; + lidUnit->waterBalance.infil = 0.0; + lidUnit->waterBalance.surfFlow = 0.0; + lidUnit->waterBalance.drainFlow = 0.0; + lidUnit->waterBalance.initVol = initVol; + lidUnit->waterBalance.finalVol = initVol; +} + +//============================================================================= + +double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, double inflow, + double evap, double infil, double maxInfil, + double tStep, double* lidEvap, + double* lidInfil, double* lidDrain) +// +// Purpose: computes runoff outflow from a single LID unit. +// Input: lidUnit = ptr. to specific LID unit being analyzed +// lidProc = ptr. to generic LID process of the LID unit +// inflow = runoff rate captured by LID unit (ft/s) +// evap = potential evaporation rate (ft/s) +// infil = infiltration rate to native soil (ft/s) +// maxInfil = max. infiltration rate to native soil (ft/s) +// tStep = time step (sec) +// Output: lidEvap = evaporation rate for LID unit (ft/s) +// lidInfil = infiltration rate for LID unit (ft/s) +// lidDrain = drain flow for LID unit (ft/s) +// returns surface runoff rate from the LID unit (ft/s) +// +{ + int i; + double x[MAX_LAYERS]; // layer moisture levels + double xOld[MAX_LAYERS]; // work vector + double xPrev[MAX_LAYERS]; // work vector + double xMin[MAX_LAYERS]; // lower limit on moisture levels + double xMax[MAX_LAYERS]; // upper limit on moisture levels + double fOld[MAX_LAYERS]; // previously computed flux rates + double f[MAX_LAYERS]; // newly computed flux rates + + // convergence tolerance on moisture levels (ft, moisture fraction , ft) + double xTol[MAX_LAYERS] = {STOPTOL, STOPTOL, STOPTOL, STOPTOL}; + + double omega = 0.0; // integration time weighting + + //... define a pointer to function that computes flux rates through the LID + void (*fluxRates) (double *, double *) = NULL; + + //... save references to the LID process and LID unit + theLidProc = lidProc; + theLidUnit = lidUnit; + + //... save evap, max. infil. & time step to shared variables + EvapRate = evap; + MaxNativeInfil = maxInfil; + Tstep = tStep; + + //... store current moisture levels in vector x + x[SURF] = theLidUnit->surfaceDepth; + x[SOIL] = theLidUnit->soilMoisture; + x[STOR] = theLidUnit->storageDepth; + x[PAVE] = theLidUnit->paveDepth; + + //... initialize layer moisture volumes, flux rates and moisture limits + SurfaceVolume = 0.0; + PaveVolume = 0.0; + SoilVolume = 0.0; + StorageVolume = 0.0; + SurfaceInflow = inflow; + SurfaceInfil = 0.0; + SurfaceEvap = 0.0; + SurfaceOutflow = 0.0; + PaveEvap = 0.0; + PavePerc = 0.0; + SoilEvap = 0.0; + SoilPerc = 0.0; + StorageInflow = 0.0; + StorageExfil = 0.0; + StorageEvap = 0.0; + StorageDrain = 0.0; + for (i = 0; i < MAX_LAYERS; i++) + { + f[i] = 0.0; + fOld[i] = theLidUnit->oldFluxRates[i]; + xMin[i] = 0.0; + xMax[i] = BIG; + Xold[i] = x[i]; + } + + //... find Green-Ampt infiltration from surface layer + if ( theLidProc->lidType == POROUS_PAVEMENT ) SurfaceInfil = 0.0; + else if ( theLidUnit->soilInfil.Ks > 0.0 ) + { + SurfaceInfil = + grnampt_getInfil(&theLidUnit->soilInfil, Tstep, + SurfaceInflow, theLidUnit->surfaceDepth, + MOD_GREEN_AMPT); + } + else SurfaceInfil = infil; + + //... set moisture limits for soil & storage layers + if ( theLidProc->soil.thickness > 0.0 ) + { + xMin[SOIL] = theLidProc->soil.wiltPoint; + xMax[SOIL] = theLidProc->soil.porosity; + } + if ( theLidProc->pavement.thickness > 0.0 ) + { + xMax[PAVE] = theLidProc->pavement.thickness; + } + if ( theLidProc->storage.thickness > 0.0 ) + { + xMax[STOR] = theLidProc->storage.thickness; + } + if ( theLidProc->lidType == GREEN_ROOF ) + { + xMax[STOR] = theLidProc->drainMat.thickness; + } + + //... determine which flux rate function to use + switch (theLidProc->lidType) + { + case BIO_CELL: + case RAIN_GARDEN: fluxRates = &biocellFluxRates; break; + case GREEN_ROOF: fluxRates = &greenRoofFluxRates; break; + case INFIL_TRENCH: fluxRates = &trenchFluxRates; break; + case POROUS_PAVEMENT: fluxRates = &pavementFluxRates; break; + case RAIN_BARREL: fluxRates = &barrelFluxRates; break; + case ROOF_DISCON: fluxRates = &roofFluxRates; break; + case VEG_SWALE: fluxRates = &swaleFluxRates; + omega = 0.5; + break; + default: return 0.0; + } + + //... update moisture levels and flux rates over the time step + i = modpuls_solve(MAX_LAYERS, x, xOld, xPrev, xMin, xMax, xTol, + fOld, f, tStep, omega, fluxRates); + +/** For debugging only ******************************************** + if (i == 0) + { + fprintf(Frpt.file, + "\n WARNING 09: integration failed to converge at %s %s", + theDate, theTime); + fprintf(Frpt.file, + "\n for LID %s placed in subcatchment %s.", + theLidProc->ID, theSubcatch->ID); + } +*******************************************************************/ + + //... add any surface overflow to surface outflow + if ( theLidProc->surface.canOverflow || theLidUnit->fullWidth == 0.0 ) + { + SurfaceOutflow += getSurfaceOverflowRate(&x[SURF]); + } + + //... save updated results + theLidUnit->surfaceDepth = x[SURF]; + theLidUnit->paveDepth = x[PAVE]; + theLidUnit->soilMoisture = x[SOIL]; + theLidUnit->storageDepth = x[STOR]; + for (i = 0; i < MAX_LAYERS; i++) theLidUnit->oldFluxRates[i] = f[i]; + + //... assign values to LID unit evaporation, infiltration & drain flow + *lidEvap = SurfaceEvap + PaveEvap + SoilEvap + StorageEvap; + *lidInfil = StorageExfil; + *lidDrain = StorageDrain; + + //... return surface outflow (per unit area) from unit + return SurfaceOutflow; +} + +//============================================================================= + +void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, double ucfRainDepth) +// +// Purpose: updates the mass balance for an LID unit and saves +// current flux rates to the LID report file. +// Input: lidUnit = ptr. to LID unit +// ucfRainfall = units conversion factor for rainfall rate +// ucfDepth = units conversion factor for rainfall depth +// Output: none +// +{ + double ucf; // units conversion factor + double totalEvap; // total evaporation rate (ft/s) + double totalVolume; // total volume stored in LID (ft) + double rptVars[MAX_RPT_VARS]; // array of reporting variables + int isDry = FALSE; // true if current state of LID is dry + char timeStamp[TIME_STAMP_SIZE + 1]; // date/time stamp + double elapsedHrs; // elapsed hours + + //... find total evap. rate and stored volume + totalEvap = SurfaceEvap + PaveEvap + SoilEvap + StorageEvap; + totalVolume = SurfaceVolume + PaveVolume + SoilVolume + StorageVolume; + + //... update mass balance totals + updateWaterBalance(theLidUnit, SurfaceInflow, totalEvap, StorageExfil, + SurfaceOutflow, StorageDrain, totalVolume); + + //... check if dry-weather conditions hold + if ( SurfaceInflow < MINFLOW && + SurfaceOutflow < MINFLOW && + StorageDrain < MINFLOW && + StorageExfil < MINFLOW && + totalEvap < MINFLOW + ) isDry = TRUE; + + //... update status of HasWetLids + if ( !isDry ) HasWetLids = TRUE; + + //... write results to LID report file + if ( lidUnit->rptFile ) + { + //... convert rate results to original units (in/hr or mm/hr) + ucf = ucfRainfall; + rptVars[SURF_INFLOW] = SurfaceInflow*ucf; + rptVars[TOTAL_EVAP] = totalEvap*ucf; + rptVars[SURF_INFIL] = SurfaceInfil*ucf; + rptVars[PAVE_PERC] = PavePerc*ucf; + rptVars[SOIL_PERC] = SoilPerc*ucf; + rptVars[STOR_EXFIL] = StorageExfil*ucf; + rptVars[SURF_OUTFLOW] = SurfaceOutflow*ucf; + rptVars[STOR_DRAIN] = StorageDrain*ucf; + + //... convert storage results to original units (in or mm) + ucf = ucfRainDepth; + rptVars[SURF_DEPTH] = theLidUnit->surfaceDepth*ucf; + rptVars[PAVE_DEPTH] = theLidUnit->paveDepth*ucf; + rptVars[SOIL_MOIST] = theLidUnit->soilMoisture; + rptVars[STOR_DEPTH] = theLidUnit->storageDepth*ucf; + + //... if the current LID state is wet but the previous state was dry + // for more than one period then write the saved previous results + // to the report file thus marking the end of a dry period + if ( !isDry && theLidUnit->rptFile->wasDry > 1) + { + fprintf(theLidUnit->rptFile->file, "%s", + theLidUnit->rptFile->results); + } + + //... write the current results to a string which is saved between + // reporting periods + elapsedHrs = NewRunoffTime / 1000.0 / 3600.0; + datetime_getTimeStamp( + M_D_Y, getDateTime(NewRunoffTime), TIME_STAMP_SIZE, timeStamp); + snprintf(theLidUnit->rptFile->results, sizeof(theLidUnit->rptFile->results), + "\n%20s\t %8.3f\t %8.3f\t %8.4f\t %8.3f\t %8.3f\t %8.3f\t %8.3f\t" + "%8.3f\t %8.3f\t %8.3f\t %8.3f\t %8.3f\t %8.3f", + timeStamp, elapsedHrs, rptVars[0], rptVars[1], rptVars[2], + rptVars[3], rptVars[4], rptVars[5], rptVars[6], rptVars[7], + rptVars[8], rptVars[9], rptVars[10], rptVars[11]); + + //... if the current LID state is dry + if ( isDry ) + { + //... if the previous state was wet then write the current + // results to file marking the start of a dry period + if ( theLidUnit->rptFile->wasDry == 0 ) + { + fprintf(theLidUnit->rptFile->file, "%s", + theLidUnit->rptFile->results); + } + + //... increment the number of successive dry periods + theLidUnit->rptFile->wasDry++; + } + + //... if the current LID state is wet + else + { + //... write the current results to the report file + fprintf(theLidUnit->rptFile->file, "%s", + theLidUnit->rptFile->results); + + //... re-set the number of successive dry periods to 0 + theLidUnit->rptFile->wasDry = 0; + } + } +} + +//============================================================================= + +void roofFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates for roof disconnection. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + double surfaceDepth = x[SURF]; + + getEvapRates(surfaceDepth, 0.0, 0.0, 0.0, 1.0); + SurfaceVolume = surfaceDepth; + SurfaceInfil = 0.0; + if ( theLidProc->surface.alpha > 0.0 ) + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + else getSurfaceOverflowRate(&surfaceDepth); + StorageDrain = MIN(theLidProc->drain.coeff/UCF(RAINFALL), SurfaceOutflow); + SurfaceOutflow -= StorageDrain; + f[SURF] = (SurfaceInflow - SurfaceEvap - StorageDrain - SurfaceOutflow); +} + +//============================================================================= + +void greenRoofFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates from the layers of a green roof. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + // Moisture level variables + double surfaceDepth; + double soilTheta; + double storageDepth; + + // Intermediate variables + double availVolume; + double maxRate; + + // Green roof properties + double soilThickness = theLidProc->soil.thickness; + double storageThickness = theLidProc->storage.thickness; + double soilPorosity = theLidProc->soil.porosity; + double storageVoidFrac = theLidProc->storage.voidFrac; + double soilFieldCap = theLidProc->soil.fieldCap; + double soilWiltPoint = theLidProc->soil.wiltPoint; + + //... retrieve moisture levels from input vector + surfaceDepth = x[SURF]; + soilTheta = x[SOIL]; + storageDepth = x[STOR]; + + //... convert moisture levels to volumes + SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; + SoilVolume = soilTheta * soilThickness; + StorageVolume = storageDepth * storageVoidFrac; + + //... get ET rates + availVolume = SoilVolume - soilWiltPoint * soilThickness; + getEvapRates(SurfaceVolume, 0.0, availVolume, StorageVolume, 1.0); + if ( soilTheta >= soilPorosity ) StorageEvap = 0.0; + + //... soil layer perc rate + SoilPerc = getSoilPercRate(soilTheta); + + //... limit perc rate by available water + availVolume = (soilTheta - soilFieldCap) * soilThickness; + maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; + SoilPerc = MIN(SoilPerc, maxRate); + SoilPerc = MAX(SoilPerc, 0.0); + + //... storage (drain mat) outflow rate + StorageExfil = 0.0; + StorageDrain = getDrainMatOutflow(storageDepth); + + //... unit is full + if ( soilTheta >= soilPorosity && storageDepth >= storageThickness ) + { + //... outflow from both layers equals limiting rate + maxRate = MIN(SoilPerc, StorageDrain); + SoilPerc = maxRate; + StorageDrain = maxRate; + + //... adjust inflow rate to soil layer + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... unit not full + else + { + //... limit drainmat outflow by available storage volume + maxRate = storageDepth * storageVoidFrac / Tstep - StorageEvap; + if ( storageDepth >= storageThickness ) maxRate += SoilPerc; + maxRate = MAX(maxRate, 0.0); + StorageDrain = MIN(StorageDrain, maxRate); + + //... limit soil perc inflow by unused storage volume + maxRate = (storageThickness - storageDepth) * storageVoidFrac / Tstep + + StorageDrain + StorageEvap; + SoilPerc = MIN(SoilPerc, maxRate); + + //... adjust surface infil. so soil porosity not exceeded + maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + + SoilPerc + SoilEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + // ... find surface outflow rate + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + + // ... compute overall layer flux rates + f[SURF] = (SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow) / + theLidProc->surface.voidFrac; + f[SOIL] = (SurfaceInfil - SoilEvap - SoilPerc) / + theLidProc->soil.thickness; + f[STOR] = (SoilPerc - StorageEvap - StorageDrain) / + theLidProc->storage.voidFrac; +} + +//============================================================================= + +void biocellFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates from the layers of a bio-retention cell LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + // Moisture level variables + double surfaceDepth; + double soilTheta; + double storageDepth; + + // Intermediate variables + double availVolume; + double maxRate; + + // LID layer properties + double soilThickness = theLidProc->soil.thickness; + double soilPorosity = theLidProc->soil.porosity; + double soilFieldCap = theLidProc->soil.fieldCap; + double soilWiltPoint = theLidProc->soil.wiltPoint; + double storageThickness = theLidProc->storage.thickness; + double storageVoidFrac = theLidProc->storage.voidFrac; + + //... retrieve moisture levels from input vector + surfaceDepth = x[SURF]; + soilTheta = x[SOIL]; + storageDepth = x[STOR]; + + //... convert moisture levels to volumes + SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; + SoilVolume = soilTheta * soilThickness; + StorageVolume = storageDepth * storageVoidFrac; + + //... get ET rates + availVolume = SoilVolume - soilWiltPoint * soilThickness; + getEvapRates(SurfaceVolume, 0.0, availVolume, StorageVolume, 1.0); + if ( soilTheta >= soilPorosity ) StorageEvap = 0.0; + + //... soil layer perc rate + SoilPerc = getSoilPercRate(soilTheta); + + //... limit perc rate by available water + availVolume = (soilTheta - soilFieldCap) * soilThickness; + maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; + SoilPerc = MIN(SoilPerc, maxRate); + SoilPerc = MAX(SoilPerc, 0.0); + + //... exfiltration rate out of storage layer + StorageExfil = getStorageExfilRate(); + + //... underdrain flow rate + StorageDrain = 0.0; + if ( theLidProc->drain.coeff > 0.0 ) + { + StorageDrain = getStorageDrainRate(storageDepth, soilTheta, 0.0, + surfaceDepth); + } + + //... special case of no storage layer present + if ( storageThickness == 0.0 ) + { + StorageEvap = 0.0; + maxRate = MIN(SoilPerc, StorageExfil); + SoilPerc = maxRate; + StorageExfil = maxRate; + + //... limit surface infil. by unused soil volume + maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + + SoilPerc + SoilEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... storage & soil layers are full + else if ( soilTheta >= soilPorosity && storageDepth >= storageThickness ) + { + //... limiting rate is smaller of soil perc and storage outflow + maxRate = StorageExfil + StorageDrain; + if ( SoilPerc < maxRate ) + { + maxRate = SoilPerc; + if ( maxRate > StorageExfil ) StorageDrain = maxRate - StorageExfil; + else + { + StorageExfil = maxRate; + StorageDrain = 0.0; + } + } + else SoilPerc = maxRate; + + //... apply limiting rate to surface infil. + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... either layer not full + else if ( storageThickness > 0.0 ) + { + //... limit storage exfiltration by available storage volume + maxRate = SoilPerc - StorageEvap + storageDepth*storageVoidFrac/Tstep; + StorageExfil = MIN(StorageExfil, maxRate); + StorageExfil = MAX(StorageExfil, 0.0); + + //... limit underdrain flow by volume above drain offset + if ( StorageDrain > 0.0 ) + { + maxRate = -StorageExfil - StorageEvap; + if ( storageDepth >= storageThickness) maxRate += SoilPerc; + if ( theLidProc->drain.offset <= storageDepth ) + { + maxRate += (storageDepth - theLidProc->drain.offset) * + storageVoidFrac/Tstep; + } + maxRate = MAX(maxRate, 0.0); + StorageDrain = MIN(StorageDrain, maxRate); + } + + //... limit soil perc by unused storage volume + maxRate = StorageExfil + StorageDrain + StorageEvap + + (storageThickness - storageDepth) * + storageVoidFrac/Tstep; + SoilPerc = MIN(SoilPerc, maxRate); + + //... limit surface infil. by unused soil volume + maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + + SoilPerc + SoilEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... find surface layer outflow rate + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + + //... compute overall layer flux rates + f[SURF] = (SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow) / + theLidProc->surface.voidFrac; + f[SOIL] = (SurfaceInfil - SoilEvap - SoilPerc) / + theLidProc->soil.thickness; + if ( storageThickness == 0.0 ) f[STOR] = 0.0; + else f[STOR] = (SoilPerc - StorageEvap - StorageExfil - StorageDrain) / + theLidProc->storage.voidFrac; +} + +//============================================================================= + +void trenchFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates from the layers of an infiltration trench LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + // Moisture level variables + double surfaceDepth; + double storageDepth; + + // Intermediate variables + double availVolume; + double maxRate; + + // Storage layer properties + double storageThickness = theLidProc->storage.thickness; + double storageVoidFrac = theLidProc->storage.voidFrac; + + //... retrieve moisture levels from input vector + surfaceDepth = x[SURF]; + storageDepth = x[STOR]; + + //... convert moisture levels to volumes + SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; + SoilVolume = 0.0; + StorageVolume = storageDepth * storageVoidFrac; + + //... get ET rates + availVolume = (storageThickness - storageDepth) * storageVoidFrac; + getEvapRates(SurfaceVolume, 0.0, 0.0, StorageVolume, 1.0); + + //... no storage evap if surface ponded + if ( surfaceDepth > 0.0 ) StorageEvap = 0.0; + + //... nominal storage inflow + StorageInflow = SurfaceInflow + SurfaceVolume / Tstep; + + //... exfiltration rate out of storage layer + StorageExfil = getStorageExfilRate(); + + //... underdrain flow rate + StorageDrain = 0.0; + if ( theLidProc->drain.coeff > 0.0 ) + { + StorageDrain = getStorageDrainRate(storageDepth, 0.0, 0.0, surfaceDepth); + } + + //... limit storage exfiltration by available storage volume + maxRate = StorageInflow - StorageEvap + storageDepth*storageVoidFrac/Tstep; + StorageExfil = MIN(StorageExfil, maxRate); + StorageExfil = MAX(StorageExfil, 0.0); + + //... limit underdrain flow by volume above drain offset + if ( StorageDrain > 0.0 ) + { + maxRate = -StorageExfil - StorageEvap; + if (storageDepth >= storageThickness ) maxRate += StorageInflow; + if ( theLidProc->drain.offset <= storageDepth ) + { + maxRate += (storageDepth - theLidProc->drain.offset) * + storageVoidFrac/Tstep; + } + maxRate = MAX(maxRate, 0.0); + StorageDrain = MIN(StorageDrain, maxRate); + } + + //... limit storage inflow to not exceed storage layer capacity + maxRate = (storageThickness - storageDepth)*storageVoidFrac/Tstep + + StorageExfil + StorageEvap + StorageDrain; + StorageInflow = MIN(StorageInflow, maxRate); + + //... equate surface infil to storage inflow + SurfaceInfil = StorageInflow; + + //... find surface outflow rate + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + + // ... find net fluxes for each layer + f[SURF] = SurfaceInflow - SurfaceEvap - StorageInflow - SurfaceOutflow / + theLidProc->surface.voidFrac;; + f[STOR] = (StorageInflow - StorageEvap - StorageExfil - StorageDrain) / + theLidProc->storage.voidFrac; + f[SOIL] = 0.0; +} + +//============================================================================= + +void pavementFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates for the layers of a porous pavement LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + //... Moisture level variables + double surfaceDepth; + double paveDepth; + double soilTheta; + double storageDepth; + + //... Intermediate variables + double pervFrac = (1.0 - theLidProc->pavement.impervFrac); + double storageInflow; // inflow rate to storage layer (ft/s) + double availVolume; + double maxRate; + + //... LID layer properties + double paveVoidFrac = theLidProc->pavement.voidFrac * pervFrac; + double paveThickness = theLidProc->pavement.thickness; + double soilThickness = theLidProc->soil.thickness; + double soilPorosity = theLidProc->soil.porosity; + double soilFieldCap = theLidProc->soil.fieldCap; + double soilWiltPoint = theLidProc->soil.wiltPoint; + double storageThickness = theLidProc->storage.thickness; + double storageVoidFrac = theLidProc->storage.voidFrac; + + //... retrieve moisture levels from input vector + surfaceDepth = x[SURF]; + paveDepth = x[PAVE]; + soilTheta = x[SOIL]; + storageDepth = x[STOR]; + + //... convert moisture levels to volumes + SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; + PaveVolume = paveDepth * paveVoidFrac; + SoilVolume = soilTheta * soilThickness; + StorageVolume = storageDepth * storageVoidFrac; + + //... get ET rates + availVolume = SoilVolume - soilWiltPoint * soilThickness; + getEvapRates(SurfaceVolume, PaveVolume, availVolume, StorageVolume, + pervFrac); + + //... no storage evap if soil or pavement layer saturated + if ( paveDepth >= paveThickness || + ( soilThickness > 0.0 && soilTheta >= soilPorosity ) + ) StorageEvap = 0.0; + + //... find nominal rate of surface infiltration into pavement layer + SurfaceInfil = SurfaceInflow + (SurfaceVolume / Tstep); + + //... find perc rate out of pavement layer + PavePerc = getPavementPermRate() * pervFrac; + + //... surface infiltration can't exceed pavement permeability + SurfaceInfil = MIN(SurfaceInfil, PavePerc); + + //... limit pavement perc by available water + maxRate = PaveVolume/Tstep + SurfaceInfil - PaveEvap; + maxRate = MAX(maxRate, 0.0); + PavePerc = MIN(PavePerc, maxRate); + + //... find soil layer perc rate + if ( soilThickness > 0.0 ) + { + SoilPerc = getSoilPercRate(soilTheta); + availVolume = (soilTheta - soilFieldCap) * soilThickness; + maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; + SoilPerc = MIN(SoilPerc, maxRate); + SoilPerc = MAX(SoilPerc, 0.0); + } + else SoilPerc = PavePerc; + + //... exfiltration rate out of storage layer + StorageExfil = getStorageExfilRate(); + + //... underdrain flow rate + StorageDrain = 0.0; + if ( theLidProc->drain.coeff > 0.0 ) + { + StorageDrain = getStorageDrainRate(storageDepth, soilTheta, paveDepth, + surfaceDepth); + } + + //... check for adjacent saturated layers + + //... no soil layer, pavement & storage layers are full + if ( soilThickness == 0.0 && + storageDepth >= storageThickness && + paveDepth >= paveThickness ) + { + //... pavement outflow can't exceed storage outflow + maxRate = StorageEvap + StorageDrain + StorageExfil; + if ( PavePerc > maxRate ) PavePerc = maxRate; + + //... storage outflow can't exceed pavement outflow + else + { + //... use up available exfiltration capacity first + StorageExfil = MIN(StorageExfil, PavePerc); + StorageDrain = PavePerc - StorageExfil; + } + + //... set soil perc to pavement perc + SoilPerc = PavePerc; + + //... limit surface infil. by pavement perc + SurfaceInfil = MIN(SurfaceInfil, PavePerc); + } + + //... pavement, soil & storage layers are full + else if ( soilThickness > 0 && + storageDepth >= storageThickness && + soilTheta >= soilPorosity && + paveDepth >= paveThickness ) + { + //... find which layer has limiting flux rate + maxRate = StorageExfil + StorageDrain; + if ( SoilPerc < maxRate) maxRate = SoilPerc; + else maxRate = MIN(maxRate, PavePerc); + + //... use up available storage exfiltration capacity first + if ( maxRate > StorageExfil ) StorageDrain = maxRate - StorageExfil; + else + { + StorageExfil = maxRate; + StorageDrain = 0.0; + } + SoilPerc = maxRate; + PavePerc = maxRate; + + //... limit surface infil. by pavement perc + SurfaceInfil = MIN(SurfaceInfil, PavePerc); + } + + //... storage & soil layers are full + else if ( soilThickness > 0.0 && + storageDepth >= storageThickness && + soilTheta >= soilPorosity ) + { + //... soil perc can't exceed storage outflow + maxRate = StorageDrain + StorageExfil; + if ( SoilPerc > maxRate ) SoilPerc = maxRate; + + //... storage outflow can't exceed soil perc + else + { + //... use up available exfiltration capacity first + StorageExfil = MIN(StorageExfil, SoilPerc); + StorageDrain = SoilPerc - StorageExfil; + } + + //... limit surface infil. by available pavement volume + availVolume = (paveThickness - paveDepth) * paveVoidFrac; + maxRate = availVolume / Tstep + PavePerc + PaveEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... soil and pavement layers are full + else if ( soilThickness > 0.0 && + paveDepth >= paveThickness && + soilTheta >= soilPorosity ) + { + PavePerc = MIN(PavePerc, SoilPerc); + SoilPerc = PavePerc; + SurfaceInfil = MIN(SurfaceInfil,PavePerc); + } + + //... no adjoining layers are full + else + { + //... limit storage exfiltration by available storage volume + // (if no soil layer, SoilPerc is same as PavePerc) + maxRate = SoilPerc - StorageEvap + StorageVolume / Tstep; + maxRate = MAX(0.0, maxRate); + StorageExfil = MIN(StorageExfil, maxRate); + + //... limit underdrain flow by volume above drain offset + if ( StorageDrain > 0.0 ) + { + maxRate = -StorageExfil - StorageEvap; + if (storageDepth >= storageThickness ) maxRate += SoilPerc; + if ( theLidProc->drain.offset <= storageDepth ) + { + maxRate += (storageDepth - theLidProc->drain.offset) * + storageVoidFrac/Tstep; + } + maxRate = MAX(maxRate, 0.0); + StorageDrain = MIN(StorageDrain, maxRate); + } + + //... limit soil & pavement outflow by unused storage volume + availVolume = (storageThickness - storageDepth) * storageVoidFrac; + maxRate = availVolume/Tstep + StorageEvap + StorageDrain + StorageExfil; + maxRate = MAX(maxRate, 0.0); + if ( soilThickness > 0.0 ) + { + SoilPerc = MIN(SoilPerc, maxRate); + maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + + SoilPerc; + } + PavePerc = MIN(PavePerc, maxRate); + + //... limit surface infil. by available pavement volume + availVolume = (paveThickness - paveDepth) * paveVoidFrac; + maxRate = availVolume / Tstep + PavePerc + PaveEvap; + SurfaceInfil = MIN(SurfaceInfil, maxRate); + } + + //... surface outflow + SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); + + //... compute overall layer flux rates + f[SURF] = SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow; + f[PAVE] = (SurfaceInfil - PaveEvap - PavePerc) / paveVoidFrac; + if ( theLidProc->soil.thickness > 0.0) + { + f[SOIL] = (PavePerc - SoilEvap - SoilPerc) / soilThickness; + storageInflow = SoilPerc; + } + else + { + f[SOIL] = 0.0; + storageInflow = PavePerc; + SoilPerc = 0.0; + } + f[STOR] = (storageInflow - StorageEvap - StorageExfil - StorageDrain) / + storageVoidFrac; +} + +//============================================================================= + +void swaleFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates from a vegetative swale LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + double depth; // depth of surface water in swale (ft) + double topWidth; // top width of full swale (ft) + double botWidth; // bottom width of swale (ft) + double length; // length of swale (ft) + double surfInflow; // inflow rate to swale (cfs) + double surfWidth; // top width at current water depth (ft) + double surfArea; // surface area of current water depth (ft2) + double flowArea; // x-section flow area (ft2) + double lidArea; // surface area of full swale (ft2) + double hydRadius; // hydraulic radius for current depth (ft) + double slope; // slope of swale side wall (run/rise) + double volume; // swale volume at current water depth (ft3) + double dVdT; // change in volume w.r.t. time (cfs) + double dStore; // depression storage depth (ft) + double xDepth; // depth above depression storage (ft) + + //... retrieve state variable from work vector + depth = x[SURF]; + depth = MIN(depth, theLidProc->surface.thickness); + + //... depression storage depth + dStore = 0.0; + + //... get swale's bottom width + // (0.5 ft minimum to avoid numerical problems) + slope = theLidProc->surface.sideSlope; + topWidth = theLidUnit->fullWidth; + topWidth = MAX(topWidth, 0.5); + botWidth = topWidth - 2.0 * slope * theLidProc->surface.thickness; + if ( botWidth < 0.5 ) + { + botWidth = 0.5; + slope = 0.5 * (topWidth - 0.5) / theLidProc->surface.thickness; + } + + //... swale's length + lidArea = theLidUnit->area; + length = lidArea / topWidth; + + //... top width, surface area and flow area of current ponded depth + surfWidth = botWidth + 2.0 * slope * depth; + surfArea = length * surfWidth; + flowArea = (depth * (botWidth + slope * depth)) * + theLidProc->surface.voidFrac; + + //... wet volume and effective depth + volume = length * flowArea; + + //... surface inflow into swale (cfs) + surfInflow = SurfaceInflow * lidArea; + + //... ET rate in cfs + SurfaceEvap = EvapRate * surfArea; + SurfaceEvap = MIN(SurfaceEvap, volume/Tstep); + + //... infiltration rate to native soil in cfs + StorageExfil = SurfaceInfil * surfArea; + + //... no surface outflow if depth below depression storage + xDepth = depth - dStore; + if ( xDepth <= ZERO ) SurfaceOutflow = 0.0; + + //... otherwise compute a surface outflow + else + { + //... modify flow area to remove depression storage, + flowArea -= (dStore * (botWidth + slope * dStore)) * + theLidProc->surface.voidFrac; + if ( flowArea < ZERO ) SurfaceOutflow = 0.0; + else + { + //... compute hydraulic radius + botWidth = botWidth + 2.0 * dStore * slope; + hydRadius = botWidth + 2.0 * xDepth * sqrt(1.0 + slope*slope); + hydRadius = flowArea / hydRadius; + + //... use Manning Eqn. to find outflow rate in cfs + SurfaceOutflow = theLidProc->surface.alpha * flowArea * + pow(hydRadius, 2./3.); + } + } + + //... net flux rate (dV/dt) in cfs + dVdT = surfInflow - SurfaceEvap - StorageExfil - SurfaceOutflow; + + //... when full, any net positive inflow becomes spillage + if ( depth == theLidProc->surface.thickness && dVdT > 0.0 ) + { + SurfaceOutflow += dVdT; + dVdT = 0.0; + } + + //... convert flux rates to ft/s + SurfaceEvap /= lidArea; + StorageExfil /= lidArea; + SurfaceOutflow /= lidArea; + f[SURF] = dVdT / surfArea; + f[SOIL] = 0.0; + f[STOR] = 0.0; + + //... assign values to layer volumes + SurfaceVolume = volume / lidArea; + SoilVolume = 0.0; + StorageVolume = 0.0; +} + +//============================================================================= + +void barrelFluxRates(double x[], double f[]) +// +// Purpose: computes flux rates for a rain barrel LID. +// Input: x = vector of storage levels +// Output: f = vector of flux rates +// +{ + double storageDepth = x[STOR]; + double head; + double maxValue; + + //... assign values to layer volumes + SurfaceVolume = 0.0; + SoilVolume = 0.0; + StorageVolume = storageDepth; + + //... initialize flows + SurfaceInfil = 0.0; + SurfaceOutflow = 0.0; + StorageDrain = 0.0; + + //... compute outflow if time since last rain exceeds drain delay + // (dryTime is updated in lid.evalLidUnit at each time step) + if ( theLidProc->drain.delay == 0.0 || + theLidUnit->dryTime >= theLidProc->drain.delay ) + { + head = storageDepth - theLidProc->drain.offset; + if ( head > 0.0 ) + { + StorageDrain = getStorageDrainRate(storageDepth, 0.0, 0.0, 0.0); + maxValue = (head/Tstep); + StorageDrain = MIN(StorageDrain, maxValue); + } + } + + //... limit inflow to available storage + StorageInflow = SurfaceInflow; + maxValue = (theLidProc->storage.thickness - storageDepth) / Tstep + + StorageDrain; + StorageInflow = MIN(StorageInflow, maxValue); + SurfaceInfil = StorageInflow; + + //... assign values to layer flux rates + f[SURF] = SurfaceInflow - StorageInflow; + f[STOR] = StorageInflow - StorageDrain; + f[SOIL] = 0.0; +} + +//============================================================================= + +double getSurfaceOutflowRate(double depth) +// +// Purpose: computes outflow rate from a LID's surface layer. +// Input: depth = depth of ponded water on surface layer (ft) +// Output: returns outflow from surface layer (ft/s) +// +// Note: this function should not be applied to swales or rain barrels. +// +{ + double delta; + double outflow; + + //... no outflow if ponded depth below storage depth + delta = depth - theLidProc->surface.thickness; + if ( delta < 0.0 ) return 0.0; + + //... compute outflow from overland flow Manning equation + outflow = theLidProc->surface.alpha * pow(delta, 5.0/3.0) * + theLidUnit->fullWidth / theLidUnit->area; + outflow = MIN(outflow, delta / Tstep); + return outflow; +} + +//============================================================================= + +double getPavementPermRate() +// +// Purpose: computes reduced permeability of a pavement layer due to +// clogging. +// Input: none +// Output: returns the reduced permeability of the pavement layer (ft/s). +// +{ + double permReduction = 0.0; + double clogFactor= theLidProc->pavement.clogFactor; + double regenDays = theLidProc->pavement.regenDays; + + // ... find permeability reduction due to clogging + if ( clogFactor > 0.0 ) + { + // ... see if permeability regeneration has occurred + // (regeneration is assumed to reduce the total + // volumetric loading that the pavement has received) + if ( regenDays > 0.0 ) + { + if ( OldRunoffTime / 1000.0 / SECperDAY >= theLidUnit->nextRegenDay ) + { + // ... reduce total volume treated by degree of regeneration + theLidUnit->volTreated *= + (1.0 - theLidProc->pavement.regenDegree); + + // ... update next day that regenration occurs + theLidUnit->nextRegenDay += regenDays; + } + } + + // ... find permeabiity reduction factor + permReduction = theLidUnit->volTreated / clogFactor; + permReduction = MIN(permReduction, 1.0); + } + + // ... return the effective pavement permeability + return theLidProc->pavement.kSat * (1.0 - permReduction); +} + +//============================================================================= + +double getSoilPercRate(double theta) +// +// Purpose: computes percolation rate of water through a LID's soil layer. +// Input: theta = moisture content (fraction) +// Output: returns percolation rate within soil layer (ft/s) +// +{ + double delta; // moisture deficit + + // ... no percolation if soil moisture <= field capacity + if ( theta <= theLidProc->soil.fieldCap ) return 0.0; + + // ... perc rate = unsaturated hydraulic conductivity + delta = theLidProc->soil.porosity - theta; + return theLidProc->soil.kSat * exp(-delta * theLidProc->soil.kSlope); + +} + +//============================================================================= + +double getStorageExfilRate() +// +// Purpose: computes exfiltration rate from storage zone into +// native soil beneath a LID. +// Input: depth = depth of water storage zone (ft) +// Output: returns infiltration rate (ft/s) +// +{ + double infil = 0.0; + double clogFactor = 0.0; + + if ( theLidProc->storage.kSat == 0.0 ) return 0.0; + if ( MaxNativeInfil == 0.0 ) return 0.0; + + //... reduction due to clogging + clogFactor = theLidProc->storage.clogFactor; + if ( clogFactor > 0.0 ) + { + clogFactor = theLidUnit->waterBalance.inflow / clogFactor; + clogFactor = MIN(clogFactor, 1.0); + } + + //... infiltration rate = storage Ksat reduced by any clogging + infil = theLidProc->storage.kSat * (1.0 - clogFactor); + + //... limit infiltration rate by any groundwater-imposed limit + return MIN(infil, MaxNativeInfil); +} + +//============================================================================= + +double getStorageDrainRate(double storageDepth, double soilTheta, + double paveDepth, double surfaceDepth) +// +// Purpose: computes underdrain flow rate in a LID's storage layer. +// Input: storageDepth = depth of water in storage layer (ft) +// soilTheta = moisture content of soil layer +// paveDepth = effective depth of water in pavement layer (ft) +// surfaceDepth = depth of ponded water on surface layer (ft) +// Output: returns flow in underdrain (ft/s) +// +// Note: drain eqn. is evaluated in user's units. +// Note: head on drain is water depth in storage layer plus the +// layers above it (soil, pavement, and surface in that order) +// minus the drain outlet offset. +{ + int curve = theLidProc->drain.qCurve; + double head = storageDepth; + double outflow = 0.0; + double paveThickness = theLidProc->pavement.thickness; + double soilThickness = theLidProc->soil.thickness; + double soilPorosity = theLidProc->soil.porosity; + double soilFieldCap = theLidProc->soil.fieldCap; + double storageThickness = theLidProc->storage.thickness; + + // --- storage layer is full + if ( storageDepth >= storageThickness ) + { + // --- a soil layer exists + if ( soilThickness > 0.0 ) + { + // --- increase head by fraction of soil layer saturated + if ( soilTheta > soilFieldCap ) + { + head += (soilTheta - soilFieldCap) / + (soilPorosity - soilFieldCap) * soilThickness; + + // --- soil layer is saturated, increase head by water + // depth in layer above it + if ( soilTheta >= soilPorosity ) + { + if ( paveThickness > 0.0 ) head += paveDepth; + else head += surfaceDepth; + } + } + } + + // --- no soil layer so increase head by water level in pavement + // layer and possibly surface layer + if ( paveThickness > 0.0 ) + { + head += paveDepth; + if ( paveDepth >= paveThickness ) head += surfaceDepth; + } + } + + // --- no outflow if: + // a) no prior outflow and head below open threshold + // b) prior outflow and head below closed threshold + if ( theLidUnit->oldDrainFlow == 0.0 && + head <= theLidProc->drain.hOpen ) return 0.0; + if ( theLidUnit->oldDrainFlow > 0.0 && + head <= theLidProc->drain.hClose ) return 0.0; + + // --- make head relative to drain offset + head -= theLidProc->drain.offset; + + // --- compute drain outflow from underdrain flow equation in user units + // (head in inches or mm, flow rate in in/hr or mm/hr) + if ( head > ZERO ) + { + // --- convert head to user units + head *= UCF(RAINDEPTH); + + // --- compute drain outflow in user units + outflow = theLidProc->drain.coeff * + pow(head, theLidProc->drain.expon); + + // --- apply user-supplied control curve to outflow + if (curve >= 0) outflow *= table_lookup(&Curve[curve], head); + + // --- convert outflow to ft/s + outflow /= UCF(RAINFALL); + } + return outflow; +} + +//============================================================================= + +double getDrainMatOutflow(double depth) +// +// Purpose: computes flow rate through a green roof's drainage mat. +// Input: depth = depth of water in drainage mat (ft) +// Output: returns flow in drainage mat (ft/s) +// +{ + //... default is to pass all inflow + double result = SoilPerc; + + //... otherwise use Manning eqn. if its parameters were supplied + if ( theLidProc->drainMat.alpha > 0.0 ) + { + result = theLidProc->drainMat.alpha * pow(depth, 5.0/3.0) * + theLidUnit->fullWidth / theLidUnit->area * + theLidProc->drainMat.voidFrac; + } + return result; +} + +//============================================================================= + +void getEvapRates(double surfaceVol, double paveVol, double soilVol, + double storageVol, double pervFrac) +// +// Purpose: computes surface, pavement, soil, and storage evaporation rates. +// Input: surfaceVol = volume/area of ponded water on surface layer (ft) +// paveVol = volume/area of water in pavement pores (ft) +// soilVol = volume/area of water in soil (or pavement) pores (ft) +// storageVol = volume/area of water in storage layer (ft) +// pervFrac = fraction of surface layer that is pervious +// Output: none +// +{ + double availEvap; + + //... surface evaporation flux + availEvap = EvapRate; + SurfaceEvap = MIN(availEvap, surfaceVol/Tstep); + SurfaceEvap = MAX(0.0, SurfaceEvap); + availEvap = MAX(0.0, (availEvap - SurfaceEvap)); + availEvap *= pervFrac; + + //... no subsurface evap if water is infiltrating + if ( SurfaceInfil > 0.0 ) + { + PaveEvap = 0.0; + SoilEvap = 0.0; + StorageEvap = 0.0; + } + else + { + //... pavement evaporation flux + PaveEvap = MIN(availEvap, paveVol / Tstep); + availEvap = MAX(0.0, (availEvap - PaveEvap)); + + //... soil evaporation flux + SoilEvap = MIN(availEvap, soilVol / Tstep); + availEvap = MAX(0.0, (availEvap - SoilEvap)); + + //... storage evaporation flux + StorageEvap = MIN(availEvap, storageVol / Tstep); + } +} + +//============================================================================= + +double getSurfaceOverflowRate(double* surfaceDepth) +// +// Purpose: finds surface overflow rate from a LID unit. +// Input: surfaceDepth = depth of water stored in surface layer (ft) +// Output: returns the overflow rate (ft/s) +// +{ + double delta = *surfaceDepth - theLidProc->surface.thickness; + if ( delta <= 0.0 ) return 0.0; + *surfaceDepth = theLidProc->surface.thickness; + return delta * theLidProc->surface.voidFrac / Tstep; +} + +//============================================================================= + +void updateWaterBalance(TLidUnit *lidUnit, double inflow, double evap, + double infil, double surfFlow, double drainFlow, double storage) +// +// Purpose: updates components of the water mass balance for a LID unit +// over the current time step. +// Input: lidUnit = a particular LID unit +// inflow = runon + rainfall to the LID unit (ft/s) +// evap = evaporation rate from the unit (ft/s) +// infil = infiltration out the bottom of the unit (ft/s) +// surfFlow = surface runoff from the unit (ft/s) +// drainFlow = underdrain flow from the unit +// storage = volume of water stored in the unit (ft) +// Output: none +// +{ + lidUnit->volTreated += inflow * Tstep; + lidUnit->waterBalance.inflow += inflow * Tstep; + lidUnit->waterBalance.evap += evap * Tstep; + lidUnit->waterBalance.infil += infil * Tstep; + lidUnit->waterBalance.surfFlow += surfFlow * Tstep; + lidUnit->waterBalance.drainFlow += drainFlow * Tstep; + lidUnit->waterBalance.finalVol = storage; +} + +//============================================================================= + +int modpuls_solve(int n, double* x, double* xOld, double* xPrev, + double* xMin, double* xMax, double* xTol, + double* qOld, double* q, double dt, double omega, + void (*derivs)(double*, double*)) +// +// Purpose: solves system of equations dx/dt = q(x) for x at end of time step +// dt using a modified Puls method. +// Input: n = number of state variables +// x = vector of state variables +// xOld = state variable values at start of time step +// xPrev = state variable values from previous iteration +// xMin = lower limits on state variables +// xMax = upper limits on state variables +// xTol = convergence tolerances on state variables +// qOld = flux rates at start of time step +// q = flux rates at end of time step +// dt = time step (sec) +// omega = time weighting parameter (use 0 for Euler method +// or 0.5 for modified Puls method) +// derivs = pointer to function that computes flux rates q as a +// function of state variables x +// Output: returns number of steps required for convergence (or 0 if +// process doesn't converge) +// +{ + int i; + int canStop; + int steps = 1; + int maxSteps = 20; + + //... initialize state variable values + for (i=0; i 0.0 && + fabs(x[i] - xPrev[i]) > xTol[i] ) canStop = 0; + xPrev[i] = x[i]; + } + + //... return if process converges + if (canStop) return steps; + steps++; + } + + //... no convergence so return 0 + return 0; +} diff --git a/src/link.c b/src/link.c new file mode 100644 index 000000000..052a030b9 --- /dev/null +++ b/src/link.c @@ -0,0 +1,2679 @@ +//----------------------------------------------------------------------------- +// link.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 10/29/22 (Build 5.2.2) +// Author: L. Rossman +// M. Tryby (EPA) +// +// Conveyance system link functions +// +// Update History +// ============== +// Build 5.1.007: +// - Optional surcharging of weirs introduced. +// Build 5.1.008: +// - Bug in finding flow through surcharged weir fixed. +// - Bug in finding if conduit is upstrm/dnstrm full fixed. +// - Monthly conductivity adjustment applied to conduit seepage. +// - Conduit seepage limited by conduit's flow rate. +// Build 5.1.010: +// - Support added for new ROADWAY_WEIR object. +// - Time of last setting change initialized for links. +// Build 5.1.011: +// - Crest elevation of regulator links raised to downstream invert. +// - Fixed converting roadWidth weir parameter to internal units. +// - Weir shape parameter deprecated. +// - Extra geometric parameters ignored for non-conduit open rectangular +// cross sections. +// Build 5.1.012: +// - Conduit seepage rate now based on flow width, not wetted perimeter. +// - Formula for side flow weir corrected. +// - Crest length contraction adjustments corrected. +// Build 5.1.013: +// - Maximum depth adjustments made for storage units that can surcharge. +// - Support added for head-dependent weir coefficient curves. +// - Adjustment of regulator link crest offset to match downstream node invert +// now only done for Dynamic Wave flow routing. +// Build 5.1.014: +// - Conduit evap. and seepage losses initialized to 0 in conduit_initState() +// and not allowed to exceed current flow rate in conduit_getLossRate(). +// Build 5.2.0: +// - Support added for Streets and Inlets. +// - Support added for variable speed pumps. +// Build 5.2.1 +// - Warning no longer issued when conduit elevation drop < MIN_DELTA_Z. +// Build 5.2.2: +// - Warning for conduit elevation drop < MIN_DELTA_Z restored. +//----------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE + +#include +#include +#include +#include "headers.h" +#include "inlet.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +static const double MIN_DELTA_Z = 0.001; // minimum elevation change for conduit + // slopes (ft) + +//----------------------------------------------------------------------------- +// External functions (declared in funcs.h) +//----------------------------------------------------------------------------- +// link_readParams (called by parseLine in input.c) +// link_readXsectParams (called by parseLine in input.c) +// link_readLossParams (called by parseLine in input.c) +// link_validate (called by project_validate in project.c) +// link_initState (called by initObjects in swmm5.c) +// link_setOldHydState (called by routing_execute in routing.c) +// link_setOldQualState (called by routing_execute in routing.c) +// link_setTargetSetting (called by routing_execute in routing.c) +// link_setSetting (called by routing_execute in routing.c) +// link_getResults (called by output_saveLinkResults) +// link_getLength (called in dwflow.c, kinwave.c & flowrout.c) +// link_getFroude (called in dwflow.c) +// link_getInflow (called in flowrout.c & dynwave.c) +// link_setOutfallDepth (called in flowrout.c & dynwave.c) +// link_getYcrit (called by link_setOutfallDepth & in dwflow.c) +// link_getYnorm (called by conduit_initState, link_setOutfallDepth & in dwflow.c) +// link_getVelocity (called by link_getResults & stats_updateLinkStats) +// link_getPower (called by stats_updateLinkStats in stats.c) +// link_getLossRate (called in dwflow.c, kinwave.c & flowrout.c) + +//----------------------------------------------------------------------------- +// Local functions +//----------------------------------------------------------------------------- +static void link_setParams(int j, int type, int n1, int n2, int k, double x[]); +static void link_convertOffsets(int j); +static double link_getOffsetHeight(int j, double offset, double elev); + +static int conduit_readParams(int j, int k, char* tok[], int ntoks); +static void conduit_validate(int j, int k); +static void conduit_initState(int j, int k); +static void conduit_reverse(int j, int k); +static double conduit_getLength(int j); +static double conduit_getLengthFactor(int j, int k, double roughness); +static double conduit_getSlope(int j); +static double conduit_getInflow(int j); +static double conduit_getLossRate(int j, double q); + +static int pump_readParams(int j, int k, char* tok[], int ntoks); +static void pump_validate(int j, int k); +static void pump_initState(int j, int k); +static double pump_getInflow(int j); + +static int orifice_readParams(int j, int k, char* tok[], int ntoks); +static void orifice_validate(int j, int k); +static void orifice_setSetting(int j, double tstep); +static double orifice_getWeirCoeff(int j, int k, double h); +static double orifice_getInflow(int j); +static double orifice_getFlow(int j, int k, double head, double f, + int hasFlapGate); + +static int weir_readParams(int j, int k, char* tok[], int ntoks); +static void weir_validate(int j, int k); +static void weir_setSetting(int j); +static double weir_getInflow(int j); +static double weir_getOpenArea(int j, double y); +static void weir_getFlow(int j, int k, double head, double dir, + int hasFlapGate, double* q1, double* q2); +static double weir_getOrificeFlow(int j, double head, double y, double cOrif); +static double weir_getdqdh(int k, double dir, double h, double q1, double q2); + +static int outlet_readParams(int j, int k, char* tok[], int ntoks); +static double outlet_getFlow(int k, double head); +static double outlet_getInflow(int j); + + +//============================================================================= + +int link_readParams(int j, int type, int k, char* tok[], int ntoks) +// +// Input: j = link index +// type = link type code +// k = link type index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads parameters for a specific type of link from a +// tokenized line of input data. +// +{ + switch ( type ) + { + case CONDUIT: return conduit_readParams(j, k, tok, ntoks); + case PUMP: return pump_readParams(j, k, tok, ntoks); + case ORIFICE: return orifice_readParams(j, k, tok, ntoks); + case WEIR: return weir_readParams(j, k, tok, ntoks); + case OUTLET: return outlet_readParams(j, k, tok, ntoks); + default: return 0; + } +} + +//============================================================================= + +int link_readXsectParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads a link's cross section parameters from a tokenized +// line of input data. +// Formats: +// Link Shape Geom1 Geom2 Geom3 Geom4 (Barrels Culvert) +// Link IRREGULAR TransectID +// Link STREET StreetID +// +{ + int i, j, k; + double x[4]; + + // --- check for minimum number of tokens + if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); + + // --- get index of link + j = project_findObject(LINK, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + + // --- get code of xsection shape + k = findmatch(tok[1], XsectTypeWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); + + // --- assign default number of barrels to conduit + if ( Link[j].type == CONDUIT ) Conduit[Link[j].subIndex].barrels = 1; + + // --- assume link is not a culvert + Link[j].xsect.culvertCode = 0; + + // --- for irregular shape, find index of transect object + if ( k == IRREGULAR ) + { + i = project_findObject(TRANSECT, tok[2]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[2]); + Link[j].xsect.type = k; + Link[j].xsect.transect = i; + return 0; + } + + // --- for street cross section, find index of Street object + else if (k == STREET_XSECT) + { + i = project_findObject(STREET, tok[2]); + if (i < 0) return error_setInpError(ERR_NAME, tok[2]); + Link[j].xsect.type = k; + Link[j].xsect.transect = i; + return 0; + } + + else + { + // --- check that geometric parameters are present + if (ntoks < 6) return error_setInpError(ERR_ITEMS, ""); + + // --- parse max. depth & shape curve for a custom shape + if ( k == CUSTOM ) + { + if ( !getDouble(tok[2], &x[0]) || x[0] <= 0.0 ) + return error_setInpError(ERR_NUMBER, tok[2]); + i = project_findObject(CURVE, tok[3]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[3]); + Link[j].xsect.type = k; + Link[j].xsect.transect = i; + Link[j].xsect.yFull = x[0] / UCF(LENGTH); + } + + // --- parse and save geometric parameters + else for (i = 2; i <= 5; i++) + { + if ( !getDouble(tok[i], &x[i-2]) ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + + // --- ignore extra parameters for non-conduit open rectangular shapes + if ( Link[j].type != CONDUIT && k == RECT_OPEN ) + { + x[2] = 0.0; + x[3] = 0.0; + } + if ( !xsect_setParams(&Link[j].xsect, k, x, UCF(LENGTH)) ) + { + return error_setInpError(ERR_NUMBER, ""); + } + + // --- parse number of barrels if present + if ( Link[j].type == CONDUIT && ntoks >= 7 ) + { + i = atoi(tok[6]); + if ( i <= 0 ) return error_setInpError(ERR_NUMBER, tok[6]); + else Conduit[Link[j].subIndex].barrels = (char)i; + } + + // --- parse culvert code if present + if ( Link[j].type == CONDUIT && ntoks >= 8 ) + { + i = atoi(tok[7]); + if ( i < 0 ) return error_setInpError(ERR_NUMBER, tok[7]); + else Link[j].xsect.culvertCode = i; + } + } + return 0; +} + +//============================================================================= + +int link_readLossParams(char* tok[], int ntoks) +// +// Input: tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads local loss parameters for a link from a tokenized +// line of input data. +// +// Format: LinkID cInlet cOutlet cAvg FlapGate(YES/NO) SeepRate +// +{ + int i, j, k; + double x[3]; + double seepRate = 0.0; + + if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); + j = project_findObject(LINK, tok[0]); + if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); + for (i=1; i<=3; i++) + { + if ( ! getDouble(tok[i], &x[i-1]) || x[i-1] < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[i]); + } + k = 0; + if ( ntoks >= 5 ) + { + k = findmatch(tok[4], NoYesWords); + if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); + } + if ( ntoks >= 6 ) + { + if ( ! getDouble(tok[5], &seepRate) ) + return error_setInpError(ERR_NUMBER, tok[5]); + } + Link[j].cLossInlet = x[0]; + Link[j].cLossOutlet = x[1]; + Link[j].cLossAvg = x[2]; + Link[j].hasFlapGate = k; + Link[j].seepRate = seepRate / UCF(RAINFALL); + return 0; +} + +//============================================================================= + +void link_setParams(int j, int type, int n1, int n2, int k, double x[]) +// +// Input: j = link index +// type = link type code +// n1 = index of upstream node +// n2 = index of downstream node +// k = index of link's sub-type +// x = array of parameter values +// Output: none +// Purpose: sets parameters for a link. +// +{ + Link[j].node1 = n1; + Link[j].node2 = n2; + Link[j].type = type; + Link[j].subIndex = k; + Link[j].offset1 = 0.0; + Link[j].offset2 = 0.0; + Link[j].q0 = 0.0; + Link[j].qFull = 0.0; + Link[j].setting = 1.0; + Link[j].targetSetting = 1.0; + Link[j].hasFlapGate = 0; + Link[j].qLimit = 0.0; // 0 means that no limit is defined + Link[j].direction = 1; + + switch (type) + { + case CONDUIT: + Conduit[k].length = x[0] / UCF(LENGTH); + Conduit[k].modLength = Conduit[k].length; + Conduit[k].roughness = x[1]; + Link[j].offset1 = x[2] / UCF(LENGTH); + Link[j].offset2 = x[3] / UCF(LENGTH); + Link[j].q0 = x[4] / UCF(FLOW); + Link[j].qLimit = x[5] / UCF(FLOW); + break; + + case PUMP: + Pump[k].pumpCurve = (int)x[0]; + Link[j].hasFlapGate = FALSE; + Pump[k].initSetting = x[1]; + Pump[k].yOn = x[2] / UCF(LENGTH); + Pump[k].yOff = x[3] / UCF(LENGTH); + Pump[k].xMin = 0.0; + Pump[k].xMax = 0.0; + break; + + case ORIFICE: + Orifice[k].type = (int)x[0]; + Link[j].offset1 = x[1] / UCF(LENGTH); + Link[j].offset2 = Link[j].offset1; + Orifice[k].cDisch = x[2]; + Link[j].hasFlapGate = (x[3] > 0.0) ? 1 : 0; + Orifice[k].orate = x[4] * 3600.0; + break; + + case WEIR: + Weir[k].type = (int)x[0]; + Link[j].offset1 = x[1] / UCF(LENGTH); + Link[j].offset2 = Link[j].offset1; + Weir[k].cDisch1 = x[2]; + Link[j].hasFlapGate = (x[3] > 0.0) ? 1 : 0; + Weir[k].endCon = x[4]; + Weir[k].cDisch2 = x[5]; + Weir[k].canSurcharge = (int)x[6]; + Weir[k].roadWidth = x[7] / UCF(LENGTH); + Weir[k].roadSurface = (int)x[8]; + Weir[k].cdCurve = (int)x[9]; + break; + + case OUTLET: + Link[j].offset1 = x[0] / UCF(LENGTH); + Link[j].offset2 = Link[j].offset1; + Outlet[k].qCoeff = x[1]; + Outlet[k].qExpon = x[2]; + Outlet[k].qCurve = (int)x[3]; + Link[j].hasFlapGate = (x[4] > 0.0) ? 1 : 0; + Outlet[k].curveType = (int)x[5]; + + xsect_setParams(&Link[j].xsect, DUMMY, NULL, 0.0); + break; + + } +} + +//============================================================================= + +void link_validate(int j) +// +// Input: j = link index +// Output: none +// Purpose: validates a link's properties. +// +{ + int n; + + if ( LinkOffsets == ELEV_OFFSET ) link_convertOffsets(j); + switch ( Link[j].type ) + { + case CONDUIT: conduit_validate(j, Link[j].subIndex); break; + case PUMP: pump_validate(j, Link[j].subIndex); break; + case ORIFICE: orifice_validate(j, Link[j].subIndex); break; + case WEIR: weir_validate(j, Link[j].subIndex); break; + } + + // --- check if crest of regulator opening < invert of downstream node + switch ( Link[j].type ) + { + case ORIFICE: + case WEIR: + case OUTLET: + if ( Node[Link[j].node1].invertElev + Link[j].offset1 < + Node[Link[j].node2].invertElev ) + { + if (RouteModel == DW) + { + Link[j].offset1 = Node[Link[j].node2].invertElev - + Node[Link[j].node1].invertElev; + report_writeWarningMsg(WARN10b, Link[j].ID); + } + else report_writeWarningMsg(WARN10a, Link[j].ID); + } + } + + // --- force max. depth of end nodes to be >= link crown height + // at non-storage nodes + + // --- skip pumps and bottom orifices + if ( Link[j].type == PUMP || + (Link[j].type == ORIFICE && + Orifice[Link[j].subIndex].type == BOTTOM_ORIFICE) ) return; + + // --- extend upstream node's full depth to link's crown elevation + n = Link[j].node1; + if ( Node[n].type != STORAGE || Node[n].surDepth > 0.0 ) + { + Node[n].fullDepth = MAX(Node[n].fullDepth, + Link[j].offset1 + Link[j].xsect.yFull); + } + + // --- do same for downstream node only for conduit links + n = Link[j].node2; + if ( (Node[n].type != STORAGE || Node[n].surDepth > 0.0) && + Link[j].type == CONDUIT ) + { + Node[n].fullDepth = MAX(Node[n].fullDepth, + Link[j].offset2 + Link[j].xsect.yFull); + } +} + +//============================================================================= + +void link_convertOffsets(int j) +// +// Input: j = link index +// Output: none +// Purpose: converts offset elevations to offset heights for a link. +// +{ + double elev; + + elev = Node[Link[j].node1].invertElev; + Link[j].offset1 = link_getOffsetHeight(j, Link[j].offset1, elev); + if ( Link[j].type == CONDUIT ) + { + elev = Node[Link[j].node2].invertElev; + Link[j].offset2 = link_getOffsetHeight(j, Link[j].offset2, elev); + } + else Link[j].offset2 = Link[j].offset1; +} + +//============================================================================= + +double link_getOffsetHeight(int j, double offset, double elev) +// +// Input: j = link index +// offset = link elevation offset (ft) +// elev = node invert elevation (ft) +// Output: returns offset distance above node invert (ft) +// Purpose: finds offset height for one end of a link. +// +{ + if ( offset <= MISSING || Link[j].type == PUMP) return 0.0; + offset -= elev; + if ( offset >= 0.0 ) return offset; + if ( offset >= -MIN_DELTA_Z ) return 0.0; + report_writeWarningMsg(WARN03, Link[j].ID); + return 0.0; +} + +//============================================================================= + +void link_initState(int j) +// +// Input: j = link index +// Output: none +// Purpose: initializes a link's state variables at start of simulation. +// +{ + int p; + + // --- initialize hydraulic state + Link[j].oldFlow = Link[j].q0; + Link[j].newFlow = Link[j].q0; + Link[j].oldDepth = 0.0; + Link[j].newDepth = 0.0; + Link[j].oldVolume = 0.0; + Link[j].newVolume = 0.0; + Link[j].setting = 1.0; + Link[j].targetSetting = 1.0; + Link[j].timeLastSet = StartDate; + Link[j].inletControl = FALSE; + Link[j].normalFlow = FALSE; + if ( Link[j].type == CONDUIT ) conduit_initState(j, Link[j].subIndex); + if ( Link[j].type == PUMP ) pump_initState(j, Link[j].subIndex); + + // --- initialize water quality state + for (p = 0; p < Nobjects[POLLUT]; p++) + { + Link[j].oldQual[p] = 0.0; + Link[j].newQual[p] = 0.0; + Link[j].totalLoad[p] = 0.0; + } +} + +//============================================================================= + +double link_getInflow(int j) +// +// Input: j = link index +// Output: returns link flow rate (cfs) +// Purpose: finds total flow entering a link during current time step. +// +{ + if ( Link[j].setting == 0 ) return 0.0; + switch ( Link[j].type ) + { + case CONDUIT: return conduit_getInflow(j); + case PUMP: return pump_getInflow(j); + case ORIFICE: return orifice_getInflow(j); + case WEIR: return weir_getInflow(j); + case OUTLET: return outlet_getInflow(j); + default: return node_getOutflow(Link[j].node1, j); + } +} + +//============================================================================= + +void link_setOldHydState(int j) +// +// Input: j = link index +// Output: none +// Purpose: replaces link's old hydraulic state values with current ones. +// +{ + int k; + + Link[j].oldDepth = Link[j].newDepth; + Link[j].oldFlow = Link[j].newFlow; + Link[j].oldVolume = Link[j].newVolume; + + if ( Link[j].type == CONDUIT ) + { + k = Link[j].subIndex; + Conduit[k].q1Old = Conduit[k].q1; + Conduit[k].q2Old = Conduit[k].q2; + } +} + +//============================================================================= + +void link_setOldQualState(int j) +// +// Input: j = link index +// Output: none +// Purpose: replaces link's old water quality state values with current ones. +// +{ + int p; + for (p = 0; p < Nobjects[POLLUT]; p++) + { + Link[j].oldQual[p] = Link[j].newQual[p]; + Link[j].newQual[p] = 0.0; + } +} + +//============================================================================= + +void link_setTargetSetting(int j) +// +// Input: j = link index +// Output: none +// Purpose: updates a link's target setting. +// +{ + int k, n1; + if ( Link[j].type == PUMP ) + { + k = Link[j].subIndex; + n1 = Link[j].node1; + Link[j].targetSetting = Link[j].setting; + if ( Pump[k].yOff > 0.0 && + Link[j].setting > 0.0 && + Node[n1].newDepth < Pump[k].yOff ) Link[j].targetSetting = 0.0; + if ( Pump[k].yOn > 0.0 && + Link[j].setting == 0.0 && + Node[n1].newDepth > Pump[k].yOn ) Link[j].targetSetting = 1.0; + } +} + +//============================================================================= + +void link_setSetting(int j, double tstep) +// +// Input: j = link index +// tstep = time step over which setting is adjusted +// Output: none +// Purpose: updates a link's setting as a result of a control action. +// +{ + if ( Link[j].type == ORIFICE ) orifice_setSetting(j, tstep); + else if ( Link[j].type == WEIR ) weir_setSetting(j); + else Link[j].setting = Link[j].targetSetting; +} + +//============================================================================= + +int link_setFlapGate(int j, int n1, int n2, double q) +// +// Input: j = link index +// n1 = index of node on upstream end of link +// n2 = index of node on downstream end of link +// q = signed flow value (value and units don't matter) +// Output: returns TRUE if there is reverse flow through a flap gate +// associated with the link. +// Purpose: based on the sign of the flow, determines if a flap gate +// associated with the link should close or not. +// +{ + int n = -1; + + // --- check for reverse flow through link's flap gate + if ( Link[j].hasFlapGate ) + { + if ( q * (double)Link[j].direction < 0.0 ) return TRUE; + } + + // --- check for Outfall with flap gate node on inflow end of link + if ( q < 0.0 ) n = n2; + if ( q > 0.0 ) n = n1; + if ( n >= 0 && + Node[n].type == OUTFALL && + Outfall[Node[n].subIndex].hasFlapGate ) return TRUE; + return FALSE; +} + +//============================================================================= + +void link_getResults(int j, double f, float x[]) +// +// Input: j = link index +// f = time weighting factor +// Output: x = array of weighted results +// Purpose: retrieves time-weighted average of old and new results for a link. +// +{ + int p; // pollutant index + double y, // depth + q, // flow + u, // velocity + v, // volume + c; // capacity, setting or concentration + double f1 = 1.0 - f; + + y = f1*Link[j].oldDepth + f*Link[j].newDepth; + q = f1*Link[j].oldFlow + f*Link[j].newFlow; + v = f1*Link[j].oldVolume + f*Link[j].newVolume; + u = link_getVelocity(j, q, y); + c = 0.0; + if (Link[j].type == CONDUIT) + { + if (Link[j].xsect.type != DUMMY) + c = xsect_getAofY(&Link[j].xsect, y) / Link[j].xsect.aFull; + } + else c = Link[j].setting; + + // --- override time weighting for pump flow between on/off states + if (Link[j].type == PUMP && Link[j].oldFlow*Link[j].newFlow == 0.0) + { + if ( f >= f1 ) q = Link[j].newFlow; + else q = Link[j].oldFlow; + } + + y *= UCF(LENGTH); + v *= UCF(VOLUME); + q *= UCF(FLOW) * (double)Link[j].direction; + u *= UCF(LENGTH) * (double)Link[j].direction; + x[LINK_DEPTH] = (float)y; + x[LINK_FLOW] = (float)q; + x[LINK_VELOCITY] = (float)u; + x[LINK_VOLUME] = (float)v; + x[LINK_CAPACITY] = (float)c; + + if ( !IgnoreQuality ) for (p = 0; p < Nobjects[POLLUT]; p++) + { + c = f1*Link[j].oldQual[p] + f*Link[j].newQual[p]; + x[LINK_QUAL+p] = (float)c; + } +} + +//============================================================================= + +void link_setOutfallDepth(int j) +// +// Input: j = link index +// Output: none +// Purpose: sets depth at outfall node connected to link j. +// +{ + int k; // conduit index + int n; // outfall node index + double z; // invert offset height (ft) + double q; // flow rate (cfs) + double yCrit = 0.0; // critical flow depth (ft) + double yNorm = 0.0; // normal flow depth (ft) + + // --- find which end node of link is an outfall + if ( Node[Link[j].node2].type == OUTFALL ) + { + n = Link[j].node2; + z = Link[j].offset2; + } + else if ( Node[Link[j].node1].type == OUTFALL ) + { + n = Link[j].node1; + z = Link[j].offset1; + } + else return; + + // --- find both normal & critical depth for current flow + if ( Link[j].type == CONDUIT ) + { + k = Link[j].subIndex; + q = fabs(Link[j].newFlow / Conduit[k].barrels); + yNorm = link_getYnorm(j, q); + yCrit = link_getYcrit(j, q); + } + + // --- set new depth at node + node_setOutletDepth(n, yNorm, yCrit, z); +} + +//============================================================================= + +double link_getYcrit(int j, double q) +// +// Input: j = link index +// q = link flow rate (cfs) +// Output: returns critical depth (ft) +// Purpose: computes critical depth for given flow rate. +// +{ + return xsect_getYcrit(&Link[j].xsect, q); +} + +//============================================================================= + +double link_getYnorm(int j, double q) +// +// Input: j = link index +// q = link flow rate (cfs) +// Output: returns normal depth (ft) +// Purpose: computes normal depth for given flow rate. +// +{ + int k; + double s, a, y; + + if ( Link[j].type != CONDUIT ) return 0.0; + if ( Link[j].xsect.type == DUMMY ) return 0.0; + q = fabs(q); + k = Link[j].subIndex; + if ( q > Conduit[k].qMax ) q = Conduit[k].qMax; + if ( q <= 0.0 ) return 0.0; + s = q / Conduit[k].beta; + a = xsect_getAofS(&Link[j].xsect, s); + y = xsect_getYofA(&Link[j].xsect, a); + return y; +} + +//============================================================================= + +double link_getLength(int j) +// +// Input: j = link index +// Output: returns length (ft) +// Purpose: finds true length of a link. +// +{ + if ( Link[j].type == CONDUIT ) return conduit_getLength(j); + return 0.0; +} + +//============================================================================= + +double link_getVelocity(int j, double flow, double depth) +// +// Input: j = link index +// flow = link flow rate (cfs) +// depth = link flow depth (ft) +// Output: returns flow velocity (fps) +// Purpose: finds flow velocity given flow and depth. +// +{ + double area; + double veloc = 0.0; + int k; + + if ( depth <= 0.01 ) return 0.0; + if ( Link[j].type == CONDUIT ) + { + k = Link[j].subIndex; + flow /= Conduit[k].barrels; + area = xsect_getAofY(&Link[j].xsect, depth); + if (area > FUDGE ) veloc = flow / area; + } + return veloc; +} + +//============================================================================= + +double link_getFroude(int j, double v, double y) +// +// Input: j = link index +// v = flow velocity (fps) +// y = flow depth (ft) +// Output: returns Froude Number +// Purpose: computes Froude Number for given velocity and flow depth +// +{ + TXsect* xsect = &Link[j].xsect; + + // --- return 0 if link is not a conduit + if ( Link[j].type != CONDUIT ) return 0.0; + + // --- return 0 if link empty or closed conduit is full + if ( y <= FUDGE ) return 0.0; + if ( !xsect_isOpen(xsect->type) && + xsect->yFull - y <= FUDGE ) return 0.0; + + // --- compute hydraulic depth + y = xsect_getAofY(xsect, y) / xsect_getWofY(xsect, y); + + // --- compute Froude No. + return fabs(v) / sqrt(GRAVITY * y); +} + +//============================================================================= + +double link_getPower(int j) +// +// Input: j = link index +// Output: returns power consumed by link in kwatts +// Purpose: computes power consumed by head loss (or head gain) of +// water flowing through a link +// +{ + int n1 = Link[j].node1; + int n2 = Link[j].node2; + double dh = (Node[n1].invertElev + Node[n1].newDepth) - + (Node[n2].invertElev + Node[n2].newDepth); + double q = fabs(Link[j].newFlow); + return fabs(dh) * q / 8.814 * KWperHP; +} + +//============================================================================= + +double link_getLossRate(int j, double q) +// +// Input: j = link index +// q = flow rate (ft3/sec) +// tstep = time step (sec) +// Output: returns uniform loss rate in link (ft3/sec) +// Purpose: computes rate at which flow volume is lost in a link due to +// evaporation and seepage. +// +{ + if ( Link[j].type == CONDUIT ) return conduit_getLossRate(j, q); + else return 0.0; +} + +//============================================================================= + +char link_getFullState(double a1, double a2, double aFull) +// +// Input: a1 = upstream link area (ft2) +// a2 = downstream link area (ft2) +// aFull = area of full conduit +// Output: returns fullness state of a link +// Purpose: determines if a link is upstream, downstream or completely full. +// +{ + if ( a1 >= aFull ) + { + if ( a2 >= aFull ) return ALL_FULL; + else return UP_FULL; + } + if ( a2 >= aFull ) return DN_FULL; + return 0; +} + +//============================================================================= +// C O N D U I T M E T H O D S +//============================================================================= + +int conduit_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = conduit index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads conduit parameters from a tokenzed line of input. +// +{ + int n1, n2; + double x[6]; + char* id; + + // --- check for valid ID and end node IDs + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); // link ID + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); // upstrm. node + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); // dwnstrm. node + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- parse length & Mannings N + if ( !getDouble(tok[3], &x[0]) ) + return error_setInpError(ERR_NUMBER, tok[3]); + if ( !getDouble(tok[4], &x[1]) ) + return error_setInpError(ERR_NUMBER, tok[4]); + + // --- parse offsets + if ( LinkOffsets == ELEV_OFFSET && *tok[5] == '*' ) x[2] = MISSING; + else if ( !getDouble(tok[5], &x[2]) ) + return error_setInpError(ERR_NUMBER, tok[5]); + if ( LinkOffsets == ELEV_OFFSET && *tok[6] == '*' ) x[3] = MISSING; + else if ( !getDouble(tok[6], &x[3]) ) + return error_setInpError(ERR_NUMBER, tok[6]); + + // --- parse optional parameters + x[4] = 0.0; // init. flow + if ( ntoks >= 8 ) + { + if ( !getDouble(tok[7], &x[4]) ) + return error_setInpError(ERR_NUMBER, tok[7]); + } + x[5] = 0.0; + if ( ntoks >= 9 ) + { + if ( !getDouble(tok[8], &x[5]) ) + return error_setInpError(ERR_NUMBER, tok[8]); + } + + // --- add parameters to data base + Link[j].ID = id; + link_setParams(j, CONDUIT, n1, n2, k, x); + return 0; +} + +//============================================================================= + +void conduit_validate(int j, int k) +// +// Input: j = link index +// k = conduit index +// Output: none +// Purpose: validates a conduit's properties. +// +{ + double aa; + double lengthFactor, roughness, slope; + + // --- a storage node cannot have a dummy outflow link + if ( Link[j].xsect.type == DUMMY && RouteModel == DW ) + { + if ( Node[Link[j].node1].type == STORAGE ) + { + report_writeErrorMsg(ERR_DUMMY_LINK, Node[Link[j].node1].ID); + return; + } + } + + // --- if custom xsection, then set its parameters + if ( Link[j].xsect.type == CUSTOM ) + xsect_setCustomXsectParams(&Link[j].xsect); + + // --- if irreg. xsection, assign transect roughness to conduit + if ( Link[j].xsect.type == IRREGULAR ) + { + xsect_setIrregXsectParams(&Link[j].xsect); + Conduit[k].roughness = Transect[Link[j].xsect.transect].roughness; + } + + // --- if street xsection, then set its parameters + if (Link[j].xsect.type == STREET_XSECT) + { + xsect_setStreetXsectParams(&Link[j].xsect); + Conduit[k].roughness = Street[Link[j].xsect.transect].roughness; + } + + // --- if force main xsection, adjust units on D-W roughness height + if ( Link[j].xsect.type == FORCE_MAIN ) + { + if ( ForceMainEqn == D_W ) Link[j].xsect.rBot /= UCF(RAINDEPTH); + if ( Link[j].xsect.rBot <= 0.0 ) + report_writeErrorMsg(ERR_XSECT, Link[j].ID); + } + + // --- check for valid length & roughness + if ( Conduit[k].length <= 0.0 ) + report_writeErrorMsg(ERR_LENGTH, Link[j].ID); + if ( Conduit[k].roughness <= 0.0 ) + report_writeErrorMsg(ERR_ROUGHNESS, Link[j].ID); + if ( Conduit[k].barrels <= 0 ) + report_writeErrorMsg(ERR_BARRELS, Link[j].ID); + + // --- check for valid xsection + if ( Link[j].xsect.type != DUMMY ) + { + if ( Link[j].xsect.type < 0 ) + report_writeErrorMsg(ERR_NO_XSECT, Link[j].ID); + else if ( Link[j].xsect.aFull <= 0.0 ) + report_writeErrorMsg(ERR_XSECT, Link[j].ID); + } + if ( ErrorCode ) return; + + // --- check for negative offsets + if ( Link[j].offset1 < 0.0 ) + { + report_writeWarningMsg(WARN03, Link[j].ID); + Link[j].offset1 = 0.0; + } + if ( Link[j].offset2 < 0.0 ) + { + report_writeWarningMsg(WARN03, Link[j].ID); + Link[j].offset2 = 0.0; + } + + // --- adjust conduit offsets for partly filled circular xsection + if ( Link[j].xsect.type == FILLED_CIRCULAR ) + { + Link[j].offset1 += Link[j].xsect.yBot; + Link[j].offset2 += Link[j].xsect.yBot; + } + + // --- compute conduit slope + slope = conduit_getSlope(j); + Conduit[k].slope = slope; + + // --- reverse orientation of conduit if using dynamic wave routing + // and slope is negative + if ( RouteModel == DW && + slope < 0.0 && + Link[j].xsect.type != DUMMY ) + { + conduit_reverse(j, k); + } + + // --- get equivalent Manning roughness for Force Mains + // for use when pipe is partly full + roughness = Conduit[k].roughness; + if ( RouteModel == DW && Link[j].xsect.type == FORCE_MAIN ) + { + roughness = forcemain_getEquivN(j, k); + } + + // --- adjust roughness for meandering natural channels + if ( Link[j].xsect.type == IRREGULAR ) + { + lengthFactor = Transect[Link[j].xsect.transect].lengthFactor; + roughness *= sqrt(lengthFactor); + } + + // --- lengthen conduit if lengthening option is in effect + lengthFactor = 1.0; + if ( RouteModel == DW && + LengtheningStep > 0.0 && + Link[j].xsect.type != DUMMY ) + { + lengthFactor = conduit_getLengthFactor(j, k, roughness); + } + + if ( lengthFactor != 1.0 ) + { + Conduit[k].modLength = lengthFactor * conduit_getLength(j); + slope /= lengthFactor; + roughness = roughness / sqrt(lengthFactor); + } + + // --- compute roughness factor used when computing friction + // slope term in Dynamic Wave flow routing + + // --- special case for non-Manning Force Mains + // (roughness factor for full flow is saved in xsect.sBot) + if ( RouteModel == DW && Link[j].xsect.type == FORCE_MAIN ) + { + Link[j].xsect.sBot = + forcemain_getRoughFactor(j, lengthFactor); + } + Conduit[k].roughFactor = GRAVITY * SQR(roughness/PHI); + + // --- compute full flow through cross section + if ( Link[j].xsect.type == DUMMY ) Conduit[k].beta = 0.0; + else Conduit[k].beta = PHI * sqrt(fabs(slope)) / roughness; + Link[j].qFull = Link[j].xsect.sFull * Conduit[k].beta; + Conduit[k].qMax = Link[j].xsect.sMax * Conduit[k].beta; + + // --- see if flow is supercritical most of time + // by comparing normal & critical velocities. + // (factor of 0.3 is for circular pipe 95% full) + // NOTE: this factor was used in the past for a modified version of + // Kinematic Wave routing but is now deprecated. + aa = Conduit[k].beta / sqrt(32.2) * + pow(Link[j].xsect.yFull, 0.1666667) * 0.3; + if ( aa >= 1.0 ) Conduit[k].superCritical = TRUE; + else Conduit[k].superCritical = FALSE; + + // --- set value of hasLosses flag + if ( Link[j].cLossInlet == 0.0 && + Link[j].cLossOutlet == 0.0 && + Link[j].cLossAvg == 0.0 + ) Conduit[k].hasLosses = FALSE; + else Conduit[k].hasLosses = TRUE; +} + +//============================================================================= + +void conduit_reverse(int j, int k) +// +// Input: j = link index +// k = conduit index +// Output: none +// Purpose: reverses direction of a conduit +// +{ + int i; + double z; + double cLoss; + + // --- reverse end nodes + i = Link[j].node1; + Link[j].node1 = Link[j].node2; + Link[j].node2 = i; + + // --- reverse node offsets + z = Link[j].offset1; + Link[j].offset1 = Link[j].offset2; + Link[j].offset2 = z; + + // --- reverse loss coeffs. + cLoss = Link[j].cLossInlet; + Link[j].cLossInlet = Link[j].cLossOutlet; + Link[j].cLossOutlet = cLoss; + + // --- reverse direction & slope + Conduit[k].slope = -Conduit[k].slope; + Link[j].direction *= (signed char)-1; + + // --- reverse initial flow value + Link[j].q0 = -Link[j].q0; +} + +//============================================================================= + +double conduit_getLength(int j) +// +// Input: j = link index +// Output: returns conduit's length (ft) +// Purpose: finds true length of a conduit. +// +// Note: for irregular natural channels, user inputs length of main +// channel (for FEMA purposes) but program should use length +// associated with entire flood plain. Transect.lengthFactor +// is the ratio of these two lengths. +// +{ + int k = Link[j].subIndex; + int t; + if ( Link[j].xsect.type != IRREGULAR ) return Conduit[k].length; + t = Link[j].xsect.transect; + if ( t < 0 || t >= Nobjects[TRANSECT] ) return Conduit[k].length; + return Conduit[k].length / Transect[t].lengthFactor; +} + +//============================================================================= + +double conduit_getLengthFactor(int j, int k, double roughness) +// +// Input: j = link index +// k = conduit index +// roughness = conduit Manning's n +// Output: returns factor by which a conduit should be lengthened +// Purpose: computes amount of conduit lengthing to improve numerical stability. +// +// The following form of the Courant criterion is used: +// L = t * v * (1 + Fr) / Fr +// where L = conduit length, t = time step, v = velocity, & Fr = Froude No. +// After substituting Fr = v / sqrt(gy), where y = flow depth, we get: +// L = t * ( sqrt(gy) + v ) +// +{ + double ratio; + double yFull; + double vFull; + double tStep; + + // --- evaluate flow depth and velocity at full normal flow condition + yFull = Link[j].xsect.yFull; + if ( xsect_isOpen(Link[j].xsect.type) ) + { + yFull = Link[j].xsect.aFull / xsect_getWofY(&Link[j].xsect, yFull); + } + vFull = PHI / roughness * Link[j].xsect.sFull * + sqrt(fabs(Conduit[k].slope)) / Link[j].xsect.aFull; + + // --- determine ratio of Courant length to actual length + if ( LengtheningStep == 0.0 ) tStep = RouteStep; + else tStep = MIN(RouteStep, LengtheningStep); + ratio = (sqrt(GRAVITY*yFull) + vFull) * tStep / conduit_getLength(j); + + // --- return max. of 1.0 and ratio + if ( ratio > 1.0 ) return ratio; + else return 1.0; +} + +//============================================================================= + +double conduit_getSlope(int j) +// +// Input: j = link index +// Output: returns conduit slope +// Purpose: computes conduit slope. +// +{ + double elev1, elev2, delta, slope; + double length = conduit_getLength(j); + + // --- check that elevation drop > minimum allowable drop + elev1 = Link[j].offset1 + Node[Link[j].node1].invertElev; + elev2 = Link[j].offset2 + Node[Link[j].node2].invertElev; + delta = fabs(elev1 - elev2); + if ( delta < MIN_DELTA_Z ) + { + report_writeWarningMsg(WARN04, Link[j].ID); + delta = MIN_DELTA_Z; + } + + // --- elevation drop cannot exceed conduit length + if ( delta >= length ) + { + report_writeWarningMsg(WARN08, Link[j].ID); + slope = delta / length; + } + + // --- slope = elev. drop / horizontal distance + else slope = delta / sqrt(SQR(length) - SQR(delta)); + + // -- check that slope exceeds minimum allowable slope + if ( MinSlope > 0.0 && slope < MinSlope ) + { + report_writeWarningMsg(WARN05, Link[j].ID); + slope = MinSlope; + // keep min. slope positive for SF or KW routing + if (RouteModel == SF || RouteModel == KW) return slope; + } + + // --- change sign for adverse slope + if ( elev1 < elev2 ) slope = -slope; + return slope; +} + +//============================================================================= + +void conduit_initState(int j, int k) +// +// Input: j = link index +// k = conduit index +// Output: none +// Purpose: sets initial conduit depth to normal depth of initial flow +// +{ + Link[j].newDepth = link_getYnorm(j, Link[j].q0 / Conduit[k].barrels); + Link[j].oldDepth = Link[j].newDepth; + Conduit[k].evapLossRate = 0.0; + Conduit[k].seepLossRate = 0.0; +} + +//============================================================================= + +double conduit_getInflow(int j) +// +// Input: j = link index +// Output: returns flow in link (cfs) +// Purpose: finds inflow to conduit from upstream node. +// +{ + double qIn = node_getOutflow(Link[j].node1, j); + if ( Link[j].qLimit > 0.0 ) qIn = MIN(qIn, Link[j].qLimit); + return qIn; +} + +//============================================================================= + +double conduit_getLossRate(int j, double q) +// +// Input: j = link index +// q = current link flow rate (cfs) +// Output: returns rate of evaporation & seepage losses (ft3/sec) +// Purpose: computes volumetric rate of water evaporation & seepage +// from a conduit (per barrel). +// +{ + TXsect *xsect; + double depth = 0.5 * (Link[j].oldDepth + Link[j].newDepth); + double length; + double topWidth; + double evapLossRate = 0.0, + seepLossRate = 0.0, + totalLossRate = 0.0; + + if ( depth > FUDGE ) + { + xsect = &Link[j].xsect; + length = conduit_getLength(j); + + // --- find evaporation rate for open conduits + if ( xsect_isOpen(xsect->type) && Evap.rate > 0.0 ) + { + topWidth = xsect_getWofY(xsect, depth); + evapLossRate = topWidth * length * Evap.rate; + } + + // --- compute seepage loss rate + if ( Link[j].seepRate > 0.0 ) + { + // limit depth to depth at max width + if ( depth >= xsect->ywMax ) depth = xsect->ywMax; + + // compute seepage loss rate across length of conduit + seepLossRate = Link[j].seepRate * xsect_getWofY(xsect, depth) * + length; + seepLossRate *= Adjust.hydconFactor; + } + + // --- compute total loss rate + totalLossRate = evapLossRate + seepLossRate; + + // --- total loss rate cannot exceed flow rate + q = ABS(q); + if (totalLossRate > q) + { + evapLossRate = evapLossRate * q / totalLossRate; + seepLossRate = seepLossRate * q / totalLossRate; + totalLossRate = q; + } + } + + Conduit[Link[j].subIndex].evapLossRate = evapLossRate; + Conduit[Link[j].subIndex].seepLossRate = seepLossRate; + return totalLossRate; +} + + +//============================================================================= +// P U M P M E T H O D S +//============================================================================= + +int pump_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = pump index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads pump parameters from a tokenized line of input. +// +{ + int m; + int n1, n2; + double x[4]; + char* id; + + // --- check for valid ID and end node IDs + if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- parse curve name + x[0] = -1.; + if ( ntoks >= 4 ) + { + if ( !strcomp(tok[3],"*") ) + { + m = project_findObject(CURVE, tok[3]); + if ( m < 0 ) return error_setInpError(ERR_NAME, tok[3]); + x[0] = m; + } + } + + // --- parse init. status if present + x[1] = 1.0; + if ( ntoks >= 5 ) + { + m = findmatch(tok[4], OffOnWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); + x[1] = m; + } + + // --- parse startup/shutoff depths if present + x[2] = 0.0; + if ( ntoks >= 6 ) + { + if ( !getDouble(tok[5], &x[2]) || x[2] < 0.0) + return error_setInpError(ERR_NUMBER, tok[5]); + } + x[3] = 0.0; + if ( ntoks >= 7 ) + { + if ( !getDouble(tok[6], &x[3]) || x[3] < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[6]); + } + + // --- add parameters to pump object + Link[j].ID = id; + link_setParams(j, PUMP, n1, n2, k, x); + return 0; +} + +//============================================================================= + +void pump_validate(int j, int k) +// +// Input: j = link index +// k = pump index +// Output: none +// Purpose: validates a pump's properties +// +{ + int m, n1; + double x, y; + + Link[j].xsect.yFull = 0.0; + + // --- check for valid curve type + m = Pump[k].pumpCurve; + if ( m < 0 ) + { + Pump[k].type = IDEAL_PUMP; + } + else + { + if ( Curve[m].curveType < PUMP1_CURVE || + Curve[m].curveType > PUMP5_CURVE ) + report_writeErrorMsg(ERR_NO_CURVE, Link[j].ID); + + // --- store pump curve type with pump's parameters + else + { + Pump[k].type = Curve[m].curveType - PUMP1_CURVE; + if ( table_getFirstEntry(&Curve[m], &x, &y) ) + { + Link[j].qFull = y; + Pump[k].xMin = x; + Pump[k].xMax = x; + while ( table_getNextEntry(&Curve[m], &x, &y) ) + { + Link[j].qFull = MAX(y, Link[j].qFull); + Pump[k].xMax = x; + } + } + Link[j].qFull /= UCF(FLOW); + } + } + + // --- check that shutoff depth < startup depth + if ( Pump[k].yOn > 0.0 && Pump[k].yOn <= Pump[k].yOff ) + report_writeErrorMsg(ERR_PUMP_LIMITS, Link[j].ID); + + // --- assign wet well volume to inlet node of Type 1 pump + if ( Pump[k].type == TYPE1_PUMP ) + { + n1 = Link[j].node1; + if ( Node[n1].type != STORAGE ) + Node[n1].fullVolume = MAX(Node[n1].fullVolume, + Pump[k].xMax / UCF(VOLUME)); + } + +} + +//============================================================================= + +void pump_initState(int j, int k) +// +// Input: j = link index +// k = pump index +// Output: none +// Purpose: initializes pump conditions at start of a simulation +// +{ + Link[j].setting = Pump[k].initSetting; + Link[j].targetSetting = Pump[k].initSetting; +} + +//============================================================================= + +double pump_getInflow(int j) +// +// Input: j = link index +// Output: returns pump flow (cfs) +// Purpose: finds flow produced by a pump. +// +{ + int k, m; + int n1, n2; + double vol, depth, head; + double qIn, qIn1, dh = 0.001; + double s = 1.0; // speed setting + + k = Link[j].subIndex; + m = Pump[k].pumpCurve; + n1 = Link[j].node1; + n2 = Link[j].node2; + + // --- no flow if setting is closed + Link[j].flowClass = NO; + Link[j].setting = Link[j].targetSetting; + if ( Link[j].setting == 0.0 ) return 0.0; + + // --- pump flow = node inflow for IDEAL_PUMP + if ( Pump[k].type == IDEAL_PUMP ) + qIn = Node[n1].inflow + Node[n1].overflow; + + // --- pumping rate depends on pump curve type + else switch(Curve[m].curveType) + { + case PUMP1_CURVE: + vol = Node[n1].newVolume * UCF(VOLUME); + qIn = table_intervalLookup(&Curve[m], vol) / UCF(FLOW); + + // --- check if off of pump curve + if ( vol < Pump[k].xMin || vol > Pump[k].xMax ) + Link[j].flowClass = YES; + break; + + case PUMP2_CURVE: + depth = Node[n1].newDepth * UCF(LENGTH); + qIn = table_intervalLookup(&Curve[m], depth) / UCF(FLOW); + + // --- check if off of pump curve + if ( depth < Pump[k].xMin || depth > Pump[k].xMax ) + Link[j].flowClass = YES; + break; + + case PUMP3_CURVE: + case PUMP5_CURVE: + if (Curve[m].curveType == PUMP5_CURVE) s = Link[j].setting; + head = ((Node[n2].newDepth + Node[n2].invertElev) - + (Node[n1].newDepth + Node[n1].invertElev)) / s / s; + head = MAX(head, 0.0) * UCF(LENGTH); + qIn = table_lookup(&Curve[m], head) / UCF(FLOW); + + // --- compute dQ/dh (slope of pump curve) and + // reverse sign since flow decreases with increasing head + Link[j].dqdh = -table_getSlope(&Curve[m], head) * + UCF(LENGTH) / UCF(FLOW) / s; + + // --- check if off of pump curve + if (head < Pump[k].xMin || head > Pump[k].xMax) + Link[j].flowClass = YES; + break; + + case PUMP4_CURVE: + depth = Node[n1].newDepth; + qIn = table_lookup(&Curve[m], depth*UCF(LENGTH)) / UCF(FLOW); + + // --- compute dQ/dh (slope of pump curve) + qIn1 = table_lookup(&Curve[m], (depth+dh)*UCF(LENGTH)) / UCF(FLOW); + Link[j].dqdh = (qIn1 - qIn) / dh; + + // --- check if off of pump curve + depth *= UCF(LENGTH); + if ( depth < Pump[k].xMin ) Link[j].flowClass = DN_DRY; + if ( depth > Pump[k].xMax ) Link[j].flowClass = UP_DRY; + break; + + default: qIn = 0.0; + } + + // --- do not allow reverse flow through pump + if ( qIn < 0.0 ) qIn = 0.0; + return qIn * Link[j].setting; +} + + +//============================================================================= +// O R I F I C E M E T H O D S +//============================================================================= + +int orifice_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = orifice index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads orifice parameters from a tokenized line of input. +// +{ + int m; + int n1, n2; + double x[5]; + char* id; + + // --- check for valid ID and end node IDs + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- parse orifice parameters + m = findmatch(tok[3], OrificeTypeWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); + x[0] = m; // type + if ( LinkOffsets == ELEV_OFFSET && *tok[4] == '*' ) x[1] = MISSING; + else if ( ! getDouble(tok[4], &x[1]) ) // crest height + return error_setInpError(ERR_NUMBER, tok[4]); + if ( ! getDouble(tok[5], &x[2]) || x[2] < 0.0 ) // cDisch + return error_setInpError(ERR_NUMBER, tok[5]); + x[3] = 0.0; + if ( ntoks >= 7 ) + { + m = findmatch(tok[6], NoYesWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); + x[3] = m; // flap gate + } + x[4] = 0.0; + if ( ntoks >= 8 ) + { + if ( ! getDouble(tok[7], &x[4]) || x[4] < 0.0 ) // orate + return error_setInpError(ERR_NUMBER, tok[7]); + } + + // --- add parameters to orifice object + Link[j].ID = id; + link_setParams(j, ORIFICE, n1, n2, k, x); + return 0; +} + +//============================================================================= + +void orifice_validate(int j, int k) +// +// Input: j = link index +// k = orifice index +// Output: none +// Purpose: validates an orifice's properties +// +{ + int err = 0; + + // --- check for valid xsection + if ( Link[j].xsect.type != RECT_CLOSED + && Link[j].xsect.type != CIRCULAR ) err = ERR_REGULATOR_SHAPE; + if ( err > 0 ) + { + report_writeErrorMsg(err, Link[j].ID); + return; + } + + // --- check for negative offset + if ( Link[j].offset1 < 0.0 ) Link[j].offset1 = 0.0; + + // --- compute partial flow adjustment + orifice_setSetting(j, 0.0); + + // --- compute an equivalent length + Orifice[k].length = 2.0 * RouteStep * sqrt(GRAVITY * Link[j].xsect.yFull); + Orifice[k].length = MAX(200.0, Orifice[k].length); + Orifice[k].surfArea = 0.0; +} + +//============================================================================= + +void orifice_setSetting(int j, double tstep) +// +// Input: j = link index +// tstep = time step over which setting is adjusted (sec) +// Output: none +// Purpose: updates an orifice's setting as a result of a control action. +// +{ + int k = Link[j].subIndex; + double delta, step; + double h, f; + + // --- case where adjustment rate is instantaneous + if ( Orifice[k].orate == 0.0 || tstep == 0.0) + Link[j].setting = Link[j].targetSetting; + + // --- case where orifice setting depends on time step + else + { + delta = Link[j].targetSetting - Link[j].setting; + step = tstep / Orifice[k].orate; + if ( step + 0.001 >= fabs(delta) ) + Link[j].setting = Link[j].targetSetting; + else Link[j].setting += SGN(delta) * step; + } + + // --- find effective orifice discharge coeff. + h = Link[j].setting * Link[j].xsect.yFull; + f = xsect_getAofY(&Link[j].xsect, h) * sqrt(2.0 * GRAVITY); + Orifice[k].cOrif = Orifice[k].cDisch * f; + + // --- find equiv. discharge coeff. for when weir flow occurs + Orifice[k].cWeir = orifice_getWeirCoeff(j, k, h) * f; +} + +//============================================================================= + +double orifice_getWeirCoeff(int j, int k, double h) +// +// Input: j = link index +// k = orifice index +// h = height of orifice opening (ft) +// Output: returns a discharge coefficient (ft^1/2) +// Purpose: computes the discharge coefficient for an orifice +// at the critical depth where weir flow begins. +// +{ + double w, aOverL; + + // --- this is for bottom orifices + if ( Orifice[k].type == BOTTOM_ORIFICE ) + { + // --- find critical height above opening where orifice flow + // turns into weir flow. It equals (Co/Cw)*(Area/Length) + // where Co is the orifice coeff., Cw is the weir coeff/sqrt(2g), + // Area is the area of the opening, and Length = circumference + // of the opening. For a basic sharp crested weir, Cw = 0.414. + if (Link[j].xsect.type == CIRCULAR) aOverL = h / 4.0; + else + { + w = Link[j].xsect.wMax; + aOverL = (h*w) / (2.0*(h+w)); + } + h = Orifice[k].cDisch / 0.414 * aOverL; + Orifice[k].hCrit = h; + } + + // --- this is for side orifices + else + { + // --- critical height is simply height of opening + Orifice[k].hCrit = h; + + // --- head on orifice is distance to center line + h = h / 2.0; + } + + // --- return a coefficient for the critical depth + return Orifice[k].cDisch * sqrt(h); +} + +//============================================================================= + +double orifice_getInflow(int j) +// +// Input: j = link index +// Output: returns orifice flow rate (cfs) +// Purpose: finds the flow through an orifice. +// +{ + int k, n1, n2; + double head, h1, h2, y1, dir; + double f; + double hcrest = 0.0; + double hcrown = 0.0; + double hmidpt; + double q, ratio; + + // --- get indexes of end nodes and link's orifice + n1 = Link[j].node1; + n2 = Link[j].node2; + k = Link[j].subIndex; + + // --- find heads at upstream & downstream nodes + if ( RouteModel == DW ) + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + } + else + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n1].invertElev; + } + dir = (h1 >= h2) ? +1.0 : -1.0; + + // --- exchange h1 and h2 for reverse flow + y1 = Node[n1].newDepth; + if ( dir < 0.0 ) + { + head = h1; + h1 = h2; + h2 = head; + y1 = Node[n2].newDepth; + } + + // --- orifice is a bottom orifice (oriented in horizontal plane) + if ( Orifice[k].type == BOTTOM_ORIFICE ) + { + // --- compute crest elevation + hcrest = Node[n1].invertElev + Link[j].offset1; + + // --- compute head on orifice + if (h1 < hcrest) head = 0.0; + else if (h2 > hcrest) head = h1 - h2; + else head = h1 - hcrest; + + // --- find fraction of critical height for which weir flow occurs + f = head / Orifice[k].hCrit; + f = MIN(f, 1.0); + } + + // --- otherwise orifice is a side orifice (oriented in vertical plane) + else + { + // --- compute elevations of orifice crest and crown + hcrest = Node[n1].invertElev + Link[j].offset1; + hcrown = hcrest + Link[j].xsect.yFull * Link[j].setting; + hmidpt = (hcrest + hcrown) / 2.0; + + // --- compute degree of inlet submergence + if ( h1 < hcrown && hcrown > hcrest ) + f = (h1 - hcrest) / (hcrown - hcrest); + else f = 1.0; + + // --- compute head on orifice + if ( f < 1.0 ) head = h1 - hcrest; + else if ( h2 < hmidpt ) head = h1 - hmidpt; + else head = h1 - h2; + } + + // --- return if head is negligible or flap gate closed + if ( head <= FUDGE || y1 <= FUDGE || + link_setFlapGate(j, n1, n2, dir) ) + { + Link[j].newDepth = 0.0; + Link[j].flowClass = DRY; + Orifice[k].surfArea = FUDGE * Orifice[k].length; + Link[j].dqdh = 0.0; + return 0.0; + } + + // --- determine flow class + Link[j].flowClass = SUBCRITICAL; + if ( hcrest > h2 ) + { + if ( dir == 1.0 ) Link[j].flowClass = DN_CRITICAL; + else Link[j].flowClass = UP_CRITICAL; + } + + // --- compute flow depth and surface area + y1 = Link[j].xsect.yFull * Link[j].setting; + if ( Orifice[k].type == SIDE_ORIFICE ) + { + Link[j].newDepth = y1 * f; + Orifice[k].surfArea = + xsect_getWofY(&Link[j].xsect, Link[j].newDepth) * + Orifice[k].length; + } + else + { + Link[j].newDepth = y1; + Orifice[k].surfArea = xsect_getAofY(&Link[j].xsect, y1); + } + + // --- find flow through the orifice + q = dir * orifice_getFlow(j, k, head, f, Link[j].hasFlapGate); + + // --- apply Villemonte eqn. to correct for submergence + if ( f < 1.0 && h2 > hcrest ) + { + ratio = (h2 - hcrest) / (h1 - hcrest); + q *= pow( (1.0 - pow(ratio, 1.5)), 0.385); + } + return q; +} + +//============================================================================= + +double orifice_getFlow(int j, int k, double head, double f, int hasFlapGate) +// +// Input: j = link index +// k = orifice index +// head = head across orifice +// f = fraction of critical depth filled +// hasFlapGate = flap gate indicator +// Output: returns flow through an orifice +// Purpose: computes flow through an orifice as a function of head. +// +{ + double area, q; + double veloc, hLoss; + + // --- case where orifice is closed + if ( head == 0.0 || f <= 0.0 ) + { + Link[j].dqdh = 0.0; + return 0.0; + } + + // --- case where inlet depth is below critical depth; + // orifice behaves as a weir + else if ( f < 1.0 ) + { + q = Orifice[k].cWeir * pow(f, 1.5); + Link[j].dqdh = 1.5 * q / (f * Orifice[k].hCrit); + } + + // --- case where normal orifice flow applies + else + { + q = Orifice[k].cOrif * sqrt(head); + Link[j].dqdh = q / (2.0 * head); + } + + // --- apply ARMCO adjustment for headloss from flap gate + if ( hasFlapGate ) + { + // --- compute velocity for current orifice flow + area = xsect_getAofY(&Link[j].xsect, + Link[j].setting * Link[j].xsect.yFull); + veloc = q / area; + + // --- compute head loss from gate + hLoss = (4.0 / GRAVITY) * veloc * veloc * + exp(-1.15 * veloc / sqrt(head) ); + + // --- update head (for orifice flow) + // or critical depth fraction (for weir flow) + if ( f < 1.0 ) + { + f = f - hLoss/Orifice[k].hCrit; + if ( f < 0.0 ) f = 0.0; + } + else + { + head = head - hLoss; + if ( head < 0.0 ) head = 0.0; + } + + // --- make recursive call to this function, with hasFlapGate + // set to false, to find flow values at adjusted head value + q = orifice_getFlow(j, k, head, f, FALSE); + } + return q; +} + +//============================================================================= +// W E I R M E T H O D S +//============================================================================= + +int weir_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = weir index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads weir parameters from a tokenized line of input. +// +{ + int m; + int n1, n2; + double x[10]; + char* id; + + // --- check for valid ID and end node IDs + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- parse weir parameters + m = findmatch(tok[3], WeirTypeWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); + x[0] = m; // type + if ( LinkOffsets == ELEV_OFFSET && *tok[4] == '*' ) x[1] = MISSING; + else if ( ! getDouble(tok[4], &x[1]) ) // height + return error_setInpError(ERR_NUMBER, tok[4]); + if ( ! getDouble(tok[5], &x[2]) || x[2] < 0.0 ) // cDisch1 + return error_setInpError(ERR_NUMBER, tok[5]); + x[3] = 0.0; + x[4] = 0.0; + x[5] = 0.0; + x[6] = 1.0; + x[7] = 0.0; + x[8] = 0.0; + x[9] = -1.0; + if ( ntoks >= 7 && *tok[6] != '*' ) + { + m = findmatch(tok[6], NoYesWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); + x[3] = m; // flap gate + } + if ( ntoks >= 8 && *tok[7] != '*' ) + { + if ( ! getDouble(tok[7], &x[4]) || x[4] < 0.0 ) // endCon + return error_setInpError(ERR_NUMBER, tok[7]); + } + if ( ntoks >= 9 && *tok[8] != '*' ) + { + if ( ! getDouble(tok[8], &x[5]) || x[5] < 0.0 ) // cDisch2 + return error_setInpError(ERR_NUMBER, tok[8]); + } + + if ( ntoks >= 10 && *tok[9] != '*' ) + { + m = findmatch(tok[9], NoYesWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[9]); + x[6] = m; // canSurcharge + } + + if ( (m = (int)x[0]) == ROADWAY_WEIR ) + { + if ( ntoks >= 11 ) // road width + { + if ( ! getDouble(tok[10], &x[7]) || x[7] < 0.0 ) + return error_setInpError(ERR_NUMBER, tok[10]); + } + if ( ntoks >= 12 ) // road surface + { + if ( strcomp(tok[11], "PAVED") ) x[8] = 1.0; + else if ( strcomp(tok[11], "GRAVEL") ) x[8] = 2.0; + } + } + + if (ntoks >= 13 && *tok[12] != '*') + { + m = project_findObject(CURVE, tok[12]); // coeff. curve + if (m < 0) return error_setInpError(ERR_NAME, tok[12]); + x[9] = m; + } + + // --- add parameters to weir object + Link[j].ID = id; + link_setParams(j, WEIR, n1, n2, k, x); + return 0; +} + +//============================================================================= + +void weir_validate(int j, int k) +// +// Input: j = link index +// k = weir index +// Output: none +// Purpose: validates a weir's properties +// +{ + int err = 0; + double q, q1, q2, head; + + // --- check for valid cross section + switch ( Weir[k].type) + { + case TRANSVERSE_WEIR: + case SIDEFLOW_WEIR: + case ROADWAY_WEIR: + if ( Link[j].xsect.type != RECT_OPEN ) err = ERR_REGULATOR_SHAPE; + Weir[k].slope = 0.0; + break; + + case VNOTCH_WEIR: + if ( Link[j].xsect.type != TRIANGULAR ) err = ERR_REGULATOR_SHAPE; + else + { + Weir[k].slope = Link[j].xsect.sBot; + } + break; + + case TRAPEZOIDAL_WEIR: + if ( Link[j].xsect.type != TRAPEZOIDAL ) err = ERR_REGULATOR_SHAPE; + else + { + Weir[k].slope = Link[j].xsect.sBot; + } + break; + } + if ( err > 0 ) + { + report_writeErrorMsg(err, Link[j].ID); + return; + } + + // --- check for negative offset + if ( Link[j].offset1 < 0.0 ) Link[j].offset1 = 0.0; + + // --- compute an equivalent length + Weir[k].length = 2.0 * RouteStep * sqrt(GRAVITY * Link[j].xsect.yFull); + Weir[k].length = MAX(200.0, Weir[k].length); + Weir[k].surfArea = 0.0; + + // --- find flow through weir when water level equals weir height + head = Link[j].xsect.yFull; + weir_getFlow(j, k, head, 1.0, FALSE, &q1, &q2); + q = q1 + q2; + + // --- compute equivalent orifice coeff. (for CFS flow units) + head = head / 2.0; // head seen by equivalent orifice + Weir[k].cSurcharge = q / sqrt(head); +} + +//============================================================================= + +void weir_setSetting(int j) +// +// Input: j = link index +// Output: none +// Purpose: updates a weir's setting as a result of a control action. +// +{ + int k = Link[j].subIndex; + double h, q, q1, q2; + + // --- adjust weir setting + Link[j].setting = Link[j].targetSetting; + if ( !Weir[k].canSurcharge ) return; + if ( Weir[k].type == ROADWAY_WEIR ) return; + + // --- find orifice coeff. for surcharged flow + if ( Link[j].setting == 0.0 ) Weir[k].cSurcharge = 0.0; + else + { + // --- find flow through weir when water level equals weir height + h = Link[j].setting * Link[j].xsect.yFull; + weir_getFlow(j, k, h, 1.0, FALSE, &q1, &q2); + q = q1 + q2; + + // --- compute equivalent orifice coeff. (for CFS flow units) + h = h / 2.0; // head seen by equivalent orifice + Weir[k].cSurcharge = q / sqrt(h); + } +} + +//============================================================================= + +double weir_getInflow(int j) +// +// Input: j = link index +// Output: returns weir flow rate (cfs) +// Purpose: finds the flow over a weir. +// +{ + int n1; // index of upstream node + int n2; // index of downstream node + int k; // index of weir + double q1; // flow through central part of weir (cfs) + double q2; // flow through end sections of weir (cfs) + double head; // head on weir (ft) + double h1; // upstrm nodal head (ft) + double h2; // downstrm nodal head (ft) + double hcrest; // head at weir crest (ft) + double hcrown; // head at weir crown (ft) + double y; // water depth in weir (ft) + double dir; // direction multiplier + double ratio; + double weirPower[] = {1.5, // transverse weir + 5./3., // side flow weir + 2.5, // v-notch weir + 1.5}; // trapezoidal weir + + n1 = Link[j].node1; + n2 = Link[j].node2; + k = Link[j].subIndex; + if ( RouteModel == DW ) + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + } + else + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n1].invertElev; + } + dir = (h1 > h2) ? +1.0 : -1.0; + + // --- exchange h1 and h2 for reverse flow + if ( dir < 0.0 ) + { + head = h1; + h1 = h2; + h2 = head; + } + + // --- find head of weir's crest and crown + hcrest = Node[n1].invertElev + Link[j].offset1; + hcrown = hcrest + Link[j].xsect.yFull; + + // --- treat a roadway weir as a special case + if ( Weir[k].type == ROADWAY_WEIR ) + return roadway_getInflow(j, dir, hcrest, h1, h2); + + // --- adjust crest ht. for partially open weir + hcrest += (1.0 - Link[j].setting) * Link[j].xsect.yFull; + + // --- compute head relative to weir crest + head = h1 - hcrest; + + // --- return if head is negligible or flap gate closed + Link[j].dqdh = 0.0; + if ( head <= FUDGE || hcrest >= hcrown || + link_setFlapGate(j, n1, n2, dir) ) + { + Link[j].newDepth = 0.0; + Link[j].flowClass = DRY; + return 0.0; + } + + // --- determine flow class + Link[j].flowClass = SUBCRITICAL; + if ( hcrest > h2 ) + { + if ( dir == 1.0 ) Link[j].flowClass = DN_CRITICAL; + else Link[j].flowClass = UP_CRITICAL; + } + + // --- compute new equivalent surface area + y = Link[j].xsect.yFull - (hcrown - MIN(h1, hcrown)); + Weir[k].surfArea = xsect_getWofY(&Link[j].xsect, y) * Weir[k].length; + + // --- head is above crown + if ( h1 >= hcrown ) + { + // --- use equivalent orifice if weir can surcharge + if ( Weir[k].canSurcharge ) + { + y = (hcrest + hcrown) / 2.0; + if ( h2 < y ) head = h1 - y; + else head = h1 - h2; + y = hcrown - hcrest; + q1 = weir_getOrificeFlow(j, head, y, Weir[k].cSurcharge); + Link[j].newDepth = y; + return dir * q1; + } + + // --- otherwise limit head to height of weir opening + else head = hcrown - hcrest; + } + + // --- use weir eqn. to find flows through central (q1) + // and end sections (q2) of weir + weir_getFlow(j, k, head, dir, Link[j].hasFlapGate, &q1, &q2); + + // --- apply Villemonte eqn. to correct for submergence + if ( h2 > hcrest ) + { + ratio = (h2 - hcrest) / (h1 - hcrest); + q1 *= pow( (1.0 - pow(ratio, weirPower[Weir[k].type])), 0.385); + if ( q2 > 0.0 ) + q2 *= pow( (1.0 - pow(ratio, weirPower[VNOTCH_WEIR])), 0.385); + } + + // --- return total flow through weir + Link[j].newDepth = MIN((h1 - hcrest), Link[j].xsect.yFull); + return dir * (q1 + q2); +} + +//============================================================================= + +void weir_getFlow(int j, int k, double head, double dir, int hasFlapGate, + double* q1, double* q2) +// +// Input: j = link index +// k = weir index +// head = head across weir (ft) +// dir = flow direction indicator +// hasFlapGate = flap gate indicator +// Output: q1 = flow through central portion of weir (cfs) +// q2 = flow through end sections of weir (cfs) +// Purpose: computes flow over weir given head. +// +{ + double length; + double h; + double y; + double hLoss; + double area; + double veloc; + int wType; + int cdCurve = Weir[k].cdCurve; + double cDisch1 = Weir[k].cDisch1; + + // --- q1 = flow through central portion of weir, + // q2 = flow through end sections of trapezoidal weir + *q1 = 0.0; + *q2 = 0.0; + Link[j].dqdh = 0.0; + if ( head <= 0.0 ) return; + + // --- convert weir length & head to original units + length = Link[j].xsect.wMax * UCF(LENGTH); + h = head * UCF(LENGTH); + + // --- lookup tabulated discharge coeff. + if ( cdCurve >= 0 ) cDisch1 = table_lookup(&Curve[cdCurve], h); + + // --- use appropriate formula for weir flow + wType = Weir[k].type; + if ( wType == VNOTCH_WEIR && + Link[j].setting < 1.0 ) wType = TRAPEZOIDAL_WEIR; + switch (wType) + { + case TRANSVERSE_WEIR: + + // --- reduce length when end contractions present + length -= 0.1 * Weir[k].endCon * h; + length = MAX(length, 0.0); + *q1 = cDisch1 * length * pow(h, 1.5); + break; + + case SIDEFLOW_WEIR: + + // --- reduce length when end contractions present + length -= 0.1 * Weir[k].endCon * h; + length = MAX(length, 0.0); + + // --- weir behaves as a transverse weir under reverse flow + if ( dir < 0.0 ) + *q1 = cDisch1 * length * pow(h, 1.5); + else + + // Corrected formula (see Metcalf & Eddy, Inc., + // Wastewater Engineering, McGraw-Hill, 1972 p. 164). + *q1 = cDisch1 * pow(length, 0.83) * pow(h, 1.67); + + break; + + case VNOTCH_WEIR: + *q1 = cDisch1 * Weir[k].slope * pow(h, 2.5); + break; + + case TRAPEZOIDAL_WEIR: + y = (1.0 - Link[j].setting) * Link[j].xsect.yFull; + length = xsect_getWofY(&Link[j].xsect, y) * UCF(LENGTH); + *q1 = cDisch1 * length * pow(h, 1.5); + *q2 = Weir[k].cDisch2 * Weir[k].slope * pow(h, 2.5); + } + + // --- convert CMS flows to CFS + if ( UnitSystem == SI ) + { + *q1 /= M3perFT3; + *q2 /= M3perFT3; + } + + // --- apply ARMCO adjustment for headloss from flap gate + if ( hasFlapGate ) + { + // --- compute flow area & velocity for current weir flow + area = weir_getOpenArea(j, head); + if ( area > TINY ) + { + veloc = (*q1 + *q2) / area; + + // --- compute headloss and subtract from original head + hLoss = (4.0 / GRAVITY) * veloc * veloc * + exp(-1.15 * veloc / sqrt(head) ); + head = head - hLoss; + if ( head < 0.0 ) head = 0.0; + + // --- make recursive call to this function, with hasFlapGate + // set to false, to find flow values at adjusted head value + weir_getFlow(j, k, head, dir, FALSE, q1, q2); + } + } + Link[j].dqdh = weir_getdqdh(k, dir, head, *q1, *q2); +} + +//============================================================================= + +double weir_getOrificeFlow(int j, double head, double y, double cOrif) +// +// Input: j = link index +// head = head across weir (ft) +// y = height of upstream water level above weir crest (ft) +// cOrif = orifice flow coefficient +// Output: returns flow through weir +// Purpose: finds flow through a surcharged weir using the orifice equation. +// +{ + double a, q, v, hloss; + + // --- evaluate the orifice flow equation + q = cOrif * sqrt(head); + + // --- apply Armco adjustment if weir has a flap gate + if ( Link[j].hasFlapGate ) + { + a = weir_getOpenArea(j, y); + if ( a > 0.0 ) + { + v = q / a; + hloss = (4.0 / GRAVITY) * v * v * exp(-1.15 * v / sqrt(y) ); + head -= hloss; + head = MAX(head, 0.0); + q = cOrif * sqrt(head); + } + } + if ( head > 0.0 ) Link[j].dqdh = q / (2.0 * head); + else Link[j].dqdh = 0.0; + return q; +} + +//============================================================================= + +double weir_getOpenArea(int j, double y) +// +// Input: j = link index +// y = depth of water above weir crest (ft) +// Output: returns area between weir crest and y (ft2) +// Purpose: finds flow area through a weir. +// +{ + double z, zy; + + // --- find offset of weir crest due to control setting + z = (1.0 - Link[j].setting) * Link[j].xsect.yFull; + + // --- ht. of crest + ht of water above crest + zy = z + y; + zy = MIN(zy, Link[j].xsect.yFull); + + // --- return difference between area of offset + water depth + // and area of just the offset + return xsect_getAofY(&Link[j].xsect, zy) - + xsect_getAofY(&Link[j].xsect, z); +} + +//============================================================================= + +double weir_getdqdh(int k, double dir, double h, double q1, double q2) +{ + double q1h; + double q2h; + + if ( fabs(h) < FUDGE ) return 0.0; + q1h = fabs(q1/h); + q2h = fabs(q2/h); + + switch (Weir[k].type) + { + case TRANSVERSE_WEIR: return 1.5 * q1h; + + case SIDEFLOW_WEIR: + // --- weir behaves as a transverse weir under reverse flow + if ( dir < 0.0 ) return 1.5 * q1h; + else return 1.67 * q1h; + + case VNOTCH_WEIR: + if ( q2h == 0.0 ) return 2.5 * q1h; // Fully open + else return 1.5 * q1h + 2.5 * q2h; // Partly open + + case TRAPEZOIDAL_WEIR: return 1.5 * q1h + 2.5 * q2h; + } + return 0.0; +} + + +//============================================================================= +// O U T L E T D E V I C E M E T H O D S +//============================================================================= + +int outlet_readParams(int j, int k, char* tok[], int ntoks) +// +// Input: j = link index +// k = outlet index +// tok[] = array of string tokens +// ntoks = number of tokens +// Output: returns an error code +// Purpose: reads outlet parameters from a tokenized line of input. +// +{ + int i, m, n; + int n1, n2; + double x[6]; + char* id; + char* s; + + // --- check for valid ID and end node IDs + if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); + id = project_findID(LINK, tok[0]); + if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); + n1 = project_findObject(NODE, tok[1]); + if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); + n2 = project_findObject(NODE, tok[2]); + if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); + + // --- get height above invert + if ( LinkOffsets == ELEV_OFFSET && *tok[3] == '*' ) x[0] = MISSING; + else + { + if ( ! getDouble(tok[3], &x[0]) ) + return error_setInpError(ERR_NUMBER, tok[3]); + if ( LinkOffsets == DEPTH_OFFSET && x[0] < 0.0 ) x[0] = 0.0; + } + + // --- see if outlet flow relation is tabular or functional + m = findmatch(tok[4], RelationWords); + if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); + x[1] = 0.0; + x[2] = 0.0; + x[3] = -1.0; + x[4] = 0.0; + + // --- see if rating curve is head or depth based + x[5] = NODE_DEPTH; //default is depth-based + s = strtok(tok[4], "/"); //parse token for + s = strtok(NULL, "/"); // qualifier term + if ( strcomp(s, w_HEAD) ) x[5] = NODE_HEAD; //check if its "HEAD" + + // --- get params. for functional outlet device + if ( m == FUNCTIONAL ) + { + if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); + if ( ! getDouble(tok[5], &x[1]) ) + return error_setInpError(ERR_NUMBER, tok[5]); + if ( ! getDouble(tok[6], &x[2]) ) + return error_setInpError(ERR_NUMBER, tok[6]); + n = 7; + } + + // --- get name of outlet rating curve + else + { + i = project_findObject(CURVE, tok[5]); + if ( i < 0 ) return error_setInpError(ERR_NAME, tok[5]); + x[3] = i; + n = 6; + } + + // --- check if flap gate specified + if ( ntoks > n) + { + i = findmatch(tok[n], NoYesWords); + if ( i < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); + x[4] = i; + } + + // --- add parameters to outlet object + Link[j].ID = id; + link_setParams(j, OUTLET, n1, n2, k, x); + return 0; +} + +//============================================================================= + +double outlet_getInflow(int j) +// +// Input: j = link index +// Output: outlet flow rate (cfs) +// Purpose: finds the flow through an outlet. +// +{ + int k, n1, n2; + double head, hcrest, h1, h2, y1, dir; + + // --- get indexes of end nodes + n1 = Link[j].node1; + n2 = Link[j].node2; + k = Link[j].subIndex; + + // --- find heads at upstream & downstream nodes + if ( RouteModel == DW ) + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n2].newDepth + Node[n2].invertElev; + } + else + { + h1 = Node[n1].newDepth + Node[n1].invertElev; + h2 = Node[n1].invertElev; + } + dir = (h1 >= h2) ? +1.0 : -1.0; + + // --- exchange h1 and h2 for reverse flow + y1 = Node[n1].newDepth; + if ( dir < 0.0 ) + { + y1 = h1; + h1 = h2; + h2 = y1; + y1 = Node[n2].newDepth; + } + + // --- for a NODE_DEPTH rating curve the effective head across the + // outlet is the depth above the crest elev. while for a NODE_HEAD + // curve it is the difference between upstream & downstream heads + hcrest = Node[n1].invertElev + Link[j].offset1; + if ( Outlet[k].curveType == NODE_HEAD && RouteModel == DW ) + head = h1 - MAX(h2, hcrest); + else head = h1 - hcrest; + + // --- no flow if either no effective head difference, + // no upstream water available, or closed flap gate + if ( head <= FUDGE || y1 <= FUDGE || + link_setFlapGate(j, n1, n2, dir) ) + { + Link[j].newDepth = 0.0; + Link[j].flowClass = DRY; + return 0.0; + } + + // --- otherwise use rating curve to compute flow + Link[j].newDepth = head; + Link[j].flowClass = SUBCRITICAL; + return dir * Link[j].setting * outlet_getFlow(k, head); +} + +//============================================================================= + +double outlet_getFlow(int k, double head) +// +// Input: k = outlet index +// head = head across outlet (ft) +// Output: returns outlet flow rate (cfs) +// Purpose: computes flow rate through an outlet given head. +// +{ + int m; + double h; + + // --- convert head to original units + h = head * UCF(LENGTH); + + // --- look-up flow in rating curve table if provided + m = Outlet[k].qCurve; + if ( m >= 0 ) return table_lookup(&Curve[m], h) / UCF(FLOW); + + // --- otherwise use function to find flow + else return Outlet[k].qCoeff * pow(h, Outlet[k].qExpon) / UCF(FLOW); +} diff --git a/src/macros.h b/src/macros.h new file mode 100644 index 000000000..c15749e4b --- /dev/null +++ b/src/macros.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// macros.h +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 11/01/21 (Build 5.2.0) +// Author: L. Rossman +//----------------------------------------------------------------------------- + +#ifndef MACROS_H +#define MACROS_H + + +//-------------------------------------------------- +// Macro to test for successful allocation of memory +//-------------------------------------------------- +#define MEMCHECK(x) (((x) == NULL) ? 101 : 0 ) + +//-------------------------------------------------- +// Macro to free a non-null pointer +//-------------------------------------------------- +#define FREE(x) { if (x) { free(x); x = NULL; } } + +//--------------------------------------------------- +// Conversion macros to be used in place of functions +//--------------------------------------------------- +#define ABS(x) (((x)<0) ? -(x) : (x)) /* absolute value of x */ +#define MIN(x,y) (((x)<=(y)) ? (x) : (y)) /* minimum of x and y */ +#define MAX(x,y) (((x)>=(y)) ? (x) : (y)) /* maximum of x and y */ +#define MOD(x,y) ((x)%(y)) /* x modulus y */ +#define LOG10(x) ((x) > 0.0 ? log10((x)) : (x)) /* safe log10 of x */ +#define SQR(x) ((x)*(x)) /* x-squared */ +#define SGN(x) (((x)<0) ? (-1) : (1)) /* sign of x */ +#define SIGN(x,y) ((y) >= 0.0 ? fabs(x) : -fabs(x)) +#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) + /* uppercase char of x */ +#define ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0])) /* length of array x */ + +//------------------------------------------------- +// Macro to evaluate function x with error checking +//------------------------------------------------- +#define CALL(x) (ErrorCode = ((ErrorCode>0) ? (ErrorCode) : (x))) + + +#endif //MACROS_H diff --git a/src/main.c b/src/main.c new file mode 100644 index 000000000..0fe1b40b7 --- /dev/null +++ b/src/main.c @@ -0,0 +1,104 @@ +//----------------------------------------------------------------------------- +// main.c +// +// Project: EPA SWMM5 +// Version: 5.2 +// Date: 03/24/2021 +// Author: L. Rossman + +// Main stub for the command line version of EPA SWMM 5.2 +// to be run with swmm5.dll. + +#include +#include +#include +#include "swmm5.h" + +int main(int argc, char *argv[]) +// +// Input: argc = number of command line arguments +// argv = array of command line arguments +// Output: returns error status +// Purpose: runs the command line version of EPA SWMM 5.2. +// +// Command line is: runswmm f1 f2 f3 +// where f1 = name of input file, f2 = name of report file, and +// f3 = name of binary output file if saved (or blank if not saved). +// +{ + char *inputFile; + char *reportFile; + char *binaryFile; + char *arg1; + char blank[] = ""; + int version, vMajor, vMinor, vRelease; + char errMsg[128]; + int msgLen = 127; + time_t start; + double runTime; + + version = swmm_getVersion(); + vMajor = version / 10000; + vMinor = (version - 10000 * vMajor) / 1000; + vRelease = (version - 10000 * vMajor - 1000 * vMinor); + start = time(0); + + // --- check for proper number of command line arguments + if (argc == 1) + { + printf("\nNot Enough Arguments (See Help --help)\n\n"); + } + else if (argc == 2) + { + // --- extract first argument + arg1 = argv[1]; + + if (strcmp(arg1, "--help") == 0 || strcmp(arg1, "-h") == 0) + { + // Help + printf("\n\nSTORMWATER MANAGEMENT MODEL (SWMM) HELP\n\n"); + printf("COMMANDS:\n"); + printf("\t--help (-h) SWMM Help\n"); + printf("\t--version (-v) Build Version\n"); + printf("\nRUNNING A SIMULATION:\n"); + printf("\t runswmm

-// -// consists of: -// value / -// where is -// E.g.: Node 123 Depth > 4.5 -// Node 456 Depth < Node 123 Depth -// -// consists of: -// = setting -// E.g.: Pump abc status = OFF -// Weir xyz setting = 0.5 -// -// Update History -// ============== -// Build 5.1.008: -// - Support added for r.h.s. variables in rule premises. -// - Node volume added as a premise variable. -// Build 5.1.009: -// - Fixed problem with parsing a RHS premise variable. -// Build 5.1.010: -// - Support added for link TIMEOPEN & TIMECLOSED premises. -// Build 5.1.011: -// - Support added for DAYOFYEAR attribute. -// - Modulated controls no longer included in reported control actions. -// Build 5.2.0: -// - Additional attributes added to condition clauses. -// - Support added for named variables in condition clauses. -// - Support added for math expressions in condition clauses. -// Build 5.2.1: -// - A refactoring bug from 5.2.0 causing duplicate actions to be added -// to the list of control actions to take was fixed. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -enum RuleState {r_RULE, r_IF, r_AND, r_OR, r_THEN, r_ELSE, r_PRIORITY, - r_VARIABLE, r_EXPRESSION, r_ERROR}; -enum RuleObject {r_GAGE, r_NODE, r_LINK, r_CONDUIT, r_PUMP, r_ORIFICE, - r_WEIR, r_OUTLET, r_SIMULATION}; -enum RuleAttrib {r_DEPTH, r_MAXDEPTH, r_HEAD, r_VOLUME, r_INFLOW, - r_FLOW, r_FULLFLOW, r_FULLDEPTH, r_STATUS, r_SETTING, - r_LENGTH, r_SLOPE, r_VELOCITY, r_TIMEOPEN, r_TIMECLOSED, - r_TIME, r_DATE, r_CLOCKTIME, r_DAYOFYEAR, r_DAY, r_MONTH}; -enum RuleRelation {EQ, NE, LT, LE, GT, GE}; -enum RuleSetting {r_CURVE, r_TIMESERIES, r_PID, r_NUMERIC}; - -#define MAXVARNAME 32 - -static char* ObjectWords[] = - {"GAGE", "NODE", "LINK", "CONDUIT", "PUMP", "ORIFICE", "WEIR", "OUTLET", - "SIMULATION", NULL}; -static char* AttribWords[] = - {"DEPTH", "MAXDEPTH", "HEAD", "VOLUME", "INFLOW", - "FLOW", "FULLFLOW", "FULLDEPTH", "STATUS", "SETTING", - "LENGTH", "SLOPE", "VELOCITY", "TIMEOPEN", "TIMECLOSED", - "TIME", "DATE", "CLOCKTIME", "DAYOFYEAR", "DAY", "MONTH", NULL}; -static char* RelOpWords[] = {"=", "<>", "<", "<=", ">", ">=", NULL}; -static char* StatusWords[] = {"OFF", "ON", NULL}; -static char* ConduitWords[] = {"CLOSED", "OPEN", NULL}; -static char* SettingTypeWords[] = {"CURVE", "TIMESERIES", "PID", NULL}; -static char* IntensityWord = "INTENSITY"; - -//----------------------------------------------------------------------------- -// Data Structures -//----------------------------------------------------------------------------- -// Rule Premise Variable -struct TVariable -{ - int object; // type of object - int index; // index in object's array - int attribute; // object's attribute -}; - -// Named Variable -struct TNamedVariable -{ - struct TVariable variable; // a rule premise variable - char name[MAXVARNAME+1]; // name used in math expression -}; - -// Rule Premise Function -struct TExpression -{ - MathExpr* expression; // tokenized math expression - char name[MAXVARNAME+1]; // expression name -}; - -// Rule Premise Clause -struct TPremise -{ - int type; // clause type (IF/AND/OR) - int exprIndex; // expression index (-1 if N/A) - struct TVariable lhsVar; // left hand side variable - struct TVariable rhsVar; // right hand side variable - int relation; // relational operator (>, <, =, etc) - double value; // right hand side value - struct TPremise *next; // next premise clause of rule -}; - -// Rule Action Clause -struct TAction -{ - int rule; // index of rule that action belongs to - int link; // index of link being controlled - int attribute; // attribute of link being controlled - int curve; // index of curve for modulated control - int tseries; // index of time series for modulated control - double value; // control setting for link attribute - double kp, ki, kd; // coeffs. for PID modulated control - double e1, e2; // PID set point error from previous time steps - struct TAction *next; // next action clause of rule -}; - -// List of Control Actions -struct TActionList -{ - struct TAction* action; - struct TActionList* next; -}; - -// Control Rule -struct TRule -{ - char* ID; // rule ID - double priority; // priority level - struct TPremise* firstPremise; // pointer to first premise of rule - struct TPremise* lastPremise; // pointer to last premise of rule - struct TAction* thenActions; // linked list of actions if true - struct TAction* elseActions; // linked list of actions if false -}; - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -struct TRule* Rules; // array of control rules -struct TActionList* ActionList; // linked list of control actions -int InputState; // state of rule interpreter -int RuleCount; // total number of rules -double ControlValue; // value of controller variable -double SetPoint; // value of controller setpoint -DateTime CurrentDate; // current date in whole days -DateTime CurrentTime; // current time of day (decimal) - -int VariableCount; -int ExpressionCount; -int CurrentVariable; -int CurrentExpression; -struct TNamedVariable* NamedVariable; // array of named variables -struct TExpression* Expression; // array of math expressions - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// controls_create -// controls_delete -// controls_init -// controls_addToCount -// controls_addVariable -// controls_addExpression -// controls_addRuleClause -// controls_evaluate - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -int addPremise(int r, int type, char* Tok[], int nToks); -int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v); -int getPremiseValue(char* token, int attrib, double* value); -int addAction(int r, char* Tok[], int nToks); - -int evaluatePremise(struct TPremise* p, double tStep); -double getVariableValue(struct TVariable v); -int compareTimes(double lhsValue, int relation, double rhsValue, - double halfStep); -int compareValues(double lhsValue, int relation, double rhsValue); - -void updateActionList(struct TAction* a); -int executeActionList(DateTime currentTime); -void clearActionList(void); -void deleteActionList(void); -void deleteRules(void); - -int findExactMatch(char *s, char *keyword[]); -int setActionSetting(char* tok[], int nToks, int* curve, int* tseries, - int* attrib, double value[]); -void updateActionValue(struct TAction* a, DateTime currentTime, double dt); -double getPIDSetting(struct TAction* a, double dt); - -int getVariableIndex(char* varName); -double getNamedVariableValue(int varIndex); -int getExpressionIndex(char* exprName); -int getGageAttrib(char* token); -double getRainValue(struct TVariable v); - -//============================================================================= - -void controls_init() -// -// Input: none -// Output: none -// Purpose: initializes the control rule system. -// -{ - Rules = NULL; - NamedVariable = NULL; - Expression = NULL; - RuleCount = 0; - VariableCount = 0; - ExpressionCount = 0; -} - -//============================================================================= - -void controls_addToCount(char* s) -// -// Input: s = either VARIABLE or EXPRESSION -// Output: none -// Purpose: updates the number of named variables or math expressions used -// by control rules. -// -{ - if (match(s, w_VARIABLE)) VariableCount++; - else if (match(s, w_EXPRESSION)) ExpressionCount++; -} - -//============================================================================= - -int controls_create(int n) -// -// Input: n = total number of control rules -// Output: returns error code -// Purpose: creates an array of control rules. -// -{ - int r; - ActionList = NULL; - InputState = r_PRIORITY; - RuleCount = n; - if (RuleCount > 0) - { - Rules = (struct TRule *) calloc(RuleCount, sizeof(struct TRule)); - if (Rules == NULL) return ERR_MEMORY; - for ( r=0; r 0) - { - NamedVariable = (struct TNamedVariable *) calloc(VariableCount, - sizeof(struct TNamedVariable)); - if (NamedVariable == NULL) return ERR_MEMORY; - } - if (ExpressionCount > 0) - { - Expression = (struct TExpression *) calloc(ExpressionCount, - sizeof(struct TExpression)); - if (Expression == NULL) return ERR_MEMORY; - } - return 0; -} - -//============================================================================= - -void controls_delete(void) -// -// Input: none -// Output: none -// Purpose: deletes all control rules. -// -{ - int i; - - for (i = 0; i < ExpressionCount; i++) - { - mathexpr_delete(Expression[i].expression); - Expression[i].expression = NULL; - } - FREE(Expression); - FREE(NamedVariable); - - if ( RuleCount == 0 ) return; - deleteActionList(); - deleteRules(); -} - -//============================================================================= - -int controls_addVariable(char* tok[], int nToks) -// -// Input: tok = an array of string tokens -// n = the size of tok[] -// Output: returns error code -// Purpose: adds a named variable to the control rule system from a -// tokenized line of input with formats: -// VARIABLE name = Object id attribute -// VARIABLE name = SIMULATION attribute -// -{ - struct TVariable v1; - int k, err; - - CurrentVariable++; - if (nToks < 5) return ERR_ITEMS; - if (findExactMatch(tok[1], AttribWords) >= 0) - return error_setInpError(ERR_KEYWORD, tok[1]); - if (!match(tok[2], "=")) return error_setInpError(ERR_KEYWORD, tok[2]); - if (!match(tok[3], "SIMULATION") && nToks < 6) return ERR_ITEMS; - k = 3; - err = getPremiseVariable(tok, nToks, &k, &v1); - if (err > 0) return err; - k = CurrentVariable; - NamedVariable[k].variable = v1; - sstrncpy(NamedVariable[k].name, tok[1], MAXVARNAME); - return 0; -} - -//============================================================================= - -int controls_addExpression(char* tok[], int nToks) -// -// Input: tok = an array of string tokens -// n = number of tokens -// Output: returns error code -// Purpose: adds a math expression to the control rule system from a -// a tokenized line of input with format: -// EXPRESSION name = -// -{ - int i, k; - char s[MAXLINE + 1]; - MathExpr* expr; - - CurrentExpression++; - if (nToks < 4) return ERR_ITEMS; - k = CurrentExpression; - Expression[k].expression = NULL; - sstrncpy(Expression[k].name, tok[1], MAXVARNAME); - sstrncpy(s, tok[3], MAXLINE); - for (i = 4; i < nToks; i++) - { - sstrcat(s, " ", MAXLINE); - sstrcat(s, tok[i], MAXLINE); - } - - expr = mathexpr_create(s, getVariableIndex); - if (expr == NULL) - return error_setInpError(ERR_MATH_EXPR, ""); - - Expression[k].expression = expr; - return 0; -} - -//============================================================================= - -int getVariableIndex(char* varName) -// -// Input: varName = string containing a variable name -// Output: returns the index of the named variable or -1 if not found -// Purpose: finds the array index of a named variable. -// -{ - int i; - for (i = 0; i < VariableCount; i++) - { - if (match(varName, NamedVariable[i].name)) return i; - } - return -1; -} - -//============================================================================= - -double getNamedVariableValue(int varIndex) -// -// Input: varIndex = index of a named variable -// Output: returns the current value of the variable -// Purpose: finds the value of a named variable. -// -{ - return getVariableValue(NamedVariable[varIndex].variable); -} - -//============================================================================= - -int getExpressionIndex(char* exprName) -// -// Input: exprName = string containing an expression name -// Output: returns the index of the expression or -1 if not found -// Purpose: finds the array index of a math expression -// -{ - int i; - for (i = 0; i < ExpressionCount; i++) - { - if (match(exprName, Expression[i].name)) return i; - } - return -1; -} - -//============================================================================= - -int controls_addRuleClause(int r, int keyword, char* tok[], int nToks) -// -// Input: r = rule index -// keyword = the clause's keyword code (IF, THEN, etc.) -// tok = an array of string tokens that comprises the clause -// nToks = number of tokens -// Output: returns an error code -// Purpose: addd a new clause to a control rule. -// -{ - switch (keyword) - { - case r_RULE: - if ( Rules[r].ID == NULL ) - Rules[r].ID = project_findID(CONTROL, tok[1]); - InputState = r_RULE; - if ( nToks > 2 ) return ERR_RULE; - return 0; - - case r_IF: - if ( InputState != r_RULE ) return ERR_RULE; - InputState = r_IF; - return addPremise(r, r_AND, tok, nToks); - - case r_AND: - if ( InputState == r_IF ) return addPremise(r, r_AND, tok, nToks); - else if ( InputState == r_THEN || InputState == r_ELSE ) - return addAction(r, tok, nToks); - else return ERR_RULE; - - case r_OR: - if ( InputState != r_IF ) return ERR_RULE; - return addPremise(r, r_OR, tok, nToks); - - case r_THEN: - if ( InputState != r_IF ) return ERR_RULE; - InputState = r_THEN; - return addAction(r, tok, nToks); - - case r_ELSE: - if ( InputState != r_THEN ) return ERR_RULE; - InputState = r_ELSE; - return addAction(r, tok, nToks); - - case r_PRIORITY: - if ( InputState != r_THEN && InputState != r_ELSE ) return ERR_RULE; - InputState = r_PRIORITY; - if ( !getDouble(tok[1], &Rules[r].priority) ) return ERR_NUMBER; - if ( nToks > 2 ) return ERR_RULE; - return 0; - } - return 0; -} - -//============================================================================= - -int controls_evaluate(DateTime currentTime, DateTime elapsedTime, double tStep) -// -// Input: currentTime = current simulation date/time -// elapsedTime = decimal days since start of simulation -// tStep = simulation time step (days) -// Output: returns number of new actions taken -// Purpose: evaluates all control rules at current time of the simulation. -// -{ - int r; // control rule index - int result; // TRUE if rule premises satisfied - struct TPremise* p; // pointer to rule premise clause - struct TAction* a; // pointer to rule action clause - - // --- save date and time to shared variables - CurrentDate = floor(currentTime); - CurrentTime = currentTime - floor(currentTime); - ElapsedTime = elapsedTime; - - // --- evaluate each rule - if ( RuleCount == 0 ) return 0; - clearActionList(); - for (r=0; rtype == r_OR ) - { - if ( result == FALSE ) - result = evaluatePremise(p, tStep); - } - else - { - if ( result == FALSE ) break; - result = evaluatePremise(p, tStep); - } - p = p->next; - } - - // --- if premises true, add THEN clauses to action list - // else add ELSE clauses to action list - if ( result == TRUE ) a = Rules[r].thenActions; - else a = Rules[r].elseActions; - while (a) - { - updateActionValue(a, currentTime, tStep); - updateActionList(a); - a = a->next; - } - } - - // --- execute actions on action list - if ( ActionList ) return executeActionList(currentTime); - else return 0; -} - -//============================================================================= - -int addPremise(int r, int type, char* tok[], int nToks) -// -// Input: r = control rule index -// type = type of premise (IF, AND, OR) -// tok = array of string tokens containing premise statement -// nToks = number of string tokens -// Output: returns an error code -// Purpose: adds a new premise to a control rule. -// -{ - int relation, n, err = 0; - double value = MISSING; - struct TPremise* p; - struct TVariable v1; - struct TVariable v2; - int obj, exprIndex, varIndex = -1; - - // --- initialize LHS variable v1 - if (nToks < 4) return ERR_ITEMS; - v1.attribute = -1; - v1.object = -1; - v1.index = -1; - n = 1; - - // --- check if 2nd token is a math expression - exprIndex = getExpressionIndex(tok[1]); - - // --- if not then check if it's a named variable - if (exprIndex < 0) - { - varIndex = getVariableIndex(tok[n]); - if (varIndex >= 0) - { - v1 = NamedVariable[varIndex].variable; - } - - // otherwise parse object|index|attribute tokens - else - { - err = getPremiseVariable(tok, nToks, &n, &v1); - if ( err > 0 ) return err; - } - } - - // --- get relational operator - n++; - if ( n >= nToks ) return error_setInpError(ERR_ITEMS, ""); - relation = findExactMatch(tok[n], RelOpWords); - if ( relation < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); - - // --- initialize RHS variable v2 - v2.attribute = -1; - v2.object = -1; - v2.index = -1; - n++; - if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); - - // --- check for named RHS variable - varIndex = getVariableIndex(tok[n]); - if (varIndex >= 0) - { - v2 = NamedVariable[varIndex].variable; - } - - // --- check for object|index|attribute variable - else - { - obj = findmatch(tok[n], ObjectWords); - if (obj >= 0) - { - err = getPremiseVariable(tok, nToks, &n, &v2); - if ( err > 0 ) return ERR_RULE; - if (exprIndex < 0 && v1.attribute != v2.attribute) - report_writeWarningMsg(WARN11, Rules[r].ID); - } - - // --- check for a single RHS value - else - { - err = getPremiseValue(tok[n], v1.attribute, &value); - if ( err > 0 ) return err; - } - } - - // --- make sure another clause is not on same line - n++; - if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE; - - // --- create the premise object - p = (struct TPremise *) malloc(sizeof(struct TPremise)); - if ( !p ) return ERR_MEMORY; - p->type = type; - p->exprIndex = exprIndex; - p->lhsVar = v1; - p->rhsVar = v2; - p->relation = relation; - p->value = value; - p->next = NULL; - if ( Rules[r].firstPremise == NULL ) - { - Rules[r].firstPremise = p; - } - else - { - Rules[r].lastPremise->next = p; - } - Rules[r].lastPremise = p; - return 0; -} - -//============================================================================= - -int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v) -// -// Input: tok = array of string tokens -// nToks = number of tokens -// k = index of current token -// Output: returns an error code; updates k to new current token and -// places identity of specified variable in v -// Purpose: parses a variable (e.g., Node 123 Depth) used in a control rule. -// -{ - int n = *k; - int object = -1; - int index = -1; - int obj, attrib; - - // --- get object type - obj = findmatch(tok[n], ObjectWords); - if ( obj < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); - - // --- get object index from its name - n++; - if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); - switch (obj) - { - case r_GAGE: - index = project_findObject(GAGE, tok[n]); - if (index < 0) return error_setInpError(ERR_NAME, tok[n]); - object = r_GAGE; - break; - - case r_NODE: - index = project_findObject(NODE, tok[n]); - if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]); - object = r_NODE; - break; - - case r_LINK: - case r_CONDUIT: - case r_PUMP: - case r_ORIFICE: - case r_WEIR: - case r_OUTLET: - index = project_findObject(LINK, tok[n]); - if ( index < 0 ) return error_setInpError(ERR_NAME, tok[n]); - object = r_LINK; - break; - default: n--; - } - n++; - if (n >= nToks) return error_setInpError(ERR_ITEMS, ""); - - // --- get attribute index from its name - if (object == r_GAGE) - attrib = getGageAttrib(tok[n]); - else - attrib = findmatch(tok[n], AttribWords); - if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); - - // --- check that attribute belongs to object type - if (obj == r_GAGE) - { - - } - - else if ( obj == r_NODE ) switch (attrib) - { - case r_DEPTH: - case r_MAXDEPTH: - case r_HEAD: - case r_VOLUME: - case r_INFLOW: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - - // --- check for link TIMEOPEN & TIMECLOSED attributes - else if ( object == r_LINK && index >= 0 && - ( (attrib == r_TIMEOPEN || attrib == r_TIMECLOSED) - )) - { - // nothing to do here - } - - else if ( obj == r_LINK || obj == r_CONDUIT ) switch (attrib) - { - case r_STATUS: - case r_DEPTH: - case r_FULLFLOW: - case r_FULLDEPTH: - case r_FLOW: - case r_LENGTH: - case r_SLOPE: - case r_VELOCITY: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - else if ( obj == r_PUMP ) switch (attrib) - { - case r_FLOW: - case r_SETTING: - case r_STATUS: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - else if ( obj == r_ORIFICE || obj == r_WEIR || - obj == r_OUTLET ) switch (attrib) - { - case r_FLOW: - case r_SETTING: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - else switch (attrib) - { - case r_TIME: - case r_DATE: - case r_CLOCKTIME: - case r_DAY: - case r_MONTH: - case r_DAYOFYEAR: break; - default: return error_setInpError(ERR_KEYWORD, tok[n]); - } - - // --- populate variable structure - v->object = object; - v->index = index; - v->attribute = attrib; - *k = n; - return 0; -} - -//============================================================================= - -int getGageAttrib(char* token) -// -// Input: token = a string token -// Output: returns an attribute code or -1 if an error occurred -// Purpose: determines the atrribute code for a rain gage variable. -// Note: a valid token is INTENSITY for current rainfall intensity -// (attribute code = 0) or nHR_PRECIP for total rain depth -// over past n hours (attribute code = n). -// -{ - int attrib; - - // --- check if token is currrent rainfall intensity - if (match(token, IntensityWord)) - return 0; - - // --- token is past rain depth - read number of past hours - attrib = atoi(token); - - // --- check that number of hours is in allowable range - if (attrib < 1 || attrib > MAXPASTRAIN) - return -1; - return attrib; -} - -//============================================================================= - -int getPremiseValue(char* token, int attrib, double* value) -// -// Input: token = a string token -// attrib = index of a node/link attribute -// Output: value = attribute value; -// returns an error code; -// Purpose: parses the numerical value of a particular node/link attribute -// in the premise clause of a control rule. -// -{ - char strDate[25]; - switch (attrib) - { - case r_STATUS: - *value = findmatch(token, StatusWords); - if ( *value < 0.0 ) *value = findmatch(token, ConduitWords); - if ( *value < 0.0 ) return error_setInpError(ERR_KEYWORD, token); - break; - - case r_TIME: - case r_CLOCKTIME: - case r_TIMEOPEN: - case r_TIMECLOSED: - if ( !datetime_strToTime(token, value) ) - return error_setInpError(ERR_DATETIME, token); - break; - - case r_DATE: - if ( !datetime_strToDate(token, value) ) - return error_setInpError(ERR_DATETIME, token); - break; - - case r_DAY: - if ( !getDouble(token, value) ) - return error_setInpError(ERR_NUMBER, token); - if ( *value < 1.0 || *value > 7.0 ) - return error_setInpError(ERR_DATETIME, token); - break; - - case r_MONTH: - if ( !getDouble(token, value) ) - return error_setInpError(ERR_NUMBER, token); - if ( *value < 1.0 || *value > 12.0 ) - return error_setInpError(ERR_DATETIME, token); - break; - - case r_DAYOFYEAR: - sstrncpy(strDate, token, 6); - sstrcat(strDate, "/1947", 25); - if ( datetime_strToDate(strDate, value) ) - { - *value = datetime_dayOfYear(*value); - } - else if ( !getDouble(token, value) || *value < 1 || *value > 365 ) - return error_setInpError(ERR_DATETIME, token); - break; - - default: if ( !getDouble(token, value) ) - return error_setInpError(ERR_NUMBER, token); - } - return 0; -} - -//============================================================================= - -int addAction(int r, char* tok[], int nToks) -// -// Input: r = control rule index -// tok = array of string tokens containing action statement -// nToks = number of string tokens -// Output: returns an error code -// Purpose: adds a new action to a control rule. -// -{ - int obj, link, attrib; - int curve = -1, tseries = -1; - int n; - int err; - double values[] = {1.0, 0.0, 0.0}; - - struct TAction* a; - - // --- check for proper number of tokens - if ( nToks < 6 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check for valid object type - obj = findmatch(tok[1], ObjectWords); - if ( obj != r_LINK && obj != r_CONDUIT && obj != r_PUMP && - obj != r_ORIFICE && obj != r_WEIR && obj != r_OUTLET ) - return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- check that object name exists and is of correct type - link = project_findObject(LINK, tok[2]); - if ( link < 0 ) return error_setInpError(ERR_NAME, tok[2]); - switch (obj) - { - case r_CONDUIT: - if ( Link[link].type != CONDUIT ) - return error_setInpError(ERR_NAME, tok[2]); - break; - case r_PUMP: - if ( Link[link].type != PUMP ) - return error_setInpError(ERR_NAME, tok[2]); - break; - case r_ORIFICE: - if ( Link[link].type != ORIFICE ) - return error_setInpError(ERR_NAME, tok[2]); - break; - case r_WEIR: - if ( Link[link].type != WEIR ) - return error_setInpError(ERR_NAME, tok[2]); - break; - case r_OUTLET: - if ( Link[link].type != OUTLET ) - return error_setInpError(ERR_NAME, tok[2]); - break; - } - - // --- check for valid attribute name - attrib = findmatch(tok[3], AttribWords); - if ( attrib < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); - - // --- get control action setting - if ( obj == r_CONDUIT ) - { - if ( attrib == r_STATUS ) - { - values[0] = findmatch(tok[5], ConduitWords); - if ( values[0] < 0.0 ) - return error_setInpError(ERR_KEYWORD, tok[5]); - } - else return error_setInpError(ERR_KEYWORD, tok[3]); - } - - else if ( obj == r_PUMP ) - { - if ( attrib == r_STATUS ) - { - values[0] = findmatch(tok[5], StatusWords); - if ( values[0] < 0.0 ) - return error_setInpError(ERR_KEYWORD, tok[5]); - } - else if ( attrib == r_SETTING ) - { - err = setActionSetting(tok, nToks, &curve, &tseries, - &attrib, values); - if ( err > 0 ) return err; - } - else return error_setInpError(ERR_KEYWORD, tok[3]); - } - - else if ( obj == r_ORIFICE || obj == r_WEIR || obj == r_OUTLET ) - { - if ( attrib == r_SETTING ) - { - err = setActionSetting(tok, nToks, &curve, &tseries, - &attrib, values); - if ( err > 0 ) return err; - if ( attrib == r_SETTING - && (values[0] < 0.0 || values[0] > 1.0) ) - return error_setInpError(ERR_NUMBER, tok[5]); - } - else return error_setInpError(ERR_KEYWORD, tok[3]); - } - else return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- check if another clause is on same line - n = 6; - if ( curve >= 0 || tseries >= 0 ) n = 7; - if ( attrib == r_PID ) n = 9; - if ( n < nToks && findmatch(tok[n], RuleKeyWords) >= 0 ) return ERR_RULE; - - // --- create the action object - a = (struct TAction *) malloc(sizeof(struct TAction)); - if ( !a ) return ERR_MEMORY; - a->rule = r; - a->link = link; - a->attribute = attrib; - a->curve = curve; - a->tseries = tseries; - a->value = values[0]; - if ( attrib == r_PID ) - { - a->kp = values[0]; - a->ki = values[1]; - a->kd = values[2]; - a->e1 = 0.0; - a->e2 = 0.0; - } - if ( InputState == r_THEN ) - { - a->next = Rules[r].thenActions; - Rules[r].thenActions = a; - } - else - { - a->next = Rules[r].elseActions; - Rules[r].elseActions = a; - } - return 0; -} - -//============================================================================= - -int setActionSetting(char* tok[], int nToks, int* curve, int* tseries, - int* attrib, double values[]) -// -// Input: tok = array of string tokens containing action statement -// nToks = number of string tokens -// Output: curve = index of controller curve -// tseries = index of controller time series -// attrib = r_PID if PID controller used -// values = values of control settings -// returns an error code -// Purpose: identifies how control actions settings are determined. -// -{ - int k, m; - - // --- see if control action is determined by a Curve or Time Series - if (nToks < 6) return error_setInpError(ERR_ITEMS, ""); - k = findmatch(tok[5], SettingTypeWords); - if ( k >= 0 && nToks < 7 ) return error_setInpError(ERR_ITEMS, ""); - switch (k) - { - - // --- control determined by a curve - find curve index - case r_CURVE: - m = project_findObject(CURVE, tok[6]); - if ( m < 0 ) return error_setInpError(ERR_NAME, tok[6]); - *curve = m; - break; - - // --- control determined by a time series - find time series index - case r_TIMESERIES: - m = project_findObject(TSERIES, tok[6]); - if ( m < 0 ) return error_setInpError(ERR_NAME, tok[6]); - *tseries = m; - Tseries[m].refersTo = CONTROL; - break; - - // --- control determined by PID controller - case r_PID: - if (nToks < 9) return error_setInpError(ERR_ITEMS, ""); - for (m=6; m<=8; m++) - { - if ( !getDouble(tok[m], &values[m-6]) ) - return error_setInpError(ERR_NUMBER, tok[m]); - } - *attrib = r_PID; - break; - - // --- direct numerical control is used - default: - if ( !getDouble(tok[5], &values[0]) ) - return error_setInpError(ERR_NUMBER, tok[5]); - } - return 0; -} - -//============================================================================= - -void updateActionValue(struct TAction* a, DateTime currentTime, double dt) -// -// Input: a = an action object -// currentTime = current simulation date/time (days) -// dt = time step (days) -// Output: none -// Purpose: updates value of actions found from Curves or Time Series. -// -{ - if ( a->curve >= 0 ) - { - a->value = table_lookup(&Curve[a->curve], ControlValue); - } - else if ( a->tseries >= 0 ) - { - a->value = table_tseriesLookup(&Tseries[a->tseries], currentTime, TRUE); - } - else if ( a->attribute == r_PID ) - { - a->value = getPIDSetting(a, dt); - } -} - -//============================================================================= - -double getPIDSetting(struct TAction* a, double dt) -// -// Input: a = an action object -// dt = current time step (days) -// Output: returns a new link setting -// Purpose: computes a new setting for a link subject to a PID controller. -// -// Note: a->kp = gain coefficient, -// a->ki = integral time (minutes) -// a->k2 = derivative time (minutes) -// a->e1 = error from previous time step -// a->e2 = error from two time steps ago -{ - double e0, setting; - double p, i, d, update; - double tolerance = 0.0001; - - // --- convert time step from days to minutes - dt *= 1440.0; - - // --- determine relative error in achieving controller set point - e0 = SetPoint - ControlValue; - if ( fabs(e0) > TINY ) - { - if ( SetPoint != 0.0 ) e0 = e0/SetPoint; - else e0 = e0/ControlValue; - } - - // --- reset previous errors to 0 if controller gets stuck - if (fabs(e0 - a->e1) < tolerance) - { - a->e2 = 0.0; - a->e1 = 0.0; - } - - // --- use the recursive form of the PID controller equation to - // determine the new setting for the controlled link - p = (e0 - a->e1); - if ( a->ki == 0.0 ) i = 0.0; - else i = e0 * dt / a->ki; - d = a->kd * (e0 - 2.0*a->e1 + a->e2) / dt; - update = a->kp * (p + i + d); - if ( fabs(update) < tolerance ) update = 0.0; - setting = Link[a->link].targetSetting + update; - - // --- update previous errors - a->e2 = a->e1; - a->e1 = e0; - - // --- check that new setting lies within feasible limits - if ( setting < 0.0 ) setting = 0.0; - if (Link[a->link].type != PUMP && setting > 1.0 ) setting = 1.0; - return setting; -} - -//============================================================================= - -void updateActionList(struct TAction* a) -// -// Input: a = an action object -// Output: none -// Purpose: adds a new action to the list of actions to be taken. -// -{ - struct TActionList* listItem; - struct TAction* a1; - double priority = Rules[a->rule].priority; - - // --- check if link referred to in action is already listed - listItem = ActionList; - while ( listItem ) - { - a1 = listItem->action; - if ( !a1 ) break; - if ( a1->link == a->link ) - { - // --- replace old action if new action has higher priority - if ( priority > Rules[a1->rule].priority ) listItem->action = a; - return; - } - listItem = listItem->next; - } - - // --- action not listed so add it to ActionList //5.2.1 - if ( !listItem ) - { - listItem = (struct TActionList *) malloc(sizeof(struct TActionList)); - listItem->next = ActionList; - ActionList = listItem; - } - listItem->action = a; -} - -//============================================================================= - -int executeActionList(DateTime currentTime) -// -// Input: currentTime = current date/time of the simulation -// Output: returns number of new actions taken -// Purpose: executes all actions required by fired control rules. -// -{ - struct TActionList* listItem; - struct TActionList* nextItem; - struct TAction* a1; - int count = 0; - - listItem = ActionList; - while ( listItem ) - { - a1 = listItem->action; - if ( !a1 ) break; - if ( a1->link >= 0 ) - { - if ( Link[a1->link].targetSetting != a1->value ) - { - Link[a1->link].targetSetting = a1->value; - if ( RptFlags.controls && a1->curve < 0 - && a1->tseries < 0 && a1->attribute != r_PID ) - report_writeControlAction(currentTime, Link[a1->link].ID, - a1->value, Rules[a1->rule].ID); - count++; - } - } - nextItem = listItem->next; - listItem = nextItem; - } - return count; -} - -//============================================================================= - -int evaluatePremise(struct TPremise* p, double tStep) -// -// Input: p = a control rule premise condition -// tStep = current time step (days) -// Output: returns TRUE if the condition is true or FALSE otherwise -// Purpose: evaluates the truth of a control rule premise condition. -// -{ - double lhsValue, rhsValue; - int result = FALSE; - - // --- check if left hand side (lhs) of premise is an expression - if (p->exprIndex >= 0) - lhsValue = mathexpr_eval(Expression[p->exprIndex].expression, - getNamedVariableValue); - - // --- otherwise get value of the lhs variable - else - lhsValue = getVariableValue(p->lhsVar); - - // --- if right hand side (rhs) of premise is a variable then get its value - if ( p->value == MISSING ) rhsValue = getVariableValue(p->rhsVar); - else rhsValue = p->value; - if ( lhsValue == MISSING || rhsValue == MISSING ) return FALSE; - - // --- compare the lhs of the premise to the rhs - switch (p->lhsVar.attribute) - { - case r_TIME: - case r_CLOCKTIME: - return compareTimes(lhsValue, p->relation, rhsValue, tStep/2.0); - case r_TIMEOPEN: - case r_TIMECLOSED: - result = compareTimes(lhsValue, p->relation, rhsValue, tStep/2.0); - ControlValue = lhsValue * 24.0; // convert time from days to hours - return result; - default: - return compareValues(lhsValue, p->relation, rhsValue); - } -} - -//============================================================================= - -double getVariableValue(struct TVariable v) -{ - int i = -1; // a node index - int j = -1; // a link index - - if (v.object == r_GAGE) - return getRainValue(v); - if (v.object == r_NODE) i = v.index; - if (v.object == r_LINK) j = v.index; - - switch ( v.attribute ) - { - case r_TIME: - return ElapsedTime; - - case r_DATE: - return CurrentDate; - - case r_CLOCKTIME: - return CurrentTime; - - case r_DAY: - return datetime_dayOfWeek(CurrentDate); - - case r_MONTH: - return datetime_monthOfYear(CurrentDate); - - case r_DAYOFYEAR: - return datetime_dayOfYear(CurrentDate); - - case r_STATUS: - if ( j < 0 || - (Link[j].type != CONDUIT && Link[j].type != PUMP) ) return MISSING; - else return Link[j].setting; - - case r_SETTING: - if ( j < 0 || (Link[j].type != PUMP && - Link[j].type != ORIFICE && - Link[j].type != WEIR) ) - return MISSING; - else return Link[j].setting; - - case r_FLOW: - if ( j < 0 ) return MISSING; - else return Link[j].direction*Link[j].newFlow*UCF(FLOW); - - case r_FULLFLOW: - case r_FULLDEPTH: - case r_VELOCITY: - case r_LENGTH: - case r_SLOPE: - if ( j < 0 ) return MISSING; - else if (Link[j].type != CONDUIT) return MISSING; - switch (v.attribute) - { - case r_FULLFLOW: return Link[j].qFull * UCF(FLOW); - case r_FULLDEPTH: return Link[j].xsect.yFull * UCF(LENGTH); - case r_VELOCITY: - return link_getVelocity(j, Link[j].newFlow, Link[j].newDepth) - * UCF(LENGTH); - case r_LENGTH: return Conduit[Link[j].subIndex].length * UCF(LENGTH); - case r_SLOPE: return Conduit[Link[j].subIndex].slope; - default: return MISSING; - } - case r_DEPTH: - if ( j >= 0 ) return Link[j].newDepth*UCF(LENGTH); - else if ( i >= 0 ) - return Node[i].newDepth*UCF(LENGTH); - else return MISSING; - - case r_MAXDEPTH: - if (i >= 0) return Node[i].fullDepth*UCF(LENGTH); - else return MISSING; - - case r_HEAD: - if ( i < 0 ) return MISSING; - return (Node[i].newDepth + Node[i].invertElev) * UCF(LENGTH); - - case r_VOLUME: - if ( i < 0 ) return MISSING; - return (Node[i].newVolume * UCF(VOLUME)); - - case r_INFLOW: - if ( i < 0 ) return MISSING; - else return Node[i].newLatFlow*UCF(FLOW); - - case r_TIMEOPEN: - if ( j < 0 ) return MISSING; - if ( Link[j].setting <= 0.0 ) return MISSING; - return CurrentDate + CurrentTime - Link[j].timeLastSet; - - case r_TIMECLOSED: - if ( j < 0 ) return MISSING; - if ( Link[j].setting > 0.0 ) return MISSING; - return CurrentDate + CurrentTime - Link[j].timeLastSet; - - default: return MISSING; - } -} - -//============================================================================= - -double getRainValue(struct TVariable v) -// -// Input: v = a rule premise variable for a rain gage -// Output: returns current or past rainfall amount -// Purpose: retrieves either the current rainfall intensity or the past -// rainfall total for a rain gage. -// -{ - if (v.index < 0) return MISSING; - else if (Gage[v.index].isUsed == FALSE) return 0.0; - else if (v.attribute == 0) - return Gage[v.index].rainfall; - else return gage_getPastRain(v.index, v.attribute); -} - -//============================================================================= - -int compareTimes(double lhsValue, int relation, double rhsValue, double halfStep) -// -// Input: lhsValue = date/time value on left hand side of relation -// relation = relational operator code (see RuleRelation enumeration) -// rhsValue = date/time value on right hand side of relation -// halfStep = 1/2 the current time step (days) -// Output: returns TRUE if time relation is satisfied -// Purpose: evaluates the truth of a relation between two date/times. -// -{ - if ( relation == EQ ) - { - if ( lhsValue >= rhsValue - halfStep - && lhsValue < rhsValue + halfStep ) return TRUE; - return FALSE; - } - else if ( relation == NE ) - { - if ( lhsValue < rhsValue - halfStep - || lhsValue >= rhsValue + halfStep ) return TRUE; - return FALSE; - } - else return compareValues(lhsValue, relation, rhsValue); -} - -//============================================================================= - -int compareValues(double lhsValue, int relation, double rhsValue) -// Input: lhsValue = value on left hand side of relation -// relation = relational operator code (see RuleRelation enumeration) -// rhsValue = value on right hand side of relation -// Output: returns TRUE if relation is satisfied -// Purpose: evaluates the truth of a relation between two values. -{ - SetPoint = rhsValue; - ControlValue = lhsValue; - switch (relation) - { - case EQ: if ( lhsValue == rhsValue ) return TRUE; break; - case NE: if ( lhsValue != rhsValue ) return TRUE; break; - case LT: if ( lhsValue < rhsValue ) return TRUE; break; - case LE: if ( lhsValue <= rhsValue ) return TRUE; break; - case GT: if ( lhsValue > rhsValue ) return TRUE; break; - case GE: if ( lhsValue >= rhsValue ) return TRUE; break; - } - return FALSE; -} - -//============================================================================= - -void clearActionList(void) -// -// Input: none -// Output: none -// Purpose: clears the list of actions to be executed. -// -{ - struct TActionList* listItem; - listItem = ActionList; - while ( listItem ) - { - listItem->action = NULL; - listItem = listItem->next; - } -} - -//============================================================================= - -void deleteActionList(void) -// -// Input: none -// Output: none -// Purpose: frees the memory used to hold the list of actions to be executed. -// -{ - struct TActionList* listItem; - struct TActionList* nextItem; - listItem = ActionList; - while ( listItem ) - { - nextItem = listItem->next; - free(listItem); - listItem = nextItem; - } - ActionList = NULL; -} - -//============================================================================= - -void deleteRules(void) -// -// Input: none -// Output: none -// Purpose: frees the memory used for all of the control rules. -// -{ - struct TPremise* p; - struct TPremise* pnext; - struct TAction* a; - struct TAction* anext; - int r; - for (r=0; rnext; - free(p); - p = pnext; - } - a = Rules[r].thenActions; - while (a ) - { - anext = a->next; - free(a); - a = anext; - } - a = Rules[r].elseActions; - while (a ) - { - anext = a->next; - free(a); - a = anext; - } - } - FREE(Rules); - RuleCount = 0; -} - -//============================================================================= - -int findExactMatch(char *s, char *keyword[]) -// -// Input: s = character string -// keyword = array of keyword strings -// Output: returns index of keyword which matches s or -1 if no match found -// Purpose: finds exact match between string and array of keyword strings. -// -{ - int i = 0; - while (keyword[i] != NULL) - { - if ( strcomp(s, keyword[i]) ) return(i); - i++; - } - return(-1); -} - -//============================================================================= diff --git a/src/culvert.c b/src/culvert.c deleted file mode 100644 index 5f62877b7..000000000 --- a/src/culvert.c +++ /dev/null @@ -1,411 +0,0 @@ -//----------------------------------------------------------------------------- -// culvert.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Culvert equations for SWMM5 -// -// Computes flow reduction in a culvert-type conduit due to -// inlet control using equations from the FHWA HEC-5 circular. -// -// Update History -// ============== -// Build 5.1.013: -// - C parameter corrected for Arch, Corrugated Metal, Mitered culvert. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "findroot.h" -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -enum CulvertParam {FORM, K, M, C, Y}; -static const int MAX_CULVERT_CODE = 57; -static const double Params[58][5] = { - -// FORM K M C Y -//------------------------------------ - {0.0, 0.0, 0.0, 0.0, 0.00}, - - //Circular concrete - {1.0, 0.0098, 2.00, 0.0398, 0.67}, //Square edge w/headwall - {1.0, 0.0018, 2.00, 0.0292, 0.74}, //Groove end w/headwall - {1.0, 0.0045, 2.00, 0.0317, 0.69}, //Groove end projecting - - //Circular Corrugated Metal Pipe - {1.0, 0.0078, 2.00, 0.0379, 0.69}, //Headwall - {1.0, 0.0210, 1.33, 0.0463, 0.75}, //Mitered to slope - {1.0, 0.0340, 1.50, 0.0553, 0.54}, //Projecting - - //Circular Pipe, Beveled Ring Entrance - {1.0, 0.0018, 2.50, 0.0300, 0.74}, //Beveled ring, 45 deg bevels - {1.0, 0.0018, 2.50, 0.0243, 0.83}, //Beveled ring, 33.7 deg bevels - - //Rectangular Box with Flared Wingwalls - {1.0, 0.026, 1.0, 0.0347, 0.81}, //30-75 deg. wingwall flares - {1.0, 0.061, 0.75, 0.0400, 0.80}, //90 or 15 deg. wingwall flares - {1.0, 0.061, 0.75, 0.0423, 0.82}, //0 deg. wingwall flares (striaght sides) - - //Rectanglar Box with Flared Wingwalls & Top Edge Bevel - {2.0, 0.510, 0.667, 0.0309, 0.80}, //45 deg. flare; 0.43D top edge bevel - {2.0, 0.486, 0.667, 0.0249, 0.83}, //18-33.7 deg flare; 0.083D top edge bevel - - //Rectangular Box; 90-deg Headwall; Chamfered or Beveled Inlet Edges - {2.0, 0.515, 0.667, 0.0375, 0.79}, //chamfered 3/4-in - {2.0, 0.495, 0.667, 0.0314, 0.82}, //beveled 1/2-in/ft at 45 deg (1:1) - {2.0, 0.486, 0.667, 0.0252, 0.865}, //beveled 1-in/ft at 33.7 deg (1:1.5) - - //Rectangular Box; Skewed Headwall; Chamfered or Beveled Inlet Edges - {2.0, 0.545, 0.667, 0.04505,0.73}, //3/4" chamfered edge, 45 deg skewed headwall - {2.0, 0.533, 0.667, 0.0425, 0.705}, //3/4" chamfered edge, 30 deg skewed headwall - {2.0, 0.522, 0.667, 0.0402, 0.68}, //3/4" chamfered edge, 15 deg skewed headwall - {2.0, 0.498, 0.667, 0.0327, 0.75}, //45 deg beveled edge, 10-45 deg skewed headwall - - //Rectangular box, Non-offset Flared Wingwalls; 3/4" Chamfer at Top of Inlet - {2.0, 0.497, 0.667, 0.0339, 0.803}, //45 deg (1:1) wingwall flare - {2.0, 0.493, 0.667, 0.0361, 0.806}, //18.4 deg (3:1) wingwall flare - {2.0, 0.495, 0.667, 0.0386, 0.71}, //18.4 deg (3:1) wingwall flare, 30 deg inlet skew - - //Rectangular box, Offset Flared Wingwalls, Beveled Edge at Inlet Top - {2.0, 0.497, 0.667, 0.0302, 0.835}, //45 deg (1:1) flare, 0.042D top edge bevel - {2.0, 0.495, 0.667, 0.0252, 0.881}, //33.7 deg (1.5:1) flare, 0.083D top edge bevel - {2.0, 0.493, 0.667, 0.0227, 0.887}, //18.4 deg (3:1) flare, 0.083D top edge bevel - - // Corrugated Metal Box - {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall - {1.0, 0.0145, 1.75, 0.0419, 0.64}, //Thick wall projecting - {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Thin wall projecting - - // Horizontal Ellipse Concrete - {1.0, 0.0100, 2.00, 0.0398, 0.67}, //Square edge w/headwall - {1.0, 0.0018, 2.50, 0.0292, 0.74}, //Grooved end w/headwall - {1.0, 0.0045, 2.00, 0.0317, 0.69}, //Grooved end projecting - - // Vertical Ellipse Concrete - {1.0, 0.0100, 2.00, 0.0398, 0.67}, //Square edge w/headwall - {1.0, 0.0018, 2.50, 0.0292, 0.74}, //Grooved end w/headwall - {1.0, 0.0095, 2.00, 0.0317, 0.69}, //Grooved end projecting - - // Pipe Arch, 18" Corner Radius, Corrugated Metal - {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall - {1.0, 0.0300, 1.00, 0.0463, 0.75}, //Mitered to slope - {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Projecting - - // Pipe Arch, 18" Corner Radius, Corrugated Metal - {1.0, 0.0300, 1.50, 0.0496, 0.57}, //Projecting - {1.0, 0.0088, 2.00, 0.0368, 0.68}, //No bevels - {1.0, 0.0030, 2.00, 0.0269, 0.77}, //33.7 deg bevels - - // Pipe Arch, 31" Corner Radius, Corrugated Metal - {1.0, 0.0300, 1.50, 0.0496, 0.57}, //Projecting - {1.0, 0.0088, 2.00, 0.0368, 0.68}, //No bevels - {1.0, 0.0030, 2.00, 0.0269, 0.77}, //33.7 deg. bevels - - // Arch, Corrugated Metal - {1.0, 0.0083, 2.00, 0.0379, 0.69}, //90 deg headwall - {1.0, 0.0300, 1.00, 0.0473, 0.75}, //Mitered to slope - {1.0, 0.0340, 1.50, 0.0496, 0.57}, //Thin wall projecting - - // Circular Culvert - {2.0, 0.534, 0.555, 0.0196, 0.90}, //Smooth tapered inlet throat - {2.0, 0.519, 0.640, 0.0210, 0.90}, //Rough tapered inlet throat - - // Elliptical Inlet Face - {2.0, 0.536, 0.622, 0.0368, 0.83}, //Tapered inlet, beveled edges - {2.0, 0.5035,0.719, 0.0478, 0.80}, //Tapered inlet, square edges - {2.0, 0.547, 0.800, 0.0598, 0.75}, //Tapered inlet, thin edge projecting - - // Rectangular - {2.0, 0.475, 0.667, 0.0179, 0.97}, //Tapered inlet throat - - // Rectangular Concrete - {2.0, 0.560, 0.667, 0.0446, 0.85}, //Side tapered, less favorable edges - {2.0, 0.560, 0.667, 0.0378, 0.87}, //Side tapered, more favorable edges - - // Rectangular Concrete - {2.0, 0.500, 0.667, 0.0446, 0.65}, //Slope tapered, less favorable edges - {2.0, 0.500, 0.667, 0.0378, 0.71} //Slope tapered, more favorable edges - - }; - -//----------------------------------------------------------------------------- -// Culvert data structure -//----------------------------------------------------------------------------- -typedef struct -{ - double yFull; // full depth of culvert (ft) - double scf; // slope correction factor - double dQdH; // Derivative of flow w.r.t. head - double qc; // Unsubmerged critical flow - double kk; - double mm; // Coeffs. for unsubmerged flow - double ad; - double hPlus; // Intermediate terms - TXsect* xsect; // Pointer to culvert cross section -} TCulvert; - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// double culvert_getInflow - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static double getUnsubmergedFlow(int code, double h, TCulvert* culvert); -static double getSubmergedFlow(int code, double h, TCulvert* culvert); -static double getTransitionFlow(int code, double h, double h1, double h2, - TCulvert* culvert); -static double getForm1Flow(double h, TCulvert* culvert); -static double form1Eqn(double yc, void* p); -/* -static void report_CulvertControl(int j, double q0, double q, int condition, - double yRatio); //for debugging only -*/ - -//============================================================================= - -double culvert_getInflow(int j, double q0, double h) -// -// Input: j = link index -// q0 = unmodified flow rate (cfs) -// h = upstream head (ft) -// Output: returns modified flow rate through culvert (cfs) -// Purpose: uses FHWA HEC-5 equations to find flow through inlet -// controlled culverts -// -{ - int code, //culvert type code number - k, //conduit index - condition; //flow condition - double y, //current depth (ft) - y1, //unsubmerged depth limit (ft) - y2, //submerged depth limit (ft) - q; //inlet-controlled flow (cfs) - TCulvert culvert; //intermediate results - - // --- check that we have a culvert conduit - if ( Link[j].type != CONDUIT ) return q0; - culvert.xsect = &Link[j].xsect; - code = culvert.xsect->culvertCode; - if ( code <= 0 || code > MAX_CULVERT_CODE ) return q0; - - // --- compute often-used variables - k = Link[j].subIndex; - culvert.yFull = culvert.xsect->yFull; - culvert.ad = culvert.xsect->aFull * sqrt(culvert.yFull); - - // --- slope correction factor (-7 for mitered inlets, 0.5 for others) - switch (code) - { - case 5: - case 37: - case 46: culvert.scf = -7.0 * Conduit[k].slope; break; - default: culvert.scf = 0.5 * Conduit[k].slope; - } - - // --- find head relative to culvert's upstream invert - // (can be greater than yFull when inlet is submerged) - y = h - (Node[Link[j].node1].invertElev + Link[j].offset1); - - // --- check for submerged flow (based on FHWA criteria of Q/AD > 4) - y2 = culvert.yFull * (16.0 * Params[code][C] + Params[code][Y] - culvert.scf); - if ( y >= y2 ) - { - q = getSubmergedFlow(code, y, &culvert); - condition = 2; - } - else - { - // --- check for unsubmerged flow (based on arbitrary limit of 0.95 full) - y1 = 0.95 * culvert.yFull; - if ( y <= y1 ) - { - q = getUnsubmergedFlow(code, y, &culvert); - condition = 1; - } - // --- flow is in transition zone - else - { - q = getTransitionFlow(code, y, y1, y2, &culvert); - condition = 0; - } - } - - // --- check if inlet controls and replace conduit's value of dq/dh - if ( q < q0 ) - { - // --- for debugging only - //if ( RptFlags.controls ) report_CulvertControl(j, q0, q, condition, - // y / culvert.yFull); - - Link[j].inletControl = TRUE; - Link[j].dqdh = culvert.dQdH; - return q; - } - else return q0; -} - -//============================================================================= - -double getUnsubmergedFlow(int code, double h, TCulvert* culvert) -// -// Input: code = culvert type code number -// h = inlet water depth above culvert invert -// culvert = pointer to a culvert data structure -// Output: returns flow rate; -// computes value of variable Dqdh -// Purpose: computes flow rate and its derivative for unsubmerged -// culvert inlet. -// -{ - double arg; - double q; - - // --- assign shared variables - culvert->kk = Params[code][K]; - culvert->mm = Params[code][M]; - arg = h / culvert->yFull / culvert->kk; - - // --- evaluate correct equation form - if ( Params[code][FORM] == 1.0) - { - q = getForm1Flow(h, culvert); - } - else q = culvert->ad * pow(arg, 1.0/culvert->mm); - culvert->dQdH = q / h / culvert->mm; - return q; -} - -//============================================================================= - -double getSubmergedFlow(int code, double h, TCulvert* culvert) -// -// Input: code = culvert type code number -// h = inlet head (ft) -// culvert = pointer to a culvert data structure -// Output: returns flow rate; -// computes value of Dqdh -// Purpose: computes flow rate and its derivative for submerged -// culvert inlet. -// -{ - double cc = Params[code][C]; - double yy = Params[code][Y]; - double arg = (h/culvert->yFull - yy + culvert->scf) / cc ; - double q; - - if ( arg <= 0.0 ) - { - culvert->dQdH = 0.0; - return BIG; - } - q = sqrt(arg) * culvert->ad; - culvert->dQdH = 0.5 * q / arg / culvert->yFull / cc; - return q; -} - -//============================================================================= - -double getTransitionFlow(int code, double h, double h1, double h2, TCulvert* culvert) -// -// Input: code = culvert type code number -// h = inlet water depth above culvert invert (ft) -// h1 = head limit for unsubmerged condition (ft) -// h2 = head limit for submerged condition (ft) -// culvert = pointer to a culvert data structure -// Output: returns flow rate )cfs); -// computes value of Dqdh (cfs/ft) -// Purpose: computes flow rate and its derivative for inlet-controlled flow -// when inlet water depth lies in the transition range between -// submerged and unsubmerged conditions. -// -{ - double q1 = getUnsubmergedFlow(code, h1, culvert); - double q2 = getSubmergedFlow(code, h2, culvert); - double q = q1 + (q2 - q1) * (h - h1) / (h2 - h1); - culvert->dQdH = (q2 - q1) / (h2 - h1); - return q; -} - -//============================================================================= - -double getForm1Flow(double h, TCulvert* culvert) -// -// Input: h = inlet water depth above culvert invert -// culvert = pointer to a culvert data structure -// Output: returns inlet controlled flow rate -// Purpose: computes inlet-controlled flow rate for unsubmerged culvert -// using FHWA Equation Form1. -// -// See pages 195-196 of FHWA HEC-5 (2001) for details. -// -{ - // --- save re-used terms in culvert structure - culvert->hPlus = h / culvert->yFull + culvert->scf; - - // --- use Ridder's method to solve Equation Form 1 for critical depth - // between a range of 0.01h and h - findroot_Ridder(0.01*h, h, 0.001, form1Eqn, culvert); - - // --- return the flow value used in evaluating Equation Form 1 - return culvert->qc; -} - -//============================================================================= - -double form1Eqn(double yc, void* p) -// -// Input: yc = critical depth -// p = pointer to a TCulvert object -// Output: returns residual error -// Purpose: evaluates the error in satisfying FHWA culvert Equation Form1: -// -// h/yFull + 0.5*s = yc/yFull + yh/2/yFull + K[ac/aFull*sqrt(g*yh/yFull)]^M -// -// for a given value of critical depth yc where: -// h = inlet depth above culvert invert -// s = culvert slope -// yFull = full depth of culvert -// yh = hydraulic depth at critical depth -// ac = flow area at critical depth -// g = accel. of gravity -// K and M = coefficients -// -{ - double ac, wc, yh; - TCulvert* culvert = (TCulvert *)p; - - ac = xsect_getAofY(culvert->xsect, yc); - wc = xsect_getWofY(culvert->xsect, yc); - yh = ac/wc; - - culvert->qc = ac * sqrt(GRAVITY * yh); - return culvert->hPlus - yc/culvert->yFull - yh/2.0/culvert->yFull - - culvert->kk * pow(culvert->qc/culvert->ad, culvert->mm); -} - -//============================================================================= -/* -void report_CulvertControl(int j, double q0, double q, int condition, double yRatio) -// -// Used for debugging only -// -{ - static char* conditionTxt[] = {"transition", "unsubmerged", "submerged"}; - char theDate[12]; - char theTime[9]; - DateTime aDate = getDateTime(NewRoutingTime); - datetime_dateToStr(aDate, theDate); - datetime_timeToStr(aDate, theTime); - fprintf(Frpt.file, - "\n %11s: %8s Culvert %s flow reduced from %.3f to %.3f cfs for %s flow (%.2f).", - theDate, theTime, Link[j].ID, q0, q, conditionTxt[condition], yRatio); -} -*/ diff --git a/src/datetime.c b/src/datetime.c deleted file mode 100644 index d96ef7f3b..000000000 --- a/src/datetime.c +++ /dev/null @@ -1,528 +0,0 @@ -//----------------------------------------------------------------------------- -// datetime.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// DateTime functions. -// -// Update History -// ============== -// Build 5.1.011: -// - decodeTime() no longer rounds up. -// - New getTimeStamp function added. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include -#include "datetime.h" - -// Macro to convert charcter x to upper case -#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const char* MonthTxt[] = - {"JAN", "FEB", "MAR", "APR", - "MAY", "JUN", "JUL", "AUG", - "SEP", "OCT", "NOV", "DEC"}; -static const int DaysPerMonth[2][12] = // days per month - {{31, 28, 31, 30, 31, 30, // normal years - 31, 31, 30, 31, 30, 31}, - {31, 29, 31, 30, 31, 30, // leap years - 31, 31, 30, 31, 30, 31}}; -static const int DateDelta = 693594; // days since 01/01/00 -static const double SecsPerDay = 86400.; // seconds per day - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -static int DateFormat; - - -//============================================================================= - -void divMod(int n, int d, int* result, int* remainder) - -// Input: n = numerator -// d = denominator -// Output: result = integer part of n/d -// remainder = remainder of n/d -// Purpose: finds integer part and remainder of n/d. - -{ - if (d == 0) - { - *result = 0; - *remainder = 0; - } - else - { - *result = n/d; - *remainder = n - d*(*result); - } -} - -//============================================================================= - -int isLeapYear(int year) - -// Input: year = a year -// Output: returns 1 if year is a leap year, 0 if not -// Purpose: determines if year is a leap year. - -{ - if ((year % 4 == 0) - && ((year % 100 != 0) - || (year % 400 == 0))) return 1; - else return 0; -} - -//============================================================================= - -int datetime_findMonth(char* month) - -// Input: month = month of year as character string -// Output: returns: month of year as a number (1-12) -// Purpose: finds number (1-12) of month. - -{ - int i; - for (i = 0; i < 12; i++) - { - if (UCHAR(month[0]) == MonthTxt[i][0] - && UCHAR(month[1]) == MonthTxt[i][1] - && UCHAR(month[2]) == MonthTxt[i][2]) return i+1; - } - return 0; -} - -//============================================================================= - -DateTime datetime_encodeDate(int year, int month, int day) - -// Input: year = a year -// month = a month (1 to 12) -// day = a day of month -// Output: returns encoded value of year-month-day -// Purpose: encodes year-month-day to a DateTime value. - -{ - int i, j; - i = isLeapYear(year); - if ((year >= 1) - && (year <= 9999) - && (month >= 1) - && (month <= 12) - && (day >= 1) - && (day <= DaysPerMonth[i][month-1])) - { - for (j = 0; j < month-1; j++) day += DaysPerMonth[i][j]; - i = year - 1; - i = i*365 + i/4 - i/100 + i/400 + day - DateDelta; - return i; - } - else return -DateDelta; -} - -//============================================================================= - -DateTime datetime_encodeTime(int hour, int minute, int second) - -// Input: hour = hour of day (0-24) -// minute = minute of hour (0-60) -// second = seconds of minute (0-60) -// Output: returns time encoded as fractional part of a day -// Purpose: encodes hour:minute:second to a DateTime value - -{ - int s; - if ((hour >= 0) - && (minute >= 0) - && (second >= 0)) - { - s = (hour * 3600 + minute * 60 + second); - return (double)s/SecsPerDay; - } - else return 0.0; -} - -//============================================================================= - -void datetime_decodeDate(DateTime date, int* year, int* month, int* day) - -// Input: date = encoded date/time value -// Output: year = 4-digit year -// month = month of year (1-12) -// day = day of month -// Purpose: decodes DateTime value to year-month-day. - -{ - int D1, D4, D100, D400; - int y, m, d, i, k, t; - - D1 = 365; //365 - D4 = D1 * 4 + 1; //1461 - D100 = D4 * 25 - 1; //36524 - D400 = D100 * 4 + 1; //146097 - - t = (int)(floor (date)) + DateDelta; - if (t <= 0) - { - *year = 0; - *month = 1; - *day = 1; - } - else - { - t--; - y = 1; - while (t >= D400) - { - t -= D400; - y += 400; - } - divMod(t, D100, &i, &d); - if (i == 4) - { - i--; - d += D100; - } - y += i*100; - divMod(d, D4, &i, &d); - y += i*4; - divMod(d, D1, &i, &d); - if (i == 4) - { - i--; - d += D1; - } - y += i; - k = isLeapYear(y); - m = 1; - for (;;) - { - i = DaysPerMonth[k][m-1]; - if (d < i) break; - d -= i; - m++; - } - *year = y; - *month = m; - *day = d + 1; - } -} - -//============================================================================= - -void datetime_decodeTime(DateTime time, int* h, int* m, int* s) - -// Input: time = decimal fraction of a day -// Output: h = hour of day (0-23) -// m = minute of hour (0-59) -// s = second of minute (0-59) -// Purpose: decodes DateTime value to hour:minute:second. - -{ - int secs; - int mins; - double fracDay = (time - floor(time)) * SecsPerDay; - secs = (int)(floor(fracDay + 0.5)); - if ( secs >= 86400 ) secs = 86399; - divMod(secs, 60, &mins, s); - divMod(mins, 60, h, m); - if ( *h > 23 ) *h = 0; -} - -//============================================================================= - -void datetime_dateToStr(DateTime date, char* s) - -// Input: date = encoded date/time value -// Output: s = formatted date string -// Purpose: represents DateTime date value as a formatted string. - -{ - int y, m, d; - datetime_decodeDate(date, &y, &m, &d); - switch (DateFormat) - { - case Y_M_D: - snprintf(s, DATE_STR_SIZE, "%4d-%3s-%02d", y, MonthTxt[m-1], d); - break; - - case M_D_Y: - //sprintf(dateStr, "%3s-%02d-%4d", MonthTxt[m-1], d, y); - snprintf(s, DATE_STR_SIZE, "%02d/%02d/%04d", m, d, y); - break; - - default: - snprintf(s, DATE_STR_SIZE, "%02d-%3s-%4d", d, MonthTxt[m-1], y); - } -} - -void datetime_timeToStr(DateTime time, char* s) - -// Input: time = decimal fraction of a day -// Output: s = time in hr:min:sec format -// Purpose: represents DateTime time value as a formatted string. - -{ - int hr, min, sec; - datetime_decodeTime(time, &hr, &min, &sec); - snprintf(s, TIME_STR_SIZE, "%02d:%02d:%02d", hr, min, sec); -} - -//============================================================================= - -int datetime_strToDate(char* s, DateTime* d) - -// Input: s = date as string -// Output: d = encoded date; -// returns 1 if conversion successful, 0 if not -// Purpose: converts string date s to DateTime value. -// -{ - int yr = 0, mon = 0, day = 0, n; - char month[4]; - char sep1, sep2; - *d = -DateDelta; - if (strchr(s, '-') || strchr(s, '/')) - { - switch (DateFormat) - { - case Y_M_D: - n = sscanf(s, "%d%c%d%c%d", &yr, &sep1, &mon, &sep2, &day); - if ( n < 3 ) - { - mon = 0; - n = sscanf(s, "%d%c%3s%c%d", &yr, &sep1, month, &sep2, &day); - if ( n < 3 ) return 0; - } - break; - - case D_M_Y: - n = sscanf(s, "%d%c%d%c%d", &day, &sep1, &mon, &sep2, &yr); - if ( n < 3 ) - { - mon = 0; - n = sscanf(s, "%d%c%3s%c%d", &day, &sep1, month, &sep2, &yr); - if ( n < 3 ) return 0; - } - break; - - default: // M_D_Y - n = sscanf(s, "%d%c%d%c%d", &mon, &sep1, &day, &sep2, &yr); - if ( n < 3 ) - { - mon = 0; - n = sscanf(s, "%3s%c%d%c%d", month, &sep1, &day, &sep2, &yr); - if ( n < 3 ) return 0; - } - } - if (mon == 0) mon = datetime_findMonth(month); - *d = datetime_encodeDate(yr, mon, day); - } - if (*d == -DateDelta) return 0; - else return 1; -} - -//============================================================================= - -int datetime_strToTime(char* s, DateTime* t) - -// Input: s = time as string -// Output: t = encoded time, -// returns 1 if conversion successful, 0 if not -// Purpose: converts a string time to a DateTime value. -// Note: accepts time as hr:min:sec or as decimal hours. - -{ - int n, hr, min = 0, sec = 0; - char *endptr; - - // Attempt to read time as decimal hours - *t = strtod(s, &endptr); - if ( *endptr == 0 ) - { - *t /= 24.0; - return 1; - } - - // Read time in hr:min:sec format - *t = 0.0; - n = sscanf(s, "%d:%d:%d", &hr, &min, &sec); - if ( n == 0 ) return 0; - *t = datetime_encodeTime(hr, min, sec); - if ( (hr >= 0) && (min >= 0) && (sec >= 0) ) return 1; - else return 0; -} - -//============================================================================= - -void datetime_setDateFormat(int fmt) - -// Input: fmt = date format code -// Output: none -// Purpose: sets date format - -{ - if ( fmt >= Y_M_D && fmt <= M_D_Y) DateFormat = fmt; -} - -//============================================================================= - -DateTime datetime_addSeconds(DateTime date1, double seconds) - -// Input: date1 = an encoded date/time value -// seconds = number of seconds to add to date1 -// Output: returns updated value of date1 -// Purpose: adds a given number of seconds to a date/time. - -{ - double d = floor(date1); - int h, m, s; - datetime_decodeTime(date1, &h, &m, &s); - return d + (3600.0*h + 60.0*m + s + seconds)/SecsPerDay; -} - -//============================================================================= - -DateTime datetime_addDays(DateTime date1, DateTime date2) - -// Input: date1 = an encoded date/time value -// date2 = decimal days to be added to date1 -// Output: returns date1 + date2 -// Purpose: adds a given number of decimal days to a date/time. - -{ - double d1 = floor(date1); - double d2 = floor(date2); - int h1, m1, s1; - int h2, m2, s2; - datetime_decodeTime(date1, &h1, &m1, &s1); - datetime_decodeTime(date2, &h2, &m2, &s2); - return d1 + d2 + datetime_encodeTime(h1+h2, m1+m2, s1+s2); -} - -//============================================================================= - -long datetime_timeDiff(DateTime date1, DateTime date2) - -// Input: date1 = an encoded date/time value -// date2 = an encoded date/time value -// Output: returns date1 - date2 in seconds -// Purpose: finds number of seconds between two dates. - -{ - double d1 = floor(date1); - double d2 = floor(date2); - int h, m, s; - long s1, s2, secs; - datetime_decodeTime(date1, &h, &m, &s); - s1 = 3600*h + 60*m + s; - datetime_decodeTime(date2, &h, &m, &s); - s2 = 3600*h + 60*m + s; - secs = (int)(floor((d1 - d2)*SecsPerDay + 0.5)); - secs += (s1 - s2); - return secs; -} - -//============================================================================= - -int datetime_monthOfYear(DateTime date) - -// Input: date = an encoded date/time value -// Output: returns index of month of year (1..12) -// Purpose: finds month of year (Jan = 1 ...) for a given date. - -{ - int year, month, day; - datetime_decodeDate(date, &year, &month, &day); - return month; -} - -//============================================================================= - -int datetime_dayOfYear(DateTime date) - -// Input: date = an encoded date/time value -// Output: returns day of year (1..365) -// Purpose: finds day of year (Jan 1 = 1) for a given date. - -{ - int year, month, day; - DateTime startOfYear; - datetime_decodeDate(date, &year, &month, &day); - startOfYear = datetime_encodeDate(year, 1, 1); - return (int)(floor(date - startOfYear)) + 1; -} - -//============================================================================= - -int datetime_dayOfWeek(DateTime date) - -// Input: date = an encoded date/time value -// Output: returns index of day of week (1..7) -// Purpose: finds day of week (Sun = 1, ... Sat = 7) for a given date. - -{ - int t = (int)(floor(date)) + DateDelta; - return (t % 7) + 1; -} - -//============================================================================= - -int datetime_hourOfDay(DateTime date) - -// Input: date = an encoded date/time value -// Output: returns hour of day (0..23) -// Purpose: finds hour of day (0 = 12 AM, ..., 23 = 11 PM) for a given date. - -{ - int hour, min, sec; - datetime_decodeTime(date, &hour, &min, &sec); - return hour; -} - -//============================================================================= - -int datetime_daysPerMonth(int year, int month) - -// Input: year = year in which month falls -// month = month of year (1..12) -// Output: returns number of days in the month -// Purpose: finds number of days in a given month of a specified year. - -{ - if ( month < 1 || month > 12 ) return 0; - return DaysPerMonth[isLeapYear(year)][month-1]; -} - -//============================================================================= - -void datetime_getTimeStamp(int fmt, DateTime aDate, int stampSize, char* timeStamp) - -// Input: fmt = desired date format code -// aDate = a date/time value in decimal days -// stampSize = the number of bytes allocated for the time stamp -// Output: returns a time stamp string (e.g., Year-Month-Day Hr:Min:Sec) -// Purpose: Expresses a decimal day date by a time stamp. -{ - char dateStr[DATE_STR_SIZE]; - char timeStr[TIME_STR_SIZE]; - int oldDateFormat = DateFormat; - - if ( stampSize < TIME_STAMP_SIZE ) return; - datetime_setDateFormat(fmt); - datetime_dateToStr(aDate, dateStr); - DateFormat = oldDateFormat; - datetime_timeToStr(aDate, timeStr); - snprintf(timeStamp, stampSize, "%s %s", dateStr, timeStr); -} diff --git a/src/datetime.h b/src/datetime.h deleted file mode 100644 index 98b87b48e..000000000 --- a/src/datetime.h +++ /dev/null @@ -1,72 +0,0 @@ -//----------------------------------------------------------------------------- -// datetime.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// The DateTime type is used to store date and time values. It is -// equivalent to a double floating point type. -// -// The integral part of a DateTime value is the number of days that have -// passed since 12/31/1899. The fractional part of a DateTime value is the -// fraction of a 24 hour day that has elapsed. -// -// Update History -// ============== -// Build 5.1.011: -// - New getTimeStamp function added. -//----------------------------------------------------------------------------- - -#ifndef DATETIME_H -#define DATETIME_H - - -typedef double DateTime; - -#define Y_M_D 0 -#define M_D_Y 1 -#define D_M_Y 2 -#define NO_DATE -693594 // 1/1/0001 -#define DATE_STR_SIZE 12 -#define TIME_STR_SIZE 9 -#define TIME_STAMP_SIZE 21 - -// Functions for encoding a date or time value to a DateTime value -DateTime datetime_encodeDate(int year, int month, int day); -DateTime datetime_encodeTime(int hour, int minute, int second); - -// Functions for decoding a DateTime value to a date and time -void datetime_decodeDate(DateTime date, int* y, int* m, int* d); -void datetime_decodeTime(DateTime time, int* h, int* m, int* s); - -// Function for finding day of week for a date (1 = Sunday) -// month of year, days per month, and hour of day -int datetime_monthOfYear(DateTime date); -int datetime_dayOfYear(DateTime date); -int datetime_dayOfWeek(DateTime date); -int datetime_hourOfDay(DateTime date); -int datetime_daysPerMonth(int year, int month); - -// Functions for converting a DateTime value to a string -void datetime_dateToStr(DateTime date, char* s); -void datetime_timeToStr(DateTime time, char* s); -void datetime_getTimeStamp(int fmt, DateTime aDate, int stampSize, - char* timeStamp); - -// Functions for converting a string date or time to a DateTime value -int datetime_findMonth(char* s); -int datetime_strToDate(char* s, DateTime* d); -int datetime_strToTime(char* s, DateTime* t); - -// Function for setting date format -void datetime_setDateFormat(int fmt); - -// Functions for adding and subtracting dates -DateTime datetime_addSeconds(DateTime date1, double seconds); -DateTime datetime_addDays(DateTime date1, DateTime date2); -long datetime_timeDiff(DateTime date1, DateTime date2); - - -#endif //DATETIME_H diff --git a/src/dwflow.c b/src/dwflow.c deleted file mode 100644 index d6484fe57..000000000 --- a/src/dwflow.c +++ /dev/null @@ -1,684 +0,0 @@ -//----------------------------------------------------------------------------- -// dwflow.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 08/01/22 (Build 5.2.1) -// Author: L. Rossman -// M. Tryby (EPA) -// R. Dickinson (CDM) -// -// Solves the momentum equation for flow in a conduit under dynamic wave -// flow routing. -// -// Update History -// ============== -// Build 5.1.008: -// - Bug in finding if conduit was upstrm/dnstrm full was fixed. -// Build 5.1.012: -// - Modified uniform loss rate term of conduit momentum equation. -// Build 5.1.013: -// - Preissmann slot surcharge option implemented. -// - Changed sign of uniform loss rate term (dq6) in flow updating equation. -// Build 5.1.014: -// - Conduit evap. and seepage loss initialized to 0 in dwflow_findConduitFlow. -// - Most current flow (qLast) used instead of previous time period flow -// (qOld) in call to link_getLossRate. -// Build 5.2.1: -// - Implements the new option to skip checking for normal flow limitations. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "headers.h" - -static const double MAXVELOCITY = 50.; // max. allowable velocity (ft/sec) - -static int getFlowClass(int link, double q, double h1, double h2, - double y1, double y2, double* criticalDepth, double* normalDepth, - double* fasnh); -static void findSurfArea(int link, double q, double length, double* h1, - double* h2, double* y1, double* y2); -static double findLocalLosses(int link, double a1, double a2, double aMid, - double q); - -static double getWidth(TXsect* xsect, double y); -static double getSlotWidth(TXsect* xsect, double y); -static double getArea(TXsect* xsect, double y, double wSlot); -static double getHydRad(TXsect* xsect, double y); - -static double checkNormalFlow(int j, double q, double y1, double y2, - double a1, double r1); - -//============================================================================= - -void dwflow_findConduitFlow(int j, int steps, double omega, double dt) -// -// Input: j = link index -// steps = number of iteration steps taken -// omega = under-relaxation parameter -// dt = time step (sec) -// Output: returns new flow value (cfs) -// Purpose: updates flow in conduit link by solving finite difference -// form of continuity and momentum equations. -// -{ - int k; // index of conduit - int n1, n2; // indexes of end nodes - double z1, z2; // upstream/downstream invert elev. (ft) - double h1, h2; // upstream/dounstream flow heads (ft) - double y1, y2; // upstream/downstream flow depths (ft) - double a1, a2; // upstream/downstream flow areas (ft2) - double r1; // upstream hyd. radius (ft) - double yMid, rMid, aMid; // mid-stream or avg. values of y, r, & a - double aWtd, rWtd; // upstream weighted area & hyd. radius - double qLast; // flow from previous iteration (cfs) - double qOld; // flow from previous time step (cfs) - double aOld; // area from previous time step (ft2) - double v; // velocity (ft/sec) - double rho; // upstream weighting factor - double sigma; // inertial damping factor - double length; // effective conduit length (ft) - double wSlot; // Preissmann slot width (ft) - double dq1, dq2, dq3, dq4, dq5, // terms in momentum eqn. - dq6; // term for evap and infil losses - double denom; // denominator of flow update formula - double q; // new flow value (cfs) - double barrels; // number of barrels in conduit - TXsect* xsect = &Link[j].xsect; // ptr. to conduit's cross section data - char isFull = FALSE; // TRUE if conduit flowing full - char isClosed = FALSE; // TRUE if conduit closed - - - - // --- adjust isClosed status by any control action - if ( Link[j].setting == 0 ) isClosed = TRUE; - - // --- get flow from last time step & previous iteration - k = Link[j].subIndex; - barrels = Conduit[k].barrels; - qOld = Link[j].oldFlow / barrels; - qLast = Conduit[k].q1; - Conduit[k].evapLossRate = 0.0; - Conduit[k].seepLossRate = 0.0; - - // --- get most current heads at upstream and downstream ends of conduit - n1 = Link[j].node1; - n2 = Link[j].node2; - z1 = Node[n1].invertElev + Link[j].offset1; - z2 = Node[n2].invertElev + Link[j].offset2; - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - h1 = MAX(h1, z1); - h2 = MAX(h2, z2); - - // --- get unadjusted upstream and downstream flow depths in conduit - // (flow depth = head in conduit - elev. of conduit invert) - y1 = h1 - z1; - y2 = h2 - z2; - y1 = MAX(y1, FUDGE); - y2 = MAX(y2, FUDGE); - - // --- flow depths can't exceed full depth of conduit if slot not used - if ( SurchargeMethod != SLOT ) - { - y1 = MIN(y1, xsect->yFull); - y2 = MIN(y2, xsect->yFull); - } - - // -- get area from solution at previous time step - aOld = Conduit[k].a2; - aOld = MAX(aOld, FUDGE); - - // --- use Courant-modified length instead of conduit's actual length - length = Conduit[k].modLength; - - // --- find surface area contributions to upstream and downstream nodes - // based on previous iteration's flow estimate - findSurfArea(j, qLast, length, &h1, &h2, &y1, &y2); - - // --- compute area at each end of conduit & hyd. radius at upstream end - wSlot = getSlotWidth(xsect, y1); - a1 = getArea(xsect, y1, wSlot); - r1 = getHydRad(xsect, y1); - wSlot = getSlotWidth(xsect, y2); - a2 = getArea(xsect, y2, wSlot); - - // --- compute area & hyd. radius at midpoint - yMid = 0.5 * (y1 + y2); - wSlot = getSlotWidth(xsect, yMid); - aMid = getArea(xsect, yMid, wSlot); - rMid = getHydRad(xsect, yMid); - - // --- alternate approach not currently used, but might produce better - // Bernoulli energy balance for steady flows - //aMid = (a1+a2)/2.0; - //rMid = (r1+getHydRad(xsect,y2))/2.0; - - // --- check if conduit is flowing full - if ( y1 >= xsect->yFull && - y2 >= xsect->yFull) isFull = TRUE; - - // --- set new flow to zero if conduit is dry or if flap gate is closed - if ( Link[j].flowClass == DRY || - Link[j].flowClass == UP_DRY || - Link[j].flowClass == DN_DRY || - isClosed || - aMid <= FUDGE ) - { - Conduit[k].a1 = 0.5 * (a1 + a2); - Conduit[k].q1 = 0.0;; - Conduit[k].q2 = 0.0; - Link[j].dqdh = GRAVITY * dt * aMid / length * barrels; - Link[j].froude = 0.0; - Link[j].newDepth = MIN(yMid, Link[j].xsect.yFull); - Link[j].newVolume = Conduit[k].a1 * link_getLength(j) * barrels; - Link[j].newFlow = 0.0; - return; - } - - // --- compute velocity from last flow estimate - v = qLast / aMid; - if ( fabs(v) > MAXVELOCITY ) v = MAXVELOCITY * SGN(qLast); - - // --- compute Froude No. - Link[j].froude = link_getFroude(j, v, yMid); - if ( Link[j].flowClass == SUBCRITICAL && - Link[j].froude > 1.0 ) Link[j].flowClass = SUPCRITICAL; - - // --- find inertial damping factor (sigma) - if ( Link[j].froude <= 0.5 ) sigma = 1.0; - else if ( Link[j].froude >= 1.0 ) sigma = 0.0; - else sigma = 2.0 * (1.0 - Link[j].froude); - - // --- get upstream-weighted area & hyd. radius based on damping factor - // (modified version of R. Dickinson's slope weighting) - rho = 1.0; - if ( !isFull && qLast > 0.0 && h1 >= h2 ) rho = sigma; - aWtd = a1 + (aMid - a1) * rho; - rWtd = r1 + (rMid - r1) * rho; - - // --- determine how much inertial damping to apply - if ( InertDamping == NO_DAMPING ) sigma = 1.0; - else if ( InertDamping == FULL_DAMPING ) sigma = 0.0; - - // --- use full inertial damping if closed conduit is surcharged - if ( isFull && !xsect_isOpen(xsect->type) ) sigma = 0.0; - - // --- compute terms of momentum eqn.: - // --- 1. friction slope term - if ( xsect->type == FORCE_MAIN && isFull ) - dq1 = dt * forcemain_getFricSlope(j, fabs(v), rMid); - else dq1 = dt * Conduit[k].roughFactor / pow(rWtd, 1.33333) * fabs(v); - - // --- 2. energy slope term - dq2 = dt * GRAVITY * aWtd * (h2 - h1) / length; - - // --- 3 & 4. inertial terms - dq3 = 0.0; - dq4 = 0.0; - if ( sigma > 0.0 ) - { - dq3 = 2.0 * v * (aMid - aOld) * sigma; - dq4 = dt * v * v * (a2 - a1) / length * sigma; - } - - // --- 5. local losses term - dq5 = 0.0; - if ( Conduit[k].hasLosses ) - { - dq5 = findLocalLosses(j, a1, a2, aMid, qLast) / 2.0 / length * dt; - } - - // --- 6. term for evap and seepage losses per unit length - dq6 = link_getLossRate(j, qLast) * 2.5 * dt * v / link_getLength(j); - - // --- combine terms to find new conduit flow - denom = 1.0 + dq1 + dq5; - q = (qOld - dq2 + dq3 + dq4 + dq6) / denom; - - // --- compute derivative of flow w.r.t. head - Link[j].dqdh = 1.0 / denom * GRAVITY * dt * aWtd / length * barrels; - - // --- check if any flow limitation applies - Link[j].inletControl = FALSE; - Link[j].normalFlow = FALSE; - if ( q > 0.0 ) - { - // --- check for inlet controlled culvert flow - if ( xsect->culvertCode > 0 && !isFull ) - q = culvert_getInflow(j, q, h1); - - // --- check for normal flow limitation based on surface slope & Fr - else if (NormalFlowLtd != NEITHER && y1 < Link[j].xsect.yFull && - ( Link[j].flowClass == SUBCRITICAL || - Link[j].flowClass == SUPCRITICAL )) - q = checkNormalFlow(j, q, y1, y2, a1, r1); - } - - // --- apply under-relaxation weighting between new & old flows; - // --- do not allow change in flow direction without first being zero - if ( steps > 0 ) - { - q = (1.0 - omega) * qLast + omega * q; - if ( q * qLast < 0.0 ) q = 0.001 * SGN(q); - } - - // --- check if user-supplied flow limit applies - if ( Link[j].qLimit > 0.0 ) - { - if ( fabs(q) > Link[j].qLimit ) q = SGN(q) * Link[j].qLimit; - } - - // --- check for reverse flow with closed flap gate - if ( link_setFlapGate(j, n1, n2, q) ) q = 0.0; - - // --- do not allow flow out of a dry node - // (as suggested by R. Dickinson) - if( q > FUDGE && Node[n1].newDepth <= FUDGE ) q = FUDGE; - if( q < -FUDGE && Node[n2].newDepth <= FUDGE ) q = -FUDGE; - - // --- save new values of area, flow, depth, & volume - Conduit[k].a1 = aMid; - Conduit[k].q1 = q; - Conduit[k].q2 = q; - Link[j].newDepth = MIN(yMid, xsect->yFull); - aMid = (a1 + a2) / 2.0; -// aMid = MIN(aMid, xsect->aFull); //Slot can have aMid > aFull - Conduit[k].fullState = link_getFullState(a1, a2, xsect->aFull); - Link[j].newVolume = aMid * link_getLength(j) * barrels; - Link[j].newFlow = q * barrels; -} - -//============================================================================= - -int getFlowClass(int j, double q, double h1, double h2, double y1, double y2, - double *yC, double *yN, double* fasnh) -// -// Input: j = conduit link index -// q = current conduit flow (cfs) -// h1 = head at upstream end of conduit (ft) -// h2 = head at downstream end of conduit (ft) -// y1 = upstream flow depth in conduit (ft) -// y2 = downstream flow depth in conduit (ft) -// yC = critical flow depth (ft) -// yN = normal flow depth (ft) -// fasnh = fraction between norm. & crit. depth -// Output: returns flow classification code -// Purpose: determines flow class for a conduit based on depths at each end. -// -{ - int n1, n2; // indexes of upstrm/downstrm nodes - int flowClass; // flow classification code - double ycMin, ycMax; // min/max critical depths (ft) - double z1, z2; // offsets of conduit inverts (ft) - - // --- get upstream & downstream node indexes - n1 = Link[j].node1; - n2 = Link[j].node2; - - // --- get upstream & downstream conduit invert offsets - z1 = Link[j].offset1; - z2 = Link[j].offset2; - - // --- base offset of an outfall conduit on outfall's depth - if ( Node[n1].type == OUTFALL ) z1 = MAX(0.0, (z1 - Node[n1].newDepth)); - if ( Node[n2].type == OUTFALL ) z2 = MAX(0.0, (z2 - Node[n2].newDepth)); - - // --- default class is SUBCRITICAL - flowClass = SUBCRITICAL; - *fasnh = 1.0; - - // --- case where both ends of conduit are wet - if ( y1 > FUDGE && y2 > FUDGE ) - { - if ( q < 0.0 ) - { - // --- upstream end at critical depth if flow depth is - // below conduit's critical depth and an upstream - // conduit offset exists - if ( z1 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - ycMin = MIN(*yN, *yC); - if ( y1 < ycMin ) flowClass = UP_CRITICAL; - } - } - - // --- case of normal direction flow - else - { - // --- downstream end at smaller of critical and normal depth - // if downstream flow depth below this and a downstream - // conduit offset exists - if ( z2 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - ycMin = MIN(*yN, *yC); - ycMax = MAX(*yN, *yC); - if ( y2 < ycMin ) flowClass = DN_CRITICAL; - else if ( y2 < ycMax ) - { - if ( ycMax - ycMin < FUDGE ) *fasnh = 0.0; - else *fasnh = (ycMax - y2) / (ycMax - ycMin); - } - } - } - } - - // --- case where no flow at either end of conduit - else if ( y1 <= FUDGE && y2 <= FUDGE ) flowClass = DRY; - - // --- case where downstream end of pipe is wet, upstream dry - else if ( y2 > FUDGE ) - { - // --- flow classification is UP_DRY if downstream head < - // invert of upstream end of conduit - if ( h2 < Node[n1].invertElev + Link[j].offset1 ) flowClass = UP_DRY; - - // --- otherwise, the downstream head will be >= upstream - // conduit invert creating a flow reversal and upstream end - // should be at critical depth, providing that an upstream - // offset exists (otherwise subcritical condition is maintained) - else if ( z1 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - flowClass = UP_CRITICAL; - } - } - - // --- case where upstream end of pipe is wet, downstream dry - else - { - // --- flow classification is DN_DRY if upstream head < - // invert of downstream end of conduit - if ( h1 < Node[n2].invertElev + Link[j].offset2 ) flowClass = DN_DRY; - - // --- otherwise flow at downstream end should be at critical depth - // providing that a downstream offset exists (otherwise - // subcritical condition is maintained) - else if ( z2 > 0.0 ) - { - *yN = link_getYnorm(j, fabs(q)); - *yC = link_getYcrit(j, fabs(q)); - flowClass = DN_CRITICAL; - } - } - return flowClass; -} - -//============================================================================= - -void findSurfArea(int j, double q, double length, double* h1, double* h2, - double* y1, double* y2) -// -// Input: j = conduit link index -// q = current conduit flow (cfs) -// length = conduit length (ft) -// h1 = head at upstream end of conduit (ft) -// h2 = head at downstream end of conduit (ft) -// y1 = upstream flow depth (ft) -// y2 = downstream flow depth (ft) -// Output: updated values of h1, h2, y1, & y2; -// Purpose: assigns surface area of conduit to its up and downstream nodes. -// -{ - int n1, n2; // indexes of upstrm/downstrm nodes - double flowDepth1; // flow depth at upstrm end (ft) - double flowDepth2; // flow depth at downstrm end (ft) - double flowDepthMid; // flow depth at midpt. (ft) - double width1; // top width at upstrm end (ft) - double width2; // top width at downstrm end (ft) - double widthMid; // top width at midpt. (ft) - double surfArea1 = 0.0; // surface area at upstream node (ft2) - double surfArea2 = 0.0; // surface area st downstrm node (ft2) - double criticalDepth; // critical flow depth (ft) - double normalDepth; // normal flow depth (ft) - double fullDepth; // full depth (ft) - double fasnh = 1.0; // fraction between norm. & crit. depth - TXsect* xsect = &Link[j].xsect; // pointer to cross-section data - - // --- get node indexes & current flow depths - n1 = Link[j].node1; - n2 = Link[j].node2; - flowDepth1 = *y1; - flowDepth2 = *y2; - - normalDepth = (flowDepth1 + flowDepth2) / 2.0; - criticalDepth = normalDepth; - - // --- find conduit's flow classification - fullDepth = xsect->yFull; - if (flowDepth1 >= fullDepth && flowDepth2 >= fullDepth) - { - Link[j].flowClass = SUBCRITICAL; - } - else Link[j].flowClass = getFlowClass(j, q, *h1, *h2, *y1, *y2, - &criticalDepth, &normalDepth, &fasnh); - - // --- add conduit's surface area to its end nodes depending on flow class - switch ( Link[j].flowClass ) - { - case SUBCRITICAL: - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width1 = getWidth(xsect, flowDepth1); - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - surfArea1 = (width1 + widthMid) * length / 4.; - surfArea2 = (widthMid + width2) * length / 4. * fasnh; - break; - - case UP_CRITICAL: - flowDepth1 = criticalDepth; - if ( normalDepth < criticalDepth ) flowDepth1 = normalDepth; - flowDepth1 = MAX(flowDepth1, FUDGE); - *h1 = Node[n1].invertElev + Link[j].offset1 + flowDepth1; - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - surfArea2 = (widthMid + width2) * length * 0.5; - break; - - case DN_CRITICAL: - flowDepth2 = criticalDepth; - if ( normalDepth < criticalDepth ) flowDepth2 = normalDepth; - flowDepth2 = MAX(flowDepth2, FUDGE); - *h2 = Node[n2].invertElev + Link[j].offset2 + flowDepth2; - width1 = getWidth(xsect, flowDepth1); - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - widthMid = getWidth(xsect, flowDepthMid); - surfArea1 = (width1 + widthMid) * length * 0.5; - break; - - case UP_DRY: - flowDepth1 = FUDGE; - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width1 = getWidth(xsect, flowDepth1); - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - - // --- assign avg. surface area of downstream half of conduit - // to the downstream node - surfArea2 = (widthMid + width2) * length / 4.; - - // --- if there is no free-fall at upstream end, assign the - // upstream node the avg. surface area of the upstream half - if ( Link[j].offset1 <= 0.0 ) - { - surfArea1 = (width1 + widthMid) * length / 4.; - } - break; - - case DN_DRY: - flowDepth2 = FUDGE; - flowDepthMid = 0.5 * (flowDepth1 + flowDepth2); - if ( flowDepthMid < FUDGE ) flowDepthMid = FUDGE; - width1 = getWidth(xsect, flowDepth1); - width2 = getWidth(xsect, flowDepth2); - widthMid = getWidth(xsect, flowDepthMid); - - // --- assign avg. surface area of upstream half of conduit - // to the upstream node - surfArea1 = (widthMid + width1) * length / 4.; - - // --- if there is no free-fall at downstream end, assign the - // downstream node the avg. surface area of the downstream half - if ( Link[j].offset2 <= 0.0 ) - { - surfArea2 = (width2 + widthMid) * length / 4.; - } - break; - - case DRY: - surfArea1 = FUDGE * length / 2.0; - surfArea2 = surfArea1; - break; - } - Link[j].surfArea1 = surfArea1; - Link[j].surfArea2 = surfArea2; - *y1 = flowDepth1; - *y2 = flowDepth2; -} - -//============================================================================= - -double findLocalLosses(int j, double a1, double a2, double aMid, double q) -// -// Input: j = link index -// a1 = upstream area (ft2) -// a2 = downstream area (ft2) -// aMid = midpoint area (ft2) -// q = flow rate (cfs) -// Output: returns local losses (ft/sec) -// Purpose: computes local losses term of momentum equation. -// -{ - double losses = 0.0; - q = fabs(q); - if ( a1 > FUDGE ) losses += Link[j].cLossInlet * (q/a1); - if ( a2 > FUDGE ) losses += Link[j].cLossOutlet * (q/a2); - if ( aMid > FUDGE ) losses += Link[j].cLossAvg * (q/aMid); - return losses; -} - -//============================================================================= - -double getSlotWidth(TXsect* xsect, double y) -{ - double yNorm = y / xsect->yFull; - - // --- return 0.0 if slot surcharge method not used - if (SurchargeMethod != SLOT || xsect_isOpen(xsect->type) || - yNorm < CrownCutoff) return 0.0; - - // --- for depth > 1.78 * pipe depth, slot width = 1% of max. width - if (yNorm > 1.78) return 0.01 * xsect->wMax; - - // --- otherwise use the Sjoberg formula - return xsect->wMax * 0.5423 * exp(-pow(yNorm, 2.4)); -} - -//============================================================================= - -double getWidth(TXsect* xsect, double y) -// -// Input: xsect = ptr. to conduit cross section -// y = flow depth (ft) -// Output: returns top width (ft) -// Purpose: computes top width of flow surface in conduit. -// -{ - double wSlot = getSlotWidth(xsect, y); - if (wSlot > 0.0) return wSlot; - if (y / xsect->yFull >= CrownCutoff && !xsect_isOpen(xsect->type)) - y = CrownCutoff * xsect->yFull; - return xsect_getWofY(xsect, y); -} - -//============================================================================= - -double getArea(TXsect* xsect, double y, double wSlot) -// -// Input: xsect = ptr. to conduit cross section -// y = flow depth (ft) -// Output: returns flow area (ft2) -// Purpose: computes area of flow cross-section in a conduit. -// -{ - if ( y >= xsect->yFull ) return xsect->aFull + (y - xsect->yFull) * wSlot; - return xsect_getAofY(xsect, y); -} - -//============================================================================= - -double getHydRad(TXsect* xsect, double y) -// -// Input: xsect = ptr. to conduit cross section -// y = flow depth (ft) -// Output: returns hydraulic radius (ft) -// Purpose: computes hydraulic radius of flow cross-section in a conduit. -// -{ - if (y >= xsect->yFull) return xsect->rFull; - return xsect_getRofY(xsect, y); -} - -//============================================================================= - -double checkNormalFlow(int j, double q, double y1, double y2, double a1, - double r1) -// -// Input: j = link index -// q = link flow found from dynamic wave equations (cfs) -// y1 = flow depth at upstream end (ft) -// y2 = flow depth at downstream end (ft) -// a1 = flow area at upstream end (ft2) -// r1 = hyd. radius at upstream end (ft) -// Output: returns modifed flow in link (cfs) -// Purpose: checks if flow in link should be replaced by normal flow. -// -{ - int check = FALSE; - int k = Link[j].subIndex; - int n1 = Link[j].node1; - int n2 = Link[j].node2; - int hasOutfall = (Node[n1].type == OUTFALL || Node[n2].type == OUTFALL); - double qNorm; - double f1; - - // --- check if water surface slope < conduit slope - if ( NormalFlowLtd == SLOPE || NormalFlowLtd == BOTH || hasOutfall ) - { - if ( y1 < y2) check = TRUE; - } - - // --- check if Fr >= 1.0 at upstream end of conduit - if ( !check && (NormalFlowLtd == FROUDE || NormalFlowLtd == BOTH) && - !hasOutfall ) - { - if ( y1 > FUDGE && y2 > FUDGE ) - { - f1 = link_getFroude(j, q/a1, y1); - if ( f1 >= 1.0 ) check = TRUE; - } - } - - // --- check if normal flow < dynamic flow - if ( check ) - { - qNorm = Conduit[k].beta * a1 * pow(r1, 2./3.); - if ( qNorm < q ) - { - Link[j].normalFlow = TRUE; - return qNorm; - } - } - return q; -} diff --git a/src/dynwave.c b/src/dynwave.c deleted file mode 100644 index ac302e849..000000000 --- a/src/dynwave.c +++ /dev/null @@ -1,908 +0,0 @@ -//----------------------------------------------------------------------------- -// dynwave.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// M. Tryby (EPA) -// R. Dickinson (CDM) -// -// Dynamic wave flow routing functions. -// -// This module solves the dynamic wave flow routing equations using -// Picard Iterations (i.e., a method of successive approximations) -// to solve the explicit form of the continuity and momentum equations -// for conduits. -// -// Update History -// ============== -// Build 5.1.002: -// - Only non-ponded nodal surface area is saved for use in -// surcharge algorithm. -// Build 5.1.007: -// - Node losses added to node outflow variable instead of treated -// as a separate item when computing change in node flow volume. -// Build 5.1.008: -// - Module-specific constants moved here from project.c. -// - Support added for user-specified minimum variable time step. -// - Node crown elevations found here instead of in flowrout.c module. -// - OpenMP use to parallelize findLinkFlows() & findNodeDepths(). -// - Bug in finding complete list of capacity limited links fixed. -// Build 5.1.011: -// - Added test for failed memory allocation. -// - Fixed illegal array index bug for Ideal Pumps. -// Build 5.1.013: -// - Include omp.h protected against lack of compiler support for OpenMP. -// - SurchargeMethod option used to decide how node surcharging is handled. -// - Storage nodes allowed to pressurize if their surcharge depth > 0. -// - Minimum flow needed to compute a Courant time step modified. -// Build 5.1.014: -// - updateNodeFlows() modified to subtract conduit evap. and seepage losses -// from downstream node inflow instead of upstream node outflow. -// Build 5.1.015: -// - Roll back the 5.1.014 change for conduit losses in updateNodeFlows(). -// Build 5.2.0: -// - Support added for reporting most frequent non-converging links. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double MINTIMESTEP = 0.001; // min. time step (sec) -static const double OMEGA = 0.5; // under-relaxation parameter -static const double DEFAULT_SURFAREA = 12.566; // Min. nodal surface area (~4 ft diam.) -static const double DEFAULT_HEADTOL = 0.005; // Default head tolerance (ft) -static const double EXTRAN_CROWN_CUTOFF = 0.96; // crown cutoff for EXTRAN -static const double SLOT_CROWN_CUTOFF = 0.985257; // crown cutoff for SLOT -static const int DEFAULT_MAXTRIALS = 8; // Max. trials per time step - - -//----------------------------------------------------------------------------- -// Data Structures -//----------------------------------------------------------------------------- -typedef struct -{ - char converged; // TRUE if iterations for a node done - double newSurfArea; // current surface area (ft2) - double oldSurfArea; // previous surface area (ft2) - double sumdqdh; // sum of dqdh from adjoining links - double dYdT; // change in depth w.r.t. time (ft/sec) -} TXnode; - -//----------------------------------------------------------------------------- -// Shared Variables -//----------------------------------------------------------------------------- -static double VariableStep; // size of variable time step (sec) -static TXnode* Xnode; // extended nodal information - -static double Omega; // actual under-relaxation parameter -static int Steps; // number of Picard iterations - -//----------------------------------------------------------------------------- -// Function declarations -//----------------------------------------------------------------------------- -static void initRoutingStep(void); -static void initNodeStates(void); -static void findBypassedLinks(); -static void findLimitedLinks(); - -static void findLinkFlows(double dt); -static int isTrueConduit(int link); -static void findNonConduitFlow(int link, double dt); -static void findNonConduitSurfArea(int link); -static double getModPumpFlow(int link, double q, double dt); -static void updateNodeFlows(int link); -static void updateConvergenceStats(); - -static int findNodeDepths(double dt); -static void setNodeDepth(int node, double dt); -static double getFloodedDepth(int node, int canPond, double dV, double yNew, - double yMax, double dt); - -static double getVariableStep(double maxStep); -static double getLinkStep(double tMin, int *minLink); -static double getNodeStep(double tMin, int *minNode); - -//============================================================================= - -void dynwave_init() -// -// Input: none -// Output: none -// Purpose: initializes dynamic wave routing method. -// -{ - int i, j; - double z; - - VariableStep = 0.0; - Xnode = (TXnode *) calloc(Nobjects[NODE], sizeof(TXnode)); - if ( Xnode == NULL ) - { - report_writeErrorMsg(ERR_MEMORY, - " Not enough memory for dynamic wave routing."); - return; - } - - // --- initialize node surface areas & crown elev. - for (i = 0; i < Nobjects[NODE]; i++ ) - { - Xnode[i].newSurfArea = 0.0; - Xnode[i].oldSurfArea = 0.0; - Node[i].crownElev = Node[i].invertElev; - } - - // --- initialize links & update node crown elevations - for (i = 0; i < Nobjects[LINK]; i++) - { - j = Link[i].node1; - z = Node[j].invertElev + Link[i].offset1 + Link[i].xsect.yFull; - Node[j].crownElev = MAX(Node[j].crownElev, z); - j = Link[i].node2; - z = Node[j].invertElev + Link[i].offset2 + Link[i].xsect.yFull; - Node[j].crownElev = MAX(Node[j].crownElev, z); - Link[i].flowClass = DRY; - Link[i].dqdh = 0.0; - } - - // --- set crown cutoff for finding top width of closed conduits - if ( SurchargeMethod == SLOT ) CrownCutoff = SLOT_CROWN_CUTOFF; - else CrownCutoff = EXTRAN_CROWN_CUTOFF; -} - -//============================================================================= - -void dynwave_close() -// -// Input: none -// Output: none -// Purpose: frees memory allocated for dynamic wave routing method. -// -{ - FREE(Xnode); -} - -//============================================================================= - -void dynwave_validate() -// -// Input: none -// Output: none -// Purpose: adjusts dynamic wave routing options. -// -{ - if ( MinRouteStep > RouteStep ) MinRouteStep = RouteStep; - if ( MinRouteStep < MINTIMESTEP ) MinRouteStep = MINTIMESTEP; - if ( MinSurfArea == 0.0 ) MinSurfArea = DEFAULT_SURFAREA; - else MinSurfArea /= UCF(LENGTH) * UCF(LENGTH); - if ( HeadTol == 0.0 ) HeadTol = DEFAULT_HEADTOL; - else HeadTol /= UCF(LENGTH); - if ( MaxTrials == 0 ) MaxTrials = DEFAULT_MAXTRIALS; -} - -//============================================================================= - -double dynwave_getRoutingStep(double fixedStep) -// -// Input: fixedStep = user-supplied fixed time step (sec) -// Output: returns routing time step (sec) -// Purpose: computes variable routing time step if applicable. -// -{ - // --- use user-supplied fixed step if variable step option turned off - // or if its smaller than the min. allowable variable time step - if ( CourantFactor == 0.0 ) return fixedStep; - if ( fixedStep < MINTIMESTEP ) return fixedStep; - - // --- at start of simulation (when current variable step is zero) - // use the minimum allowable time step - if ( VariableStep == 0.0 ) - { - VariableStep = MinRouteStep; - } - - // --- otherwise compute variable step based on current flow solution - else VariableStep = getVariableStep(fixedStep); - - // --- adjust step to be a multiple of a millisecond - VariableStep = floor(1000.0 * VariableStep) / 1000.0; - return VariableStep; -} - -//============================================================================= - -int dynwave_execute(double tStep) -// -// Input: links = array of topo sorted links indexes -// tStep = time step (sec) -// Output: returns number of iterations used -// Purpose: routes flows through drainage network over current time step. -// -{ - int converged; - - // --- initialize - if ( ErrorCode ) return 0; - Steps = 0; - converged = FALSE; - Omega = OMEGA; - initRoutingStep(); - - // --- keep iterating until convergence - while ( Steps < MaxTrials ) - { - // --- execute a routing step & check for nodal convergence - initNodeStates(); - findLinkFlows(tStep); - converged = findNodeDepths(tStep); - Steps++; - if ( Steps > 1 ) - { - if ( converged ) break; - - // --- check if link calculations can be skipped in next step - findBypassedLinks(); - } - } - if ( !converged ) updateConvergenceStats(); - - // --- identify any capacity-limited conduits - findLimitedLinks(); - return Steps; -} - -//============================================================================= - -void updateConvergenceStats() -{ - int i; - NonConvergeCount++; - for (i = 0; i < Nobjects[NODE]; i++) - stats_updateConvergenceStats(i, Xnode[i].converged); -} - -//============================================================================= - -void initRoutingStep() -{ - int i; - for (i = 0; i < Nobjects[NODE]; i++) - { - Xnode[i].converged = FALSE; - Xnode[i].dYdT = 0.0; - } - for (i = 0; i < Nobjects[LINK]; i++) - { - Link[i].bypassed = FALSE; - Link[i].surfArea1 = 0.0; - Link[i].surfArea2 = 0.0; - } - - // --- a2 preserves conduit area from solution at last time step - for ( i = 0; i < Nlinks[CONDUIT]; i++) Conduit[i].a2 = Conduit[i].a1; -} - -//============================================================================= - -void initNodeStates() -// -// Input: none -// Output: none -// Purpose: initializes node's surface area, inflow & outflow -// -{ - int i; - - for (i = 0; i < Nobjects[NODE]; i++) - { - // --- initialize nodal surface area - if ( AllowPonding ) - { - Xnode[i].newSurfArea = node_getPondedArea(i, Node[i].newDepth); - } - else - { - Xnode[i].newSurfArea = node_getSurfArea(i, Node[i].newDepth); - } - - // --- initialize nodal inflow & outflow - Node[i].inflow = 0.0; - Node[i].outflow = Node[i].losses; - if ( Node[i].newLatFlow >= 0.0 ) - { - Node[i].inflow += Node[i].newLatFlow; - } - else - { - Node[i].outflow -= Node[i].newLatFlow; - } - Xnode[i].sumdqdh = 0.0; - } -} - -//============================================================================= - -void findBypassedLinks() -{ - int i; - for (i = 0; i < Nobjects[LINK]; i++) - { - if ( Xnode[Link[i].node1].converged && - Xnode[Link[i].node2].converged ) - Link[i].bypassed = TRUE; - else Link[i].bypassed = FALSE; - } -} - -//============================================================================= - -void findLimitedLinks() -// -// Input: none -// Output: none -// Purpose: determines if a conduit link is capacity limited. -// -{ - int j, n1, n2, k; - double h1, h2; - - for (j = 0; j < Nobjects[LINK]; j++) - { - // ---- check only non-dummy conduit links - if ( !isTrueConduit(j) ) continue; - - // --- check that upstream end is full - k = Link[j].subIndex; - Conduit[k].capacityLimited = FALSE; - if ( Conduit[k].a1 >= Link[j].xsect.aFull ) - { - // --- check if HGL slope > conduit slope - n1 = Link[j].node1; - n2 = Link[j].node2; - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - if ( (h1 - h2) > fabs(Conduit[k].slope) * Conduit[k].length ) - Conduit[k].capacityLimited = TRUE; - } - } -} - -//============================================================================= - -void findLinkFlows(double dt) -{ - int i; - - // --- find new flow in each non-dummy conduit -#pragma omp parallel num_threads(NumThreads) -{ - #pragma omp for - for ( i = 0; i < Nobjects[LINK]; i++) - { - if ( isTrueConduit(i) && !Link[i].bypassed ) - dwflow_findConduitFlow(i, Steps, Omega, dt); - } -} - - // --- update inflow/outflows for nodes attached to non-dummy conduits - for ( i = 0; i < Nobjects[LINK]; i++) - { - if ( isTrueConduit(i) ) updateNodeFlows(i); - } - - // --- find new flows for all dummy conduits, pumps & regulators - for ( i = 0; i < Nobjects[LINK]; i++) - { - if ( !isTrueConduit(i) ) - { - if ( !Link[i].bypassed ) findNonConduitFlow(i, dt); - updateNodeFlows(i); - } - } -} - -//============================================================================= - -int isTrueConduit(int j) -{ - return ( Link[j].type == CONDUIT && Link[j].xsect.type != DUMMY ); -} - -//============================================================================= - -void findNonConduitFlow(int i, double dt) -// -// Input: i = link index -// dt = time step (sec) -// Output: none -// Purpose: finds new flow in a non-conduit-type link -// -{ - double qLast; // previous link flow (cfs) - double qNew; // new link flow (cfs) - - // --- get link flow from last iteration - qLast = Link[i].newFlow; - Link[i].dqdh = 0.0; - - // --- get new inflow to link from its upstream node - // (link_getInflow returns 0 if flap gate closed or pump is offline) - qNew = link_getInflow(i); - if ( Link[i].type == PUMP ) qNew = getModPumpFlow(i, qNew, dt); - - // --- find surface area at each end of link - findNonConduitSurfArea(i); - - // --- apply under-relaxation with flow from previous iteration; - // --- do not allow flow to change direction without first being 0 - if ( Steps > 0 && Link[i].type != PUMP ) - { - qNew = (1.0 - Omega) * qLast + Omega * qNew; - if ( qNew * qLast < 0.0 ) qNew = 0.001 * SGN(qNew); - } - Link[i].newFlow = qNew; -} - -//============================================================================= - -double getModPumpFlow(int i, double q, double dt) -// -// Input: i = link index -// q = pump flow from pump curve (cfs) -// dt = time step (sec) -// Output: returns modified pump flow rate (cfs) -// Purpose: modifies pump curve pumping rate depending on amount of water -// available at pump's inlet node. -// -{ - int j = Link[i].node1; // pump's inlet node index - int k = Link[i].subIndex; // pump's index - double newNetInflow; // inflow - outflow rate (cfs) - double netFlowVolume; // inflow - outflow volume (ft3) - double y; // node depth (ft) - - if ( q == 0.0 ) return q; - - // --- case where inlet node is a storage node: - // prevent node volume from going negative - if ( Node[j].type == STORAGE ) return node_getMaxOutflow(j, q, dt); - - // --- case where inlet is a non-storage node - switch ( Pump[k].type ) - { - // --- for Type1 pump, a volume is computed for inlet node, - // so make sure it doesn't go negative - case TYPE1_PUMP: - return node_getMaxOutflow(j, q, dt); - - // --- for other types of pumps, if pumping rate would make depth - // at upstream node negative, then set pumping rate = inflow - case TYPE2_PUMP: - case TYPE4_PUMP: - case TYPE3_PUMP: - newNetInflow = Node[j].inflow - Node[j].outflow - q; - netFlowVolume = 0.5 * (Node[j].oldNetInflow + newNetInflow ) * dt; - y = Node[j].oldDepth + netFlowVolume / Xnode[j].newSurfArea; - if ( y <= 0.0 ) return Node[j].inflow; - } - return q; -} - -//============================================================================= - -void findNonConduitSurfArea(int i) -// -// Input: i = link index -// Output: none -// Purpose: finds the surface area contributed by a non-conduit -// link to its upstream and downstream nodes. -// -{ - if ( Link[i].type == ORIFICE ) - { - Link[i].surfArea1 = Orifice[Link[i].subIndex].surfArea / 2.; - } - - // --- no surface area for weirs to maintain SWMM 4 compatibility - else Link[i].surfArea1 = 0.0; - - Link[i].surfArea2 = Link[i].surfArea1; - if ( Link[i].flowClass == UP_CRITICAL || - Node[Link[i].node1].type == STORAGE ) Link[i].surfArea1 = 0.0; - if ( Link[i].flowClass == DN_CRITICAL || - Node[Link[i].node2].type == STORAGE ) Link[i].surfArea2 = 0.0; -} - -//============================================================================= - -void updateNodeFlows(int i) -// -// Input: i = link index -// q = link flow rate (cfs) -// Output: none -// Purpose: updates cumulative inflow & outflow at link's end nodes. -// -{ - int k; - int barrels = 1; - int n1 = Link[i].node1; - int n2 = Link[i].node2; - double q = Link[i].newFlow; - double uniformLossRate = 0.0; - - // --- compute any uniform seepage loss from a conduit - if ( Link[i].type == CONDUIT ) - { - k = Link[i].subIndex; - uniformLossRate = Conduit[k].evapLossRate + Conduit[k].seepLossRate; - barrels = Conduit[k].barrels; - uniformLossRate *= barrels; - } - - // --- update total inflow & outflow at upstream/downstream nodes - if ( q >= 0.0 ) - { - Node[n1].outflow += q + uniformLossRate; - Node[n2].inflow += q; - } - else - { - Node[n1].inflow -= q; - Node[n2].outflow -= q - uniformLossRate; - } - - // --- add surf. area contributions to upstream/downstream nodes - Xnode[Link[i].node1].newSurfArea += Link[i].surfArea1 * barrels; - Xnode[Link[i].node2].newSurfArea += Link[i].surfArea2 * barrels; - - // --- update summed value of dqdh at each end node - Xnode[Link[i].node1].sumdqdh += Link[i].dqdh; - if ( Link[i].type == PUMP ) - { - k = Link[i].subIndex; - if ( Pump[k].type != TYPE4_PUMP ) - { - Xnode[n2].sumdqdh += Link[i].dqdh; - } - } - else Xnode[n2].sumdqdh += Link[i].dqdh; -} - -//============================================================================= - -int findNodeDepths(double dt) -// -// Input: dt = time step (sec) -// Output: returns TRUE if depth change at all non-Outfall nodes is -// within the convergence tolerance and FALSE otherwise -// Purpose: finds new depth at all nodes and checks if convergence achieved. -// -{ - int i; - double yOld; // previous node depth (ft) - - // --- compute outfall depths based on flow in connecting link - for ( i = 0; i < Nobjects[LINK]; i++ ) link_setOutfallDepth(i); - - // --- compute new depth for all non-outfall nodes and determine if - // depth change from previous iteration is below tolerance -#pragma omp parallel num_threads(NumThreads) -{ - #pragma omp for private(yOld) - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - if ( Node[i].type == OUTFALL ) continue; - yOld = Node[i].newDepth; - setNodeDepth(i, dt); - Xnode[i].converged = TRUE; - if ( fabs(yOld - Node[i].newDepth) > HeadTol ) - { - Xnode[i].converged = FALSE; - } - } -} - - // --- return FALSE if any non-Outfall node failed to converge - for (i = 0; i < Nobjects[NODE]; i++) - { - if ( Node[i].type == OUTFALL ) continue; - if (Xnode[i].converged == FALSE) return FALSE; - } - return TRUE; -} - -//============================================================================= - -void setNodeDepth(int i, double dt) -// -// Input: i = node index -// dt = time step (sec) -// Output: none -// Purpose: sets depth at non-outfall node after current time step. -// -{ - int canPond; // TRUE if node can pond overflows - int isPonded; // TRUE if node is currently ponded - int isSurcharged = FALSE; // TRUE if node is surcharged - double dQ; // inflow minus outflow at node (cfs) - double dV; // change in node volume (ft3) - double dy; // change in node depth (ft) - double yMax; // max. depth at node (ft) - double yOld; // node depth at previous time step (ft) - double yLast; // previous node depth (ft) - double yNew; // new node depth (ft) - double yCrown; // depth to node crown (ft) - double surfArea; // node surface area (ft2) - double denom; // denominator term - double corr; // correction factor - double f; // relative surcharge depth - - // --- see if node can pond water above it - canPond = (AllowPonding && Node[i].pondedArea > 0.0); - isPonded = (canPond && Node[i].newDepth > Node[i].fullDepth); - - // --- initialize values - yCrown = Node[i].crownElev - Node[i].invertElev; - yOld = Node[i].oldDepth; - yLast = Node[i].newDepth; - Node[i].overflow = 0.0; - surfArea = Xnode[i].newSurfArea; - surfArea = MAX(surfArea, MinSurfArea); - - // --- determine average net flow volume into node over the time step - dQ = Node[i].inflow - Node[i].outflow; - dV = 0.5 * (Node[i].oldNetInflow + dQ) * dt; - - - // --- determine if node is EXTRAN surcharged - if (SurchargeMethod == EXTRAN) - { - // --- ponded nodes don't surcharge - if (isPonded) isSurcharged = FALSE; - - // --- closed storage units that are full are in surcharge - else if (Node[i].type == STORAGE) - { - isSurcharged = (Node[i].surDepth > 0.0 && - yLast > Node[i].fullDepth); - } - - // --- surcharge occurs when node depth exceeds top of its highest link - else isSurcharged = (yCrown > 0.0 && yLast > yCrown); - } - - // --- if node not surcharged, base depth change on surface area - if (!isSurcharged) - { - dy = dV / surfArea; - yNew = yOld + dy; - - // --- save non-ponded surface area for use in surcharge algorithm - if ( !isPonded ) Xnode[i].oldSurfArea = surfArea; - - // --- apply under-relaxation to new depth estimate - if ( Steps > 0 ) - { - yNew = (1.0 - Omega) * yLast + Omega * yNew; - } - - // --- don't allow a ponded node to drop much below full depth - if ( isPonded && yNew < Node[i].fullDepth ) - yNew = Node[i].fullDepth - FUDGE; - } - - // --- if node surcharged, base depth change on dqdh - // NOTE: depth change is w.r.t depth from previous - // iteration; also, do not apply under-relaxation. - else - { - // --- apply correction factor for upstream terminal nodes - corr = 1.0; - if ( Node[i].degree < 0 ) corr = 0.6; - - // --- allow surface area from last non-surcharged condition - // to influence dqdh if depth close to crown depth - denom = Xnode[i].sumdqdh; - if ( yLast < 1.25 * yCrown ) - { - f = (yLast - yCrown) / yCrown; - denom += (Xnode[i].oldSurfArea/dt - - Xnode[i].sumdqdh) * exp(-15.0 * f); - } - - // --- compute new estimate of node depth - if ( denom == 0.0 ) dy = 0.0; - else dy = corr * dQ / denom; - yNew = yLast + dy; - if ( yNew < yCrown ) yNew = yCrown - FUDGE; - - // --- don't allow a newly ponded node to rise much above full depth - if ( canPond && yNew > Node[i].fullDepth ) - yNew = Node[i].fullDepth + FUDGE; - } - - // --- depth cannot be negative - if ( yNew < 0 ) yNew = 0.0; - - // --- determine max. non-flooded depth - yMax = Node[i].fullDepth; - if ( canPond == FALSE ) yMax += Node[i].surDepth; - - // --- find flooded depth & volume - if ( yNew > yMax ) - { - yNew = getFloodedDepth(i, canPond, dV, yNew, yMax, dt); - } - else Node[i].newVolume = node_getVolume(i, yNew); - - // --- compute change in depth w.r.t. time - Xnode[i].dYdT = fabs(yNew - yOld) / dt; - - // --- save new depth for node - Node[i].newDepth = yNew; -} - -//============================================================================= - -double getFloodedDepth(int i, int canPond, double dV, double yNew, - double yMax, double dt) -// -// Input: i = node index -// canPond = TRUE if water can pond over node -// isPonded = TRUE if water is currently ponded -// dV = change in volume over time step (ft3) -// yNew = current depth at node (ft) -// yMax = max. depth at node before ponding (ft) -// dt = time step (sec) -// Output: returns depth at node when flooded (ft) -// Purpose: computes depth, volume and overflow for a flooded node. -// -{ - if ( canPond == FALSE ) - { - Node[i].overflow = dV / dt; - Node[i].newVolume = Node[i].fullVolume; - yNew = yMax; - } - else - { - Node[i].newVolume = MAX((Node[i].oldVolume+dV), Node[i].fullVolume); - Node[i].overflow = (Node[i].newVolume - - MAX(Node[i].oldVolume, Node[i].fullVolume)) / dt; - } - if ( Node[i].overflow < FUDGE ) Node[i].overflow = 0.0; - return yNew; - -} - -//============================================================================= - -double getVariableStep(double maxStep) -// -// Input: maxStep = user-supplied max. time step (sec) -// Output: returns time step (sec) -// Purpose: finds time step that satisfies stability criterion but -// is no greater than the user-supplied max. time step. -// -{ - int minLink = -1; // index of link w/ min. time step - int minNode = -1; // index of node w/ min. time step - double tMin; // allowable time step (sec) - double tMinLink; // allowable time step for links (sec) - double tMinNode; // allowable time step for nodes (sec) - - // --- find stable time step for links & then nodes - tMin = maxStep; - tMinLink = getLinkStep(tMin, &minLink); - tMinNode = getNodeStep(tMinLink, &minNode); - - // --- use smaller of the link and node time step - tMin = tMinLink; - if ( tMinNode < tMin ) - { - tMin = tMinNode ; - minLink = -1; - } - - // --- update count of times the minimum node or link was critical - stats_updateCriticalTimeCount(minNode, minLink); - - // --- don't let time step go below an absolute minimum - if ( tMin < MinRouteStep ) tMin = MinRouteStep; - return tMin; -} - -//============================================================================= - -double getLinkStep(double tMin, int *minLink) -// -// Input: tMin = critical time step found so far (sec) -// Output: minLink = index of link with critical time step; -// returns critical time step (sec) -// Purpose: finds critical time step for conduits based on Courant criterion. -// -{ - int i; // link index - int k; // conduit index - double q; // conduit flow (cfs) - double t; // time step (sec) - double tLink = tMin; // critical link time step (sec) - - // --- examine each conduit link - for ( i = 0; i < Nobjects[LINK]; i++ ) - { - if ( Link[i].type == CONDUIT ) - { - // --- skip conduits with negligible flow, area or Fr - k = Link[i].subIndex; - q = fabs(Link[i].newFlow) / Conduit[k].barrels; - if ( q <= FUDGE - || Conduit[k].a1 <= FUDGE - || Link[i].froude <= 0.01 - ) continue; - - // --- compute time step to satisfy Courant condition - t = Link[i].newVolume / Conduit[k].barrels / q; - t = t * Conduit[k].modLength / link_getLength(i); - t = t * Link[i].froude / (1.0 + Link[i].froude) * CourantFactor; - - // --- update critical link time step - if ( t < tLink ) - { - tLink = t; - *minLink = i; - } - } - } - return tLink; -} - -//============================================================================= - -double getNodeStep(double tMin, int *minNode) -// -// Input: tMin = critical time step found so far (sec) -// Output: minNode = index of node with critical time step; -// returns critical time step (sec) -// Purpose: finds critical time step for nodes based on max. allowable -// projected change in depth. -// -{ - int i; // node index - double maxDepth; // max. depth allowed at node (ft) - double dYdT; // change in depth per unit time (ft/sec) - double t1; // time needed to reach depth limit (sec) - double tNode = tMin; // critical node time step (sec) - - // --- find smallest time so that estimated change in nodal depth - // does not exceed safety factor * maxdepth - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - // --- see if node can be skipped - if ( Node[i].type == OUTFALL ) continue; - if ( Node[i].newDepth <= FUDGE) continue; - if ( Node[i].newDepth + FUDGE >= - Node[i].crownElev - Node[i].invertElev ) continue; - - // --- define max. allowable depth change using crown elevation - maxDepth = (Node[i].crownElev - Node[i].invertElev) * 0.25; - if ( maxDepth < FUDGE ) continue; - dYdT = Xnode[i].dYdT; - if (dYdT < FUDGE ) continue; - - // --- compute time to reach max. depth & compare with critical time - t1 = maxDepth / dYdT; - if ( t1 < tNode ) - { - tNode = t1; - *minNode = i; - } - } - return tNode; -} diff --git a/src/enums.h b/src/enums.h deleted file mode 100644 index c260e4150..000000000 --- a/src/enums.h +++ /dev/null @@ -1,500 +0,0 @@ -//----------------------------------------------------------------------------- -// enums.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 06/01/22 (Build 5.2.1) -// Author: L. Rossman -// -// Enumerated constants -// -// Update History -// ============== -// Build 5.1.004: -// - IGNORE_RDII for the ignore RDII option added. -// Build 5.1.007: -// - s_GWF for [GWF] input file section added. -// - s_ADJUST for [ADJUSTMENTS] input file section added. -// Build 5.1.008: -// - Enumerations for fullness state of a conduit added. -// - NUM_THREADS added for number of parallel threads option. -// - Runoff flow categories added to represent mass balance components. -// Build 5.1.010: -// - New ROADWAY_WEIR type of weir added. -// - Potential evapotranspiration (PET) added as a system output variable. -// Build 5.1.011: -// - s_EVENT added to InputSectionType enumeration. -// Build 5.1.013: -// - SURCHARGE_METHOD and RULE_STEP options added. -// - WEIR_CURVE added as a curve type. -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for variable speed pumps. -// - Support added for analytical storage shapes. -// Build 5.2.1: -// - Adds a NEITHER option to the NormalFlowType enumeration. -//----------------------------------------------------------------------------- - -#ifndef ENUMS_H -#define ENUMS_H - - -//------------------------------------- -// Names of major object types -//------------------------------------- - enum ObjectType { - GAGE, // rain gage - SUBCATCH, // subcatchment - NODE, // conveyance system node - LINK, // conveyance system link - POLLUT, // pollutant - LANDUSE, // land use category - TIMEPATTERN, // dry weather flow time pattern - CURVE, // generic table of values - TSERIES, // generic time series of values - CONTROL, // conveyance system control rules - TRANSECT, // irregular channel cross-section - AQUIFER, // groundwater aquifer - UNITHYD, // RDII unit hydrograph - SNOWMELT, // snowmelt parameter set - SHAPE, // custom conduit shape - LID, // LID treatment units - STREET, // street cross section - INLET, // street inlet design - MAX_OBJ_TYPES}; - -//------------------------------------- -// Names of Node sub-types -//------------------------------------- - #define MAX_NODE_TYPES 5 - enum NodeType { - JUNCTION, - OUTFALL, - STORAGE, - DIVIDER}; - -//------------------------------------- -// Names of Link sub-types -//------------------------------------- - #define MAX_LINK_TYPES 5 - enum LinkType { - CONDUIT, - PUMP, - ORIFICE, - WEIR, - OUTLET}; - -//------------------------------------- -// File types -//------------------------------------- - enum FileType { - RAINFALL_FILE, // rainfall file - RUNOFF_FILE, // runoff file - HOTSTART_FILE, // hotstart file - RDII_FILE, // RDII file - INFLOWS_FILE, // inflows interface file - OUTFLOWS_FILE}; // outflows interface file - -//------------------------------------- -// File usage types -//------------------------------------- - enum FileUsageType { - NO_FILE, // no file usage - SCRATCH_FILE, // use temporary scratch file - USE_FILE, // use previously saved file - SAVE_FILE}; // save file currently in use - -//------------------------------------- -// Rain gage data types -//------------------------------------- - enum GageDataType { - RAIN_TSERIES, // rainfall from user-supplied time series - RAIN_FILE}; // rainfall from external file - -//------------------------------------- -// Cross section shape types -//------------------------------------- - enum XsectType { - DUMMY, // 0 - CIRCULAR, // 1 closed - FILLED_CIRCULAR, // 2 closed - RECT_CLOSED, // 3 closed - RECT_OPEN, // 4 - TRAPEZOIDAL, // 5 - TRIANGULAR, // 6 - PARABOLIC, // 7 - POWERFUNC, // 8 - RECT_TRIANG, // 9 - RECT_ROUND, // 10 - MOD_BASKET, // 11 - HORIZ_ELLIPSE, // 12 closed - VERT_ELLIPSE, // 13 closed - ARCH, // 14 closed - EGGSHAPED, // 15 closed - HORSESHOE, // 16 closed - GOTHIC, // 17 closed - CATENARY, // 18 closed - SEMIELLIPTICAL, // 19 closed - BASKETHANDLE, // 20 closed - SEMICIRCULAR, // 21 closed - IRREGULAR, // 22 - CUSTOM, // 23 closed - FORCE_MAIN, // 24 closed - STREET_XSECT}; // 25 - -//------------------------------------- -// Measurement units types -//------------------------------------- - enum UnitsType { - US, // US units - SI}; // SI (metric) units - - enum FlowUnitsType { - CFS, // cubic feet per second - GPM, // gallons per minute - MGD, // million gallons per day - CMS, // cubic meters per second - LPS, // liters per second - MLD}; // million liters per day - - enum ConcUnitsType { - MG, // Milligrams / L - UG, // Micrograms / L - COUNT}; // Counts / L - -//-------------------------------------- -// Quantities requiring unit conversions -//-------------------------------------- - enum ConversionType { - RAINFALL, - RAINDEPTH, - EVAPRATE, - LENGTH, - LANDAREA, - VOLUME, - WINDSPEED, - TEMPERATURE, - MASS, - GWFLOW, - FLOW}; // Flow must always be listed last - -//------------------------------------- -// Computed subcatchment quantities -//------------------------------------- - #define MAX_SUBCATCH_RESULTS 9 - enum SubcatchResultType { - SUBCATCH_RAINFALL, // rainfall intensity - SUBCATCH_SNOWDEPTH, // snow depth - SUBCATCH_EVAP, // evap loss - SUBCATCH_INFIL, // infil loss - SUBCATCH_RUNOFF, // runoff flow rate - SUBCATCH_GW_FLOW, // groundwater flow rate to node - SUBCATCH_GW_ELEV, // elevation of saturated gw table - SUBCATCH_SOIL_MOIST, // soil moisture - SUBCATCH_WASHOFF}; // pollutant washoff concentration - -//------------------------------------- -// Computed node quantities -//------------------------------------- - #define MAX_NODE_RESULTS 7 - enum NodeResultType { - NODE_DEPTH, // water depth above invert - NODE_HEAD, // hydraulic head - NODE_VOLUME, // volume stored & ponded - NODE_LATFLOW, // lateral inflow rate - NODE_INFLOW, // total inflow rate - NODE_OVERFLOW, // overflow rate - NODE_QUAL}; // concentration of each pollutant - -//------------------------------------- -// Computed link quantities -//------------------------------------- - #define MAX_LINK_RESULTS 6 - enum LinkResultType { - LINK_FLOW, // flow rate - LINK_DEPTH, // flow depth - LINK_VELOCITY, // flow velocity - LINK_VOLUME, // link volume - LINK_CAPACITY, // ratio of area to full area - LINK_QUAL}; // concentration of each pollutant - -//------------------------------------- -// System-wide quantities -//------------------------------------- -#define MAX_SYS_RESULTS 15 -enum SysFlowType { - SYS_TEMPERATURE, // air temperature - SYS_RAINFALL, // rainfall intensity - SYS_SNOWDEPTH, // snow depth - SYS_INFIL, // infil - SYS_RUNOFF, // runoff flow - SYS_DWFLOW, // dry weather inflow - SYS_GWFLOW, // ground water inflow - SYS_IIFLOW, // RDII inflow - SYS_EXFLOW, // external inflow - SYS_INFLOW, // total lateral inflow - SYS_FLOODING, // flooding outflow - SYS_OUTFLOW, // outfall outflow - SYS_STORAGE, // storage volume - SYS_EVAP, // evaporation - SYS_PET}; // potential ET - -//------------------------------------- -// Conduit flow classifications -//------------------------------------- - enum FlowClassType { - DRY, // dry conduit - UP_DRY, // upstream end is dry - DN_DRY, // downstream end is dry - SUBCRITICAL, // sub-critical flow - SUPCRITICAL, // super-critical flow - UP_CRITICAL, // free-fall at upstream end - DN_CRITICAL, // free-fall at downstream end - MAX_FLOW_CLASSES, // number of distinct flow classes - UP_FULL, // upstream end is full - DN_FULL, // downstream end is full - ALL_FULL}; // completely full - -//------------------------ -// Runoff flow categories -//------------------------ -enum RunoffFlowType { - RUNOFF_RAINFALL, // rainfall - RUNOFF_EVAP, // evaporation - RUNOFF_INFIL, // infiltration - RUNOFF_RUNOFF, // runoff - RUNOFF_DRAINS, // LID drain flow - RUNOFF_RUNON}; // runon from outfalls - -//------------------------------------- -// Surface pollutant loading categories -//------------------------------------- - enum LoadingType { - BUILDUP_LOAD, // pollutant buildup load - DEPOSITION_LOAD, // rainfall deposition load - SWEEPING_LOAD, // load removed by sweeping - BMP_REMOVAL_LOAD, // load removed by BMPs - INFIL_LOAD, // runon load removed by infiltration - RUNOFF_LOAD, // load removed by runoff - FINAL_LOAD}; // load remaining on surface - -//------------------------------------- -// Input data options -//------------------------------------- - enum RainfallType { - RAINFALL_INTENSITY, // rainfall expressed as intensity - RAINFALL_VOLUME, // rainfall expressed as volume - CUMULATIVE_RAINFALL}; // rainfall expressed as cumulative volume - - enum TempType { - NO_TEMP, // no temperature data supplied - TSERIES_TEMP, // temperatures come from time series - FILE_TEMP}; // temperatures come from file - -enum WindType { - MONTHLY_WIND, // wind speed varies by month - FILE_WIND}; // wind speed comes from file - - enum EvapType { - CONSTANT_EVAP, // constant evaporation rate - MONTHLY_EVAP, // evaporation rate varies by month - TIMESERIES_EVAP, // evaporation supplied by time series - TEMPERATURE_EVAP, // evaporation from daily temperature - FILE_EVAP, // evaporation comes from file - RECOVERY, // soil recovery pattern - DRYONLY}; // evap. allowed only in dry periods - - enum NormalizerType { - PER_AREA, // buildup is per unit of area - PER_CURB}; // buildup is per unit of curb length - - enum BuildupType { - NO_BUILDUP, // no buildup - POWER_BUILDUP, // power function buildup equation - EXPON_BUILDUP, // exponential function buildup equation - SATUR_BUILDUP, // saturation function buildup equation - EXTERNAL_BUILDUP}; // external time series buildup - - enum WashoffType { - NO_WASHOFF, // no washoff - EXPON_WASHOFF, // exponential washoff equation - RATING_WASHOFF, // rating curve washoff equation - EMC_WASHOFF}; // event mean concentration washoff - -enum SubAreaType { - IMPERV0, // impervious w/o depression storage - IMPERV1, // impervious w/ depression storage - PERV}; // pervious - - enum RunoffRoutingType { - TO_OUTLET, // perv & imperv runoff goes to outlet - TO_IMPERV, // perv runoff goes to imperv area - TO_PERV}; // imperv runoff goes to perv subarea - - enum RouteModelType { - NO_ROUTING, // no routing - SF, // steady flow model - KW, // kinematic wave model - EKW, // extended kin. wave model - DW}; // dynamic wave model - - enum ForceMainType { - H_W, // Hazen-Williams eqn. - D_W}; // Darcy-Weisbach eqn. - - enum OffsetType { - DEPTH_OFFSET, // offset measured as depth - ELEV_OFFSET}; // offset measured as elevation - - enum KinWaveMethodType { - NORMAL, // normal method - MODIFIED}; // modified method - -enum CompatibilityType { - SWMM5, // SWMM 5 weighting for area & hyd. radius - SWMM3, // SWMM 3 weighting - SWMM4}; // SWMM 4 weighting - - enum NormalFlowType { - SLOPE, // based on slope only - FROUDE, // based on Fr only - BOTH, // based on slope & Fr - NEITHER}; - - enum InertialDampingType { - NO_DAMPING, // no inertial damping - PARTIAL_DAMPING, // partial damping - FULL_DAMPING}; // full damping - - enum SurchargeMethodType { - EXTRAN, // original EXTRAN method - SLOT}; // Preissmann slot method - - enum InflowType { - EXTERNAL_INFLOW, // user-supplied external inflow - DRY_WEATHER_INFLOW, // user-supplied dry weather inflow - WET_WEATHER_INFLOW, // computed runoff inflow - GROUNDWATER_INFLOW, // computed groundwater inflow - RDII_INFLOW, // computed I&I inflow - FLOW_INFLOW, // inflow parameter is flow - CONCEN_INFLOW, // inflow parameter is pollutant concen. - MASS_INFLOW}; // inflow parameter is pollutant mass - - enum PatternType { - MONTHLY_PATTERN, // DWF multipliers for each month - DAILY_PATTERN, // DWF multipliers for each day of week - HOURLY_PATTERN, // DWF multipliers for each hour of day - WEEKEND_PATTERN}; // hourly multipliers for week end days - - enum OutfallType { - FREE_OUTFALL, // critical depth outfall condition - NORMAL_OUTFALL, // normal flow depth outfall condition - FIXED_OUTFALL, // fixed depth outfall condition - TIDAL_OUTFALL, // variable tidal stage outfall condition - TIMESERIES_OUTFALL}; // variable time series outfall depth - - enum StorageType { - TABULAR, // area v. depth from table - FUNCTIONAL, // area v. depth from power function - CYLINDRICAL, // area v. depth from elliptical cylinder - CONICAL, // area v. depth from elliptical cone - PARABOLOID, // area v. depth from elliptical paraboloid - PYRAMIDAL}; // area v. depth from rectangular pyramid - - enum ReactorType { - CSTR, // completely mixed reactor - PLUG}; // plug flow reactor - - enum TreatmentType { - REMOVAL, // treatment stated as a removal - CONCEN}; // treatment stated as effluent concen. - - enum DividerType { - CUTOFF_DIVIDER, // diverted flow is excess of cutoff flow - TABULAR_DIVIDER, // table of diverted flow v. inflow - WEIR_DIVIDER, // diverted flow proportional to excess flow - OVERFLOW_DIVIDER}; // diverted flow is flow > full conduit flow - - enum PumpCurveType { - TYPE1_PUMP, // flow varies stepwise with wet well volume - TYPE2_PUMP, // flow varies stepwise with inlet depth - TYPE3_PUMP, // flow varies with head delivered - TYPE4_PUMP, // flow varies with inlet depth - TYPE5_PUMP, // variable speed version of TYPE3 pump - IDEAL_PUMP}; // outflow equals inflow - - enum OrificeType { - SIDE_ORIFICE, // side orifice - BOTTOM_ORIFICE}; // bottom orifice - - enum WeirType { - TRANSVERSE_WEIR, // transverse weir - SIDEFLOW_WEIR, // side flow weir - VNOTCH_WEIR, // V-notch (triangular) weir - TRAPEZOIDAL_WEIR, // trapezoidal weir - ROADWAY_WEIR}; // FHWA HDS-5 roadway weir - - enum CurveType { - STORAGE_CURVE, // surf. area v. depth for storage node - DIVERSION_CURVE, // diverted flow v. inflow for divider node - TIDAL_CURVE, // water elev. v. hour of day for outfall - RATING_CURVE, // flow rate v. head for outlet link - CONTROL_CURVE, // control setting v. controller variable - SHAPE_CURVE, // width v. depth for custom x-section - WEIR_CURVE, // discharge coeff. v. head for weir - PUMP1_CURVE, // flow v. wet well volume for pump - PUMP2_CURVE, // flow v. depth for pump (discrete) - PUMP3_CURVE, // flow v. head for pump (continuous) - PUMP4_CURVE, // flow v. depth for pump (continuous) - PUMP5_CURVE}; // variable speed version of TYPE3 pump - - enum NodeInletType { - NO_INLET, - BYPASS, - CAPTURE - }; - - enum InputSectionType { - s_TITLE, s_OPTION, s_FILE, s_RAINGAGE, - s_TEMP, s_EVAP, s_SUBCATCH, s_SUBAREA, - s_INFIL, s_AQUIFER, s_GROUNDWATER, s_SNOWMELT, - s_JUNCTION, s_OUTFALL, s_STORAGE, s_DIVIDER, - s_CONDUIT, s_PUMP, s_ORIFICE, s_WEIR, - s_OUTLET, s_XSECTION, s_TRANSECT, s_LOSSES, - s_CONTROL, s_POLLUTANT, s_LANDUSE, s_BUILDUP, - s_WASHOFF, s_COVERAGE, s_INFLOW, s_DWF, - s_PATTERN, s_RDII, s_UNITHYD, s_LOADING, - s_TREATMENT, s_CURVE, s_TIMESERIES, s_REPORT, - s_COORDINATE, s_VERTICES, s_POLYGON, s_LABEL, - s_SYMBOL, s_BACKDROP, s_TAG, s_PROFILE, - s_MAP, s_LID_CONTROL, s_LID_USAGE, s_GWF, - s_ADJUST, s_EVENT, s_STREET, s_INLET_USAGE, - s_INLET}; - - enum InputOptionType { - FLOW_UNITS, INFIL_MODEL, ROUTE_MODEL, - START_DATE, START_TIME, END_DATE, - END_TIME, REPORT_START_DATE, REPORT_START_TIME, - SWEEP_START, SWEEP_END, START_DRY_DAYS, - WET_STEP, DRY_STEP, ROUTE_STEP, RULE_STEP, - REPORT_STEP, ALLOW_PONDING, INERT_DAMPING, - SLOPE_WEIGHTING, VARIABLE_STEP, NORMAL_FLOW_LTD, - LENGTHENING_STEP, MIN_SURFAREA, COMPATIBILITY, - SKIP_STEADY_STATE, TEMPDIR, IGNORE_RAINFALL, - FORCE_MAIN_EQN, LINK_OFFSETS, MIN_SLOPE, - IGNORE_SNOWMELT, IGNORE_GWATER, IGNORE_ROUTING, - IGNORE_QUALITY, MAX_TRIALS, HEAD_TOL, - SYS_FLOW_TOL, LAT_FLOW_TOL, IGNORE_RDII, - MIN_ROUTE_STEP, NUM_THREADS, SURCHARGE_METHOD}; - -enum NoYesType { - NO, - YES}; - -enum NoneAllType { - NONE, - ALL, - SOME}; - - -#endif //ENUMS_H diff --git a/src/error.c b/src/error.c deleted file mode 100644 index 626950b6f..000000000 --- a/src/error.c +++ /dev/null @@ -1,49 +0,0 @@ -//----------------------------------------------------------------------------- -// error.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Error messages -// -// Update History -// ============== -// Build 5.1.008: -// - Text of Error 217 for control rules modified. -// Build 5.1.010: -// - Text of Error 318 for rainfall data files modified. -// Build 5.1.015: -// - Added new Error 140 for storage nodes. -// Build 5.2.0: -// - Re-designed error message system. -// - Added new Error 235 for invalid infiltration parameters. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "error.h" - -char ErrString[256]; - -char* error_getMsg(int errCode, char* msg) -{ - switch (errCode) - { - -#define ERR(code,string) case code: strcpy(msg, string); break; -#include "error.txt" -#undef ERR - - default: - strcpy(msg, ""); - } - return (msg); -}; - -int error_setInpError(int errcode, char* s) -{ - strcpy(ErrString, s); - return errcode; -} diff --git a/src/error.h b/src/error.h deleted file mode 100644 index fedf0847e..000000000 --- a/src/error.h +++ /dev/null @@ -1,182 +0,0 @@ -//----------------------------------------------------------------------------- -// error.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Error codes -// -//----------------------------------------------------------------------------- - -#ifndef ERROR_H -#define ERROR_H - -enum ErrorType { - -// ... Runtime Errors - ERR_NONE = 0, - ERR_MEMORY = 101, - ERR_KINWAVE = 103, - ERR_ODE_SOLVER = 105, - ERR_TIMESTEP = 107, - -// ... Subcatchment/Aquifer Errors - ERR_SUBCATCH_OUTLET = 108, - ERR_AQUIFER_PARAMS = 109, - ERR_GROUND_ELEV = 110, - -// ... Conduit/Pump Errors - ERR_LENGTH = 111, - ERR_ELEV_DROP = 112, - ERR_ROUGHNESS = 113, - ERR_BARRELS = 114, - ERR_SLOPE = 115, - ERR_NO_XSECT = 117, - ERR_XSECT = 119, - ERR_NO_CURVE = 121, - ERR_PUMP_LIMITS = 122, - -// ... Topology Errors - ERR_LOOP = 131, - ERR_MULTI_OUTLET = 133, - ERR_DUMMY_LINK = 134, - -// ... Node Errors - ERR_DIVIDER = 135, - ERR_DIVIDER_LINK = 136, - ERR_WEIR_DIVIDER = 137, - ERR_NODE_DEPTH = 138, - ERR_REGULATOR = 139, - ERR_STORAGE_VOLUME = 140, - ERR_OUTFALL = 141, - ERR_REGULATOR_SHAPE = 143, - ERR_NO_OUTLETS = 145, - -// ... RDII Errors - ERR_UNITHYD_TIMES = 151, - ERR_UNITHYD_RATIOS = 153, - ERR_RDII_AREA = 155, - -// ... Rain Gage Errors - ERR_RAIN_FILE_CONFLICT = 156, - ERR_RAIN_GAGE_FORMAT = 157, - ERR_RAIN_GAGE_TSERIES = 158, - ERR_RAIN_GAGE_INTERVAL = 159, - -// ... Treatment Function Error - ERR_CYCLIC_TREATMENT = 161, - -// ... Curve/Time Series Errors - ERR_CURVE_SEQUENCE = 171, - ERR_TIMESERIES_SEQUENCE = 173, - -// ... Snowmelt Errors - ERR_SNOWMELT_PARAMS = 181, - ERR_SNOWPACK_PARAMS = 182, - -// ... LID Errors - ERR_LID_TYPE = 183, - ERR_LID_LAYER = 184, - ERR_LID_PARAMS = 185, - ERR_LID_AREAS = 187, - ERR_LID_CAPTURE_AREA = 188, - -// ... Simulation Date/Time Errors - ERR_START_DATE = 191, - ERR_REPORT_DATE = 193, - ERR_REPORT_STEP = 195, - -// ... Input Parser Errors - ERR_INPUT = 200, - ERR_LINE_LENGTH = 201, - ERR_ITEMS = 203, - ERR_KEYWORD = 205, - ERR_DUP_NAME = 207, - ERR_NAME = 209, - ERR_NUMBER = 211, - ERR_DATETIME = 213, - ERR_RULE = 217, - ERR_TRANSECT_UNKNOWN = 219, - ERR_TRANSECT_SEQUENCE = 221, - ERR_TRANSECT_TOO_FEW = 223, - ERR_TRANSECT_TOO_MANY = 225, - ERR_TRANSECT_MANNING = 227, - ERR_TRANSECT_OVERBANK = 229, - ERR_TRANSECT_NO_DEPTH = 231, - ERR_MATH_EXPR = 233, - ERR_INFIL_PARAMS = 235, - -// ... File Name/Opening Errors - ERR_FILE_NAME = 301, - ERR_INP_FILE = 303, - ERR_RPT_FILE = 305, - ERR_OUT_FILE = 307, - ERR_OUT_SIZE = 308, - ERR_OUT_WRITE = 309, - ERR_OUT_READ = 311, - -// ... Rain File Errors - ERR_RAIN_FILE_SCRATCH = 313, - ERR_RAIN_FILE_OPEN = 315, - ERR_RAIN_FILE_DATA = 317, - ERR_RAIN_FILE_SEQUENCE = 318, - ERR_RAIN_FILE_FORMAT = 319, - ERR_RAIN_IFACE_FORMAT = 320, - ERR_RAIN_FILE_GAGE = 321, - -// ... Runoff File Errors - ERR_RUNOFF_FILE_OPEN = 323, - ERR_RUNOFF_FILE_FORMAT = 325, - ERR_RUNOFF_FILE_END = 327, - ERR_RUNOFF_FILE_READ = 329, - -// ... Hotstart File Errors - ERR_HOTSTART_FILE_OPEN = 331, - ERR_HOTSTART_FILE_FORMAT = 333, - ERR_HOTSTART_FILE_READ = 335, - -// ... Climate File Errors - ERR_NO_CLIMATE_FILE = 336, - ERR_CLIMATE_FILE_OPEN = 337, - ERR_CLIMATE_FILE_READ = 338, - ERR_CLIMATE_END_OF_FILE = 339, - -// ... RDII File Errors - ERR_RDII_FILE_SCRATCH = 341, - ERR_RDII_FILE_OPEN = 343, - ERR_RDII_FILE_FORMAT = 345, - -// ... Routing File Errors - ERR_ROUTING_FILE_OPEN = 351, - ERR_ROUTING_FILE_FORMAT = 353, - ERR_ROUTING_FILE_NOMATCH = 355, - ERR_ROUTING_FILE_NAMES = 357, - -// ... Time Series File Errors - ERR_TABLE_FILE_OPEN = 361, - ERR_TABLE_FILE_READ = 363, - -// ... Runtime Errors - ERR_SYSTEM = 500, - -// ... API Errors - ERR_API_NOT_OPEN = 501, - ERR_API_NOT_STARTED = 502, - ERR_API_NOT_ENDED = 503, - ERR_API_OBJECT_TYPE = 504, - ERR_API_OBJECT_INDEX = 505, - ERR_API_OBJECT_NAME = 506, - ERR_API_PROPERTY_TYPE = 507, - ERR_API_PROPERTY_VALUE = 508, - ERR_API_TIME_PERIOD = 509, - -// ... Additional Errors - MAXERRMSG = 1000 -}; - -char* error_getMsg(int i, char* msg); -int error_setInpError(int errcode, char* s); - -#endif //ERROR_H diff --git a/src/error.txt b/src/error.txt deleted file mode 100644 index 30d45a783..000000000 --- a/src/error.txt +++ /dev/null @@ -1,135 +0,0 @@ -// SWMM 5.2 Error Messages - -ERR(101,"\n ERROR 101: memory allocation error.") -ERR(103,"\n ERROR 103: cannot solve KW equations for Link %s.") -ERR(105,"\n ERROR 105: cannot open ODE solver.") -ERR(107,"\n ERROR 107: cannot compute a valid time step.") - -ERR(108,"\n ERROR 108: ambiguous outlet ID name for Subcatchment %s.") -ERR(109,"\n ERROR 109: invalid parameter values for Aquifer %s.") -ERR(110,"\n ERROR 110: ground elevation is below water table for Subcatchment %s.") - -ERR(111,"\n ERROR 111: invalid length for Conduit %s.") -ERR(112,"\n ERROR 112: elevation drop exceeds length for Conduit %s.") -ERR(113,"\n ERROR 113: invalid roughness for Conduit %s.") -ERR(114,"\n ERROR 114: invalid number of barrels for Conduit %s.") -ERR(115,"\n ERROR 115: adverse slope for Conduit %s.") -ERR(117,"\n ERROR 117: no cross section defined for Link %s.") -ERR(119,"\n ERROR 119: invalid cross section for Link %s.") -ERR(121,"\n ERROR 121: missing or invalid pump curve assigned to Pump %s.") -ERR(122,"\n ERROR 122: startup depth not higher than shutoff depth for Pump %s.") - -ERR(131,"\n ERROR 131: the following links form cyclic loops in the drainage system:") -ERR(133,"\n ERROR 133: Node %s has more than one outlet link.") -ERR(134,"\n ERROR 134: Node %s has illegal DUMMY link connections.") - -ERR(135,"\n ERROR 135: Divider %s does not have two outlet links.") -ERR(136,"\n ERROR 136: Divider %s has invalid diversion link.") -ERR(137,"\n ERROR 137: Weir Divider %s has invalid parameters.") -ERR(138,"\n ERROR 138: Node %s has initial depth greater than maximum depth.") -ERR(139,"\n ERROR 139: Regulator %s is the outlet of a non-storage node.") -ERR(140,"\n ERROR 140: Storage node %s has negative volume at full depth.") -ERR(141,"\n ERROR 141: Outfall %s has more than 1 inlet link or an outlet link.") -ERR(143,"\n ERROR 143: Regulator %s has invalid cross-section shape.") -ERR(145,"\n ERROR 145: Drainage system has no acceptable outlet nodes.") - -ERR(151,"\n ERROR 151: a Unit Hydrograph in set %s has invalid time base.") -ERR(153,"\n ERROR 153: a Unit Hydrograph in set %s has invalid response ratios.") -ERR(155,"\n ERROR 155: invalid sewer area for RDII at node %s.") - -ERR(156,"\n ERROR 156: ambiguous station ID for Rain Gage %s.") -ERR(157,"\n ERROR 157: inconsistent rainfall format for Rain Gage %s.") -ERR(158,"\n ERROR 158: time series for Rain Gage %s is also used by another object.") -ERR(159,"\n ERROR 159: recording interval greater than time series interval for Rain Gage %s.") - -ERR(161,"\n ERROR 161: cyclic dependency in treatment functions at node %s.") - -ERR(171,"\n ERROR 171: Curve %s has invalid or out of sequence data.") -ERR(173,"\n ERROR 173: Time Series %s has its data out of sequence.") - -ERR(181,"\n ERROR 181: invalid Snow Melt Climatology parameters.") -ERR(182,"\n ERROR 182: invalid parameters for Snow Pack %s.") - -ERR(183,"\n ERROR 183: no type specified for LID %s.") -ERR(184,"\n ERROR 184: missing layer for LID %s.") -ERR(185,"\n ERROR 185: invalid parameter value for LID %s.") -ERR(187,"\n ERROR 187: LID area exceeds total area for Subcatchment %s.") -ERR(188,"\n ERROR 188: LID capture area exceeds total impervious area for Subcatchment %s.") - -ERR(191,"\n ERROR 191: simulation start date comes after ending date.") -ERR(193,"\n ERROR 193: report start date comes after ending date.") -ERR(195,"\n ERROR 195: reporting time step or duration is less than routing time step.") - -ERR(200,"\n ERROR 200: one or more errors in input file.") -ERR(201,"\n ERROR 201: too many characters in input line ") -ERR(203,"\n ERROR 203: too few items ") -ERR(205,"\n ERROR 205: invalid keyword %s ") -ERR(207,"\n ERROR 207: duplicate ID name %s ") -ERR(209,"\n ERROR 209: undefined object %s ") -ERR(211,"\n ERROR 211: invalid number %s ") -ERR(213,"\n ERROR 213: invalid date/time %s ") -ERR(217,"\n ERROR 217: control rule clause invalid or out of sequence ") -ERR(219,"\n ERROR 219: data provided for unidentified transect ") -ERR(221,"\n ERROR 221: transect station out of sequence ") -ERR(223,"\n ERROR 223: Transect %s has too few stations.") -ERR(225,"\n ERROR 225: Transect %s has too many stations.") -ERR(227,"\n ERROR 227: Transect %s has no Manning's N.") -ERR(229,"\n ERROR 229: Transect %s has invalid overbank locations.") -ERR(231,"\n ERROR 231: Transect %s has no depth.") -ERR(233,"\n ERROR 233: invalid math expression ") -ERR(235,"\n ERROR 235: invalid infiltration parameters ") - -ERR(301,"\n ERROR 301: files share same names.") -ERR(303,"\n ERROR 303: cannot open input file.") -ERR(305,"\n ERROR 305: cannot open report file.") -ERR(307,"\n ERROR 307: cannot open binary results file.") -ERR(308,"\n ERROR 308: amount of output produced will exceed maximum file size.") - -ERR(309,"\n ERROR 309: error writing to binary results file.") -ERR(311,"\n ERROR 311: error reading from binary results file.") - -ERR(313,"\n ERROR 313: cannot open scratch rainfall interface file.") -ERR(315,"\n ERROR 315: cannot open rainfall interface file %s.") -ERR(317,"\n ERROR 317: cannot open rainfall data file %s.") -ERR(318,"\n ERROR 318: the following line is out of sequence in rainfall data file %s.") -ERR(319,"\n ERROR 319: unknown format for rainfall data file %s.") -ERR(320,"\n ERROR 320: invalid format for rainfall interface file.") -ERR(321,"\n ERROR 321: no data in rainfall interface file for gage %s.") - -ERR(323,"\n ERROR 323: cannot open runoff interface file %s.") -ERR(325,"\n ERROR 325: incompatible data found in runoff interface file.") -ERR(327,"\n ERROR 327: attempting to read beyond end of runoff interface file.") -ERR(329,"\n ERROR 329: error in reading from runoff interface file.") - -ERR(331,"\n ERROR 331: cannot open hot start interface file %s.") -ERR(333,"\n ERROR 333: incompatible data found in hot start interface file.") -ERR(335,"\n ERROR 335: error in reading from hot start interface file.") - -ERR(336,"\n ERROR 336: no climate file specified for evaporation and/or wind speed.") -ERR(337,"\n ERROR 337: cannot open climate file %s.") -ERR(338,"\n ERROR 338: error in reading from climate file %s.") -ERR(339,"\n ERROR 339: attempt to read beyond end of climate file %s.") - -ERR(341,"\n ERROR 341: cannot open scratch RDII interface file.") -ERR(343,"\n ERROR 343: cannot open RDII interface file %s.") -ERR(345,"\n ERROR 345: invalid format for RDII interface file.") - -ERR(351,"\n ERROR 351: cannot open routing interface file %s.") -ERR(353,"\n ERROR 353: invalid format for routing interface file %s.") -ERR(355,"\n ERROR 355: mis-matched names in routing interface file %s.") -ERR(357,"\n ERROR 357: inflows and outflows interface files have same name.") - -ERR(361,"\n ERROR 361: could not open external file used for Time Series %s.") -ERR(363,"\n ERROR 363: invalid data in external file used for Time Series %s.") - -// API Error Keys -ERR(500,"\n ERROR 500: System exception thrown.") -ERR(501,"\n API Error 501: project not opened.") -ERR(502,"\n API Error 502: simulation not started.") -ERR(503,"\n API Error 503: simulation not ended.") -ERR(504,"\n API Error 504: invalid object type.") -ERR(505,"\n API Error 505: invalid object index.") -ERR(506,"\n API Error 506: invalid object name.") -ERR(507,"\n API Error 507: invalid property type.") -ERR(508,"\n API Error 508: invalid property value.") -ERR(509,"\n API Error 509: invalid time period.") diff --git a/src/exfil.c b/src/exfil.c deleted file mode 100644 index f1cb2adf6..000000000 --- a/src/exfil.c +++ /dev/null @@ -1,252 +0,0 @@ -//----------------------------------------------------------------------------- -// exfil.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Storage unit exfiltration functions. -// -// Update History -// ============== -// Build 5.1.008: -// - Monthly conductivity adjustment applied to exfiltration rate. -// Build 5.1.010: -// - New modified Green-Ampt infiltration option used. -// Build 5.1.011: -// - Fixed units conversion error for storage units with surface area curves. -// Build 5.2.0: -// - Support added for analytical storage shapes. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" -#include "infil.h" -#include "exfil.h" - -static int createStorageExfil(int k, double x[]); - -//============================================================================= - -int exfil_readStorageParams(int k, char* tok[], int ntoks, int n) -// -// Input: k = storage unit index -// tok[] = array of string tokens -// ntoks = number of tokens -// n = last token processed -// Output: returns an error code -// Purpose: reads a storage unit's exfiltration parameters from a -// tokenized line of input. -// -{ - int i; - double x[3]; //suction head, Ksat, IMDmax - - // --- read Ksat if it's the only remaining token - if ( ntoks == n+1 ) - { - if ( ! getDouble(tok[n], &x[1]) ) - return error_setInpError(ERR_NUMBER, tok[n]); - x[0] = 0.0; - x[2] = 0.0; - } - - // --- otherwise read Green-Ampt infiltration parameters from input tokens - else if ( ntoks < n + 3 ) return error_setInpError(ERR_ITEMS, ""); - else for (i = 0; i < 3; i++) - { - if ( ! getDouble(tok[n+i], &x[i]) ) - return error_setInpError(ERR_NUMBER, tok[n+i]); - } - - // --- no exfiltration if Ksat is 0 - if ( x[1] == 0.0 ) return 0; - - // --- create an exfiltration object - return createStorageExfil(k, x); -} - -//============================================================================= - -void exfil_initState(int k) -// -// Input: k = storage unit index -// Output: none -// Purpose: initializes the state of a storage unit's exfiltration object. -// -{ - int i; - double a, alast, d; - TTable* aCurve; - TExfil* exfil = Storage[k].exfil; - - // --- initialize exfiltration object - if ( exfil != NULL ) - { - // --- initialize the Green-Ampt infil. parameters - grnampt_initState(exfil->btmExfil); - grnampt_initState(exfil->bankExfil); - - switch (Storage[k].shape) - { - // --- shape given by a Storage Curve - case TABULAR: - i = Storage[k].aCurve; - exfil->btmArea = 0.0; - exfil->bankMinDepth = 0.0; - exfil->bankMaxDepth = 0.0; - exfil->bankMaxArea = 0.0; - if ( i >= 0 ) - { - // --- get bottom area - aCurve = &Curve[i]; - Storage[k].exfil->btmArea = table_lookupEx(aCurve, 0.0); - - // --- find min/max bank depths and max. bank area - table_getFirstEntry(aCurve, &d, &a); - alast = a; - while ( table_getNextEntry(aCurve, &d, &a) ) - { - if ( a < alast ) break; - else if ( a > alast ) - { - exfil->bankMaxArea = a; - exfil->bankMaxDepth = d; - } - else if ( exfil->bankMaxArea == 0.0 ) - exfil->bankMinDepth = d; - else break; - alast = a; - } - - // --- convert from user units to internal units - exfil->btmArea /= UCF(LENGTH) * UCF(LENGTH); - exfil->bankMaxArea /= UCF(LENGTH) * UCF(LENGTH); - exfil->bankMinDepth /= UCF(LENGTH); - exfil->bankMaxDepth /= UCF(LENGTH); - } - break; - - // --- functional storage shape curve - case FUNCTIONAL: - exfil->btmArea = Storage[k].a0; - if ( Storage[k].a2 == 0.0 ) - exfil->btmArea +=Storage[k].a1; - exfil->bankMinDepth = 0.0; - exfil->bankMaxDepth = BIG; - exfil->bankMaxArea = BIG; - break; - - // --- cylindrical, conical & prismatic shapes - case CYLINDRICAL: - case CONICAL: - case PYRAMIDAL: - exfil->btmArea = Storage[k].a0; - exfil->bankMinDepth = 0.0; - exfil->bankMaxDepth = BIG; - exfil->bankMaxArea = BIG; - break; - } - } -} - -//============================================================================= - -double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area) -// -// Input: exfil = ptr. to a storage exfiltration object -// tStep = time step (sec) -// depth = water depth (ft) -// area = surface area (ft2) -// Output: returns exfiltration rate out of storage unit (cfs) -// Purpose: computes rate of water exfiltrated from a storage node into -// the soil beneath it. -// -{ - double exfilRate = 0.0; - - // --- find infiltration through bottom of unit - if ( exfil->btmExfil->IMDmax == 0.0 ) - { - exfilRate = exfil->btmExfil->Ks * Adjust.hydconFactor; - } - else exfilRate = grnampt_getInfil(exfil->btmExfil, tStep, 0.0, depth, - MOD_GREEN_AMPT); - exfilRate *= exfil->btmArea; - - // --- find infiltration through sloped banks - if ( depth > exfil->bankMinDepth ) - { - // --- get area of banks - area = MIN(area, exfil->bankMaxArea) - exfil->btmArea; - if ( area > 0.0 ) - { - // --- if infil. rate not a function of depth - if ( exfil->btmExfil->IMDmax == 0.0 ) - { - exfilRate += area * exfil->btmExfil->Ks * Adjust.hydconFactor; - } - - // --- infil. rate depends on depth above bank - else - { - // --- case where water depth is above the point where the - // storage curve no longer has increasing area with depth - if ( depth > exfil->bankMaxDepth ) - { - depth = depth - exfil->bankMaxDepth + - (exfil->bankMaxDepth - exfil->bankMinDepth) / 2.0; - } - - // --- case where water depth is below top of bank - else depth = (depth - exfil->bankMinDepth) / 2.0; - - // --- use Green-Ampt function for bank infiltration - exfilRate += area * grnampt_getInfil(exfil->bankExfil, - tStep, 0.0, depth, MOD_GREEN_AMPT); - } - } - } - return exfilRate; -} - -//============================================================================= - -int createStorageExfil(int k, double x[]) -// -// Input: k = index of storage unit node -// x = array of Green-Ampt infiltration parameters -// Output: returns an error code. -// Purpose: creates an exfiltration object for a storage node. -// -// Note: the exfiltration object is freed in project.c. -// -{ - TExfil* exfil; - - // --- create an exfiltration object for the storage node - exfil = Storage[k].exfil; - if ( exfil == NULL ) - { - exfil = (TExfil *) malloc(sizeof(TExfil)); - if ( exfil == NULL ) return error_setInpError(ERR_MEMORY, ""); - Storage[k].exfil = exfil; - - // --- create Green-Ampt infiltration objects for the bottom & banks - exfil->btmExfil = NULL; - exfil->bankExfil = NULL; - exfil->btmExfil = (TGrnAmpt *) malloc(sizeof(TGrnAmpt)); - if ( exfil->btmExfil == NULL ) return error_setInpError(ERR_MEMORY, ""); - exfil->bankExfil = (TGrnAmpt *) malloc(sizeof(TGrnAmpt)); - if ( exfil->bankExfil == NULL ) return error_setInpError(ERR_MEMORY, ""); - } - - // --- initialize the Green-Ampt parameters - if ( !grnampt_setParams(exfil->btmExfil, x) ) - return error_setInpError(ERR_NUMBER, ""); - grnampt_setParams(exfil->bankExfil, x); - return 0; -} diff --git a/src/exfil.h b/src/exfil.h deleted file mode 100644 index 8da4da302..000000000 --- a/src/exfil.h +++ /dev/null @@ -1,35 +0,0 @@ -//----------------------------------------------------------------------------- -// exfil.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Public interface for exfiltration functions. -//----------------------------------------------------------------------------- - -#ifndef EXFIL_H -#define EXFIL_H - -//---------------------------- -// EXFILTRATION OBJECT -//---------------------------- -typedef struct -{ - TGrnAmpt* btmExfil; - TGrnAmpt* bankExfil; - double btmArea; - double bankMinDepth; - double bankMaxDepth; - double bankMaxArea; -} TExfil; - -//----------------------------------------------------------------------------- -// Exfiltration Methods -//----------------------------------------------------------------------------- -int exfil_readStorageParams(int k, char* tok[], int ntoks, int n); -void exfil_initState(int k); -double exfil_getLoss(TExfil* exfil, double tStep, double depth, double area); - -#endif diff --git a/src/findroot.c b/src/findroot.c deleted file mode 100644 index d6056f658..000000000 --- a/src/findroot.c +++ /dev/null @@ -1,138 +0,0 @@ -//----------------------------------------------------------------------------- -// findroot.c -// -// Finds solution of func(x) = 0 using either the Newton-Raphson -// method or Ridder's Method. -// Based on code from Numerical Recipes in C (Cambridge University -// Press, 1992). -// -// Date: 11/19/13 -// Author: L. Rossman -//----------------------------------------------------------------------------- - -#include -#include "findroot.h" - -#define SIGN(a,b) ((b) >= 0.0 ? fabs(a) : -fabs(a)) -#define MAXIT 60 - - -int findroot_Newton(double x1, double x2, double* rts, double xacc, - void (*func) (double x, double* f, double* df, void* p), - void* p) -// -// Using a combination of Newton-Raphson and bisection, find the root of a -// function func bracketed between x1 and x2. The root, returned in rts, -// will be refined until its accuracy is known within +/-xacc. func is a -// user-supplied routine, that returns both the function value and the first -// derivative of the function. p is a pointer to any auxilary data structure -// that func may require. It can be NULL if not needed. The function returns -// the number of function evaluations used or 0 if the maximum allowed -// iterations were exceeded. -// -// NOTES: -// 1. The calling program must insure that the signs of func(x1) and func(x2) -// are not the same, otherwise x1 and x2 do not bracket the root. -// 2. If func(x1) > func(x2) then the order of x1 and x2 should be -// switched in the call to Newton. -// -{ - int j, n = 0; - double df, dx, dxold, f, x; - double temp, xhi, xlo; - - // Initialize the "stepsize before last" and the last step. - x = *rts; - xlo = x1; - xhi = x2; - dxold = fabs(x2-x1); - dx = dxold; - func(x, &f, &df, p); - n++; - - // Loop over allowed iterations. - for (j=1; j<=MAXIT; j++) - { - // Bisect if Newton out of range or not decreasing fast enough. - if ( ( ( (x-xhi)*df-f)*((x-xlo)*df-f) >= 0.0 - || (fabs(2.0*f) > fabs(dxold*df) ) ) ) - { - dxold = dx; - dx = 0.5*(xhi-xlo); - x = xlo + dx; - if ( xlo == x ) break; - } - - // Newton step acceptable. Take it. - else - { - dxold = dx; - dx = f/df; - temp = x; - x -= dx; - if ( temp == x ) break; - } - - // Convergence criterion. - if ( fabs(dx) < xacc ) break; - - // Evaluate function. Maintain bracket on the root. - func(x, &f, &df, p); - n++; - if ( f < 0.0 ) xlo = x; - else xhi = x; - } - *rts = x; - if ( n <= MAXIT) return n; - else return 0; -}; - - -double findroot_Ridder(double x1, double x2, double xacc, - double (*func)(double, void* p), void* p) -{ - int j; - double ans, fhi, flo, fm, fnew, s, xhi, xlo, xm, xnew; - - flo = func(x1, p); - fhi = func(x2, p); - if ( flo == 0.0 ) return x1; - if ( fhi == 0.0 ) return x2; - ans = 0.5*(x1+x2); - if ( (flo > 0.0 && fhi < 0.0) || (flo < 0.0 && fhi > 0.0) ) - { - xlo = x1; - xhi = x2; - for (j=1; j<=MAXIT; j++) { - xm = 0.5*(xlo + xhi); - fm = func(xm, p); - s = sqrt( fm*fm - flo*fhi ); - if (s == 0.0) return ans; - xnew = xm + (xm-xlo)*( (flo >= fhi ? 1.0 : -1.0)*fm/s ); - if ( fabs(xnew - ans) <= xacc ) break; - ans = xnew; - fnew = func(ans, p); - if ( SIGN(fm, fnew) != fm) - { - xlo = xm; - flo = fm; - xhi = ans; - fhi = fnew; - } - else if ( SIGN(flo, fnew) != flo ) - { - xhi = ans; - fhi = fnew; - } - else if ( SIGN(fhi, fnew) != fhi) - { - xlo = ans; - flo = fnew; - } - else return ans; - if ( fabs(xhi - xlo) <= xacc ) return ans; - } - return ans; - } - return -1.e20; -} diff --git a/src/findroot.h b/src/findroot.h deleted file mode 100644 index 3b54c6456..000000000 --- a/src/findroot.h +++ /dev/null @@ -1,18 +0,0 @@ -//----------------------------------------------------------------------------- -// findroot.h -// -// Header file for root finding method contained in findroot.c -// -// Last modified on 11/19/13. -//----------------------------------------------------------------------------- - -#ifndef FINDROOT_H -#define FINDROOT_H - -int findroot_Newton(double x1, double x2, double* rts, double xacc, - void (*func) (double x, double* f, double* df, void* p), - void* p); -double findroot_Ridder(double x1, double x2, double xacc, - double (*func)(double, void* p), void* p); - -#endif //FINDROOT_H diff --git a/src/flowrout.c b/src/flowrout.c deleted file mode 100644 index f296c3fb0..000000000 --- a/src/flowrout.c +++ /dev/null @@ -1,800 +0,0 @@ -//----------------------------------------------------------------------------- -// flowrout.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 05/02/22 (Build 5.2.1) -// Author: L. Rossman -// M. Tryby (EPA) -// -// Flow routing functions. -// -// Update History -// ============== -// Build 5.1.007: -// - updateStorageState() modified in response to node outflow being -// initialized with current evap & seepage losses in routing_execute(). -// Build 5.1.008: -// - Determination of node crown elevations moved to dynwave.c. -// - Support added for new way of recording conduit's fullness state. -// Build 5.1.012: -// - Overflow computed in updateStorageState() must be non-negative. -// - Terminal storage nodes now updated corectly. -// Build 5.1.014: -// - Arguments to function link_getLossRate changed. -// Build 5.2.0: -// - Correction made to updating state of terminal storage nodes. -// Build 5.2.1: -// - For storage routing, after convergence the reported depth is now -// based on the last volume found rather than the next trial depth. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double OMEGA = 0.55; // under-relaxation parameter -static const int MAXITER = 10; // max. iterations for storage updating -static const double STOPTOL = 0.005; // storage updating stopping tolerance - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// flowrout_init (called by routing_open) -// flowrout_close (called by routing_close) -// flowrout_getRoutingStep (called routing_getRoutingStep) -// flowrout_execute (called routing_execute) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static void initLinkDepths(void); -static void initNodeDepths(void); -static void initNodes(void); -static void initLinks(int routingModel); -static void validateTreeLayout(void); -static void validateGeneralLayout(void); -static void updateStorageState(int i, int j, int links[], double dt); -static double getStorageOutflow(int node, int j, int links[], double dt); -static double getLinkInflow(int link, double dt); -static void setNewNodeState(int node, double dt); -static void setNewLinkState(int link); -static void updateNodeDepth(int node, double y); -static int steadyflow_execute(int link, double* qin, double* qout, - double tStep); - - -//============================================================================= - -void flowrout_init(int routingModel) -// -// Input: routingModel = routing model code -// Output: none -// Purpose: initializes flow routing system. -// -{ - // --- initialize for dynamic wave routing - if ( routingModel == DW ) - { - // --- check for valid conveyance network layout - validateGeneralLayout(); - dynwave_init(); - - // --- initialize node & link depths if not using a hotstart file - if ( Fhotstart1.mode == NO_FILE ) - { - initNodeDepths(); - initLinkDepths(); - } - } - - // --- validate network layout for kinematic wave routing - else validateTreeLayout(); - - // --- initialize node & link volumes - initNodes(); - initLinks(routingModel); -} - -//============================================================================= - -void flowrout_close(int routingModel) -// -// Input: routingModel = routing method code -// Output: none -// Purpose: closes down routing method used. -// -{ - if ( routingModel == DW ) dynwave_close(); -} - -//============================================================================= - -double flowrout_getRoutingStep(int routingModel, double fixedStep) -// -// Input: routingModel = type of routing method used -// fixedStep = user-assigned max. routing step (sec) -// Output: returns adjusted value of routing time step (sec) -// Purpose: finds variable time step for dynamic wave routing. -// -{ - if ( routingModel == DW ) - { - return dynwave_getRoutingStep(fixedStep); - } - return fixedStep; -} - -//============================================================================= - -int flowrout_execute(int links[], int routingModel, double tStep) -// -// Input: links = array of link indexes in topo-sorted order (per routing model) -// routingModel = type of routing method used -// tStep = routing time step (sec) -// Output: returns number of computational steps taken -// Purpose: routes flow through conveyance network over current time step. -// -{ - int i, j; - int n1; // upstream node of link - double qin; // link inflow (cfs) - double qout; // link outflow (cfs) - double steps; // computational step count - - // --- set overflows to drain any ponded water - if ( ErrorCode ) return 0; - for (j = 0; j < Nobjects[NODE]; j++) - { - Node[j].updated = FALSE; - Node[j].overflow = 0.0; - if ( Node[j].type != STORAGE - && Node[j].newVolume > Node[j].fullVolume ) - { - Node[j].overflow = (Node[j].newVolume - Node[j].fullVolume)/tStep; - } - } - - // --- execute dynamic wave routing if called for - if ( routingModel == DW ) - { - return dynwave_execute(tStep); - } - - // --- otherwise examine each link, moving from upstream to downstream - steps = 0.0; - for (i = 0; i < Nobjects[LINK]; i++) - { - // --- see if upstream node is a storage unit whose state needs updating - j = links[i]; - n1 = Link[j].node1; - if ( Node[n1].type == STORAGE ) updateStorageState(n1, i, links, tStep); - - // --- retrieve inflow at upstream end of link - qin = getLinkInflow(j, tStep); - - // --- route flow through link - if ( routingModel == SF ) - steps += steadyflow_execute(j, &qin, &qout, tStep); - else - steps += kinwave_execute(j, &qin, &qout, tStep); - Link[j].newFlow = qout; - - // adjust outflow at upstream node and inflow at downstream node - Node[ Link[j].node1 ].outflow += qin; - Node[ Link[j].node2 ].inflow += qout; - } - if ( Nobjects[LINK] > 0 ) steps /= Nobjects[LINK]; - - // --- update state of each non-updated node and link - for ( j=0; j 2 ) - { - report_writeErrorMsg(ERR_DIVIDER, Node[j].ID); - } - break; - - // --- outfalls cannot have any outlet links - case OUTFALL: - if ( Node[j].degree > 0 ) - { - report_writeErrorMsg(ERR_OUTFALL, Node[j].ID); - } - break; - - // --- storage nodes can have multiple outlets - case STORAGE: break; - - // --- all other nodes allowed only one outlet link - default: - if ( Node[j].degree > 1 ) - { - report_writeErrorMsg(ERR_MULTI_OUTLET, Node[j].ID); - } - } - } - - // --- check links - for (j=0; j 1 ) - { - report_writeErrorMsg(ERR_DUMMY_LINK, Node[i].ID); - } - } - } - - // --- check each node to see if it qualifies as an outlet node - // (meaning that degree = 0) - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - // --- if node is of type Outfall, check that it has only 1 - // connecting link (which can either be an outflow or inflow link) - if ( Node[i].type == OUTFALL ) - { - if ( Node[i].degree + (int)Node[i].inflow > 1 ) - { - report_writeErrorMsg(ERR_OUTFALL, Node[i].ID); - } - else outletCount++; - } - } - if ( outletCount == 0 ) report_writeErrorMsg(ERR_NO_OUTLETS, ""); - - // --- reset node inflows back to zero - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - if ( Node[i].inflow == 0.0 ) Node[i].degree = -Node[i].degree; - Node[i].inflow = 0.0; - } -} - -//============================================================================= - -void initNodeDepths(void) -// -// Input: none -// Output: none -// Purpose: sets initial depth at nodes for Dynamic Wave flow routing. -// -{ - int i; // link or node index - int n; // node index - double y; // node water depth (ft) - - // --- use Node[].inflow as a temporary accumulator for depth in - // connecting links and Node[].outflow as a temporary counter - // for the number of connecting links - for (i = 0; i < Nobjects[NODE]; i++) - { - Node[i].inflow = 0.0; - Node[i].outflow = 0.0; - } - - // --- total up flow depths in all connecting links into nodes - for (i = 0; i < Nobjects[LINK]; i++) - { - if ( Link[i].newDepth > FUDGE ) y = Link[i].newDepth + Link[i].offset1; - else y = 0.0; - n = Link[i].node1; - Node[n].inflow += y; - Node[n].outflow += 1.0; - n = Link[i].node2; - Node[n].inflow += y; - Node[n].outflow += 1.0; - } - - // --- if no user-supplied depth then set initial depth at non-storage/ - // non-outfall nodes to average of depths in connecting links - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - if ( Node[i].type == OUTFALL ) continue; - if ( Node[i].type == STORAGE ) continue; - if ( Node[i].initDepth > 0.0 ) continue; - if ( Node[i].outflow > 0.0 ) - { - Node[i].newDepth = Node[i].inflow / Node[i].outflow; - } - } - - // --- compute initial depths at all outfall nodes - for ( i = 0; i < Nobjects[LINK]; i++ ) link_setOutfallDepth(i); -} - -//============================================================================= - -void initLinkDepths() -// -// Input: none -// Output: none -// Purpose: sets initial flow depths in conduits under Dyn. Wave routing. -// -{ - int i; // link index - double y, y1, y2; // depths (ft) - - // --- examine each link - for (i = 0; i < Nobjects[LINK]; i++) - { - // --- examine each conduit - if ( Link[i].type == CONDUIT ) - { - // --- skip conduits with user-assigned initial flows - // (their depths have already been set to normal depth) - if ( Link[i].q0 != 0.0 ) continue; - - // --- set depth to average of depths at end nodes - y1 = Node[Link[i].node1].newDepth - Link[i].offset1; - y1 = MAX(y1, 0.0); - y1 = MIN(y1, Link[i].xsect.yFull); - y2 = Node[Link[i].node2].newDepth - Link[i].offset2; - y2 = MAX(y2, 0.0); - y2 = MIN(y2, Link[i].xsect.yFull); - y = 0.5 * (y1 + y2); - y = MAX(y, FUDGE); - Link[i].newDepth = y; - } - } -} - -//============================================================================= - -void initNodes() -// -// Input: none -// Output: none -// Purpose: sets initial inflow/outflow and volume for each node -// -{ - int i; - - for ( i = 0; i < Nobjects[NODE]; i++ ) - { - // --- initialize node inflow and outflow - Node[i].inflow = Node[i].newLatFlow; - Node[i].outflow = 0.0; - - // --- initialize node volume - Node[i].newVolume = 0.0; - if ( AllowPonding && - Node[i].pondedArea > 0.0 && - Node[i].newDepth > Node[i].fullDepth ) - { - Node[i].newVolume = Node[i].fullVolume + - (Node[i].newDepth - Node[i].fullDepth) * - Node[i].pondedArea; - } - else Node[i].newVolume = node_getVolume(i, Node[i].newDepth); - } - - // --- update nodal inflow/outflow at ends of each link - // (needed for Steady Flow & Kin. Wave routing) - for ( i = 0; i < Nobjects[LINK]; i++ ) - { - if ( Link[i].newFlow >= 0.0 ) - { - Node[Link[i].node1].outflow += Link[i].newFlow; - Node[Link[i].node2].inflow += Link[i].newFlow; - } - else - { - Node[Link[i].node1].inflow -= Link[i].newFlow; - Node[Link[i].node2].outflow -= Link[i].newFlow; - } - } -} - -//============================================================================= - -void initLinks(int routingModel) -// -// Input: none -// Output: none -// Purpose: sets initial upstream/downstream conditions in links. -// -{ - int i; // link index - int k; // conduit or pump index - - // --- examine each link - for ( i = 0; i < Nobjects[LINK]; i++ ) - { - if ( routingModel == SF) Link[i].newFlow = 0.0; - - // --- otherwise if link is a conduit - else if ( Link[i].type == CONDUIT ) - { - // --- assign initial flow to both ends of conduit - k = Link[i].subIndex; - Conduit[k].q1 = Link[i].newFlow / Conduit[k].barrels; - Conduit[k].q2 = Conduit[k].q1; - - // --- find areas based on initial flow depth - Conduit[k].a1 = xsect_getAofY(&Link[i].xsect, Link[i].newDepth); - Conduit[k].a2 = Conduit[k].a1; - - // --- compute initial volume from area - { - Link[i].newVolume = Conduit[k].a1 * link_getLength(i) * - Conduit[k].barrels; - } - Link[i].oldVolume = Link[i].newVolume; - } - } -} - -//============================================================================= - -double getLinkInflow(int j, double dt) -// -// Input: j = link index -// dt = routing time step (sec) -// Output: returns link inflow (cfs) -// Purpose: finds flow into upstream end of link at current time step under -// Steady or Kin. Wave routing. -// -{ - int n1 = Link[j].node1; - double q; - if ( Link[j].type == CONDUIT || - Link[j].type == PUMP || - Node[n1].type == STORAGE ) q = link_getInflow(j); - else q = 0.0; - return node_getMaxOutflow(n1, q, dt); -} - -//============================================================================= - -void updateStorageState(int i, int j, int links[], double dt) -// -// Input: i = index of storage node -// j = current position in links array -// links = array of topo-sorted link indexes -// dt = routing time step (sec) -// Output: none -// Purpose: updates depth and volume of a storage node using successive -// approximation with under-relaxation for Steady or Kin. Wave -// routing. -// -{ - int iter; // iteration counter - int stopped; // TRUE when iterations stop - double vFixed; // fixed terms of flow balance eqn. - double v2; // new volume estimate (ft3) - double d1; // initial value of storage depth (ft) - double d2; // updated value of storage depth (ft) - - // --- see if storage node needs updating - if ( Node[i].type != STORAGE ) return; - if ( Node[i].updated ) return; - - // --- compute terms of flow balance eqn. - // v2 = v1 + (inflow - outflow)*dt - // that do not depend on storage depth at end of time step - vFixed = Node[i].oldVolume + - 0.5 * (Node[i].oldNetInflow + Node[i].inflow - - Node[i].outflow) * dt; - d1 = Node[i].newDepth; - - // --- iterate finding outflow (which depends on depth) and subsequent - // new volume and depth until negligible depth change occurs - iter = 1; - stopped = FALSE; - while ( iter < MAXITER && !stopped ) - { - // --- find new volume from flow balance eqn. - v2 = vFixed - 0.5 * getStorageOutflow(i, j, links, dt) * dt; - - // --- limit volume to full volume if no ponding - // and compute overflow rate - v2 = MAX(0.0, v2); - Node[i].overflow = 0.0; - if ( v2 > Node[i].fullVolume ) - { - Node[i].overflow = (v2 - MAX(Node[i].oldVolume, - Node[i].fullVolume)) / dt; - if ( Node[i].overflow < FUDGE ) Node[i].overflow = 0.0; - if ( !AllowPonding || Node[i].pondedArea == 0.0 ) - v2 = Node[i].fullVolume; - } - - // --- update node's volume & depth - Node[i].newVolume = v2; - d2 = node_getDepth(i, v2); - Node[i].newDepth = d2; - - // --- use under-relaxation to estimate new depth value - // and stop if close enough to previous value - d2 = (1.0 - OMEGA)*d1 + OMEGA*d2; - if ( fabs(d2 - d1) <= STOPTOL ) stopped = TRUE; - - // --- update old depth with new value and continue to iterate - d1 = d2; - iter++; - } - - // --- mark node as being updated - Node[i].updated = TRUE; -} - -//============================================================================= - -double getStorageOutflow(int i, int j, int links[], double dt) -// -// Input: i = index of storage node -// j = current position in links array -// links = array of topo-sorted link indexes -// dt = routing time step (sec) -// Output: returns total outflow from storage node (cfs) -// Purpose: computes total flow released from a storage node. -// -{ - int k, m; - double outflow = 0.0; - - for (k = j; k < Nobjects[LINK]; k++) - { - m = links[k]; - if ( Link[m].node1 != i ) break; - outflow += getLinkInflow(m, dt); - } - return outflow; -} - -//============================================================================= - -void setNewNodeState(int j, double dt) -// -// Input: j = node index -// dt = time step (sec) -// Output: none -// Purpose: updates state of node after current time step -// for Steady Flow or Kinematic Wave flow routing. -// -{ - int canPond; // TRUE if ponding can occur at node - double newNetInflow; // inflow - outflow at node (cfs) - - // --- update terminal storage nodes - if ( Node[j].type == STORAGE ) - { - if ( Node[j].updated == FALSE ) - updateStorageState(j, Nobjects[LINK], NULL, dt); - return; - } - - // --- update stored volume - newNetInflow = Node[j].inflow - Node[j].outflow - Node[j].losses; - Node[j].newVolume = Node[j].oldVolume + newNetInflow * dt; - if ( Node[j].newVolume < FUDGE ) Node[j].newVolume = 0.0; - - // --- determine any overflow lost from system - Node[j].overflow = 0.0; - canPond = (AllowPonding && Node[j].pondedArea > 0.0); - if ( Node[j].newVolume > Node[j].fullVolume ) - { - Node[j].overflow = (Node[j].newVolume - MAX(Node[j].oldVolume, - Node[j].fullVolume)) / dt; - if ( Node[j].overflow < FUDGE ) Node[j].overflow = 0.0; - if ( !canPond ) Node[j].newVolume = Node[j].fullVolume; - } - - // --- compute a depth from volume - // (depths at upstream nodes are subsequently adjusted in - // setNewLinkState to reflect depths in connected conduit) - Node[j].newDepth = node_getDepth(j, Node[j].newVolume); -} - -//============================================================================= - -void setNewLinkState(int j) -// -// Input: j = link index -// Output: none -// Purpose: updates state of link after current time step under -// Steady Flow or Kinematic Wave flow routing -// -{ - int k; - double a, y1, y2; - - Link[j].newDepth = 0.0; - Link[j].newVolume = 0.0; - - if ( Link[j].type == CONDUIT ) - { - // --- find avg. depth from entry/exit conditions - k = Link[j].subIndex; - a = 0.5 * (Conduit[k].a1 + Conduit[k].a2); - Link[j].newVolume = a * link_getLength(j) * Conduit[k].barrels; - y1 = xsect_getYofA(&Link[j].xsect, Conduit[k].a1); - y2 = xsect_getYofA(&Link[j].xsect, Conduit[k].a2); - Link[j].newDepth = 0.5 * (y1 + y2); - - // --- update depths at end nodes - updateNodeDepth(Link[j].node1, y1 + Link[j].offset1); - updateNodeDepth(Link[j].node2, y2 + Link[j].offset2); - - // --- check if capacity limited - if ( Conduit[k].a1 >= Link[j].xsect.aFull ) - { - Conduit[k].capacityLimited = TRUE; - Conduit[k].fullState = ALL_FULL; - } - else - { - Conduit[k].capacityLimited = FALSE; - Conduit[k].fullState = 0; - } - } -} - -//============================================================================= - -void updateNodeDepth(int i, double y) -// -// Input: i = node index -// y = flow depth (ft) -// Output: none -// Purpose: updates water depth at a node with a possibly higher value. -// -{ - // --- storage nodes were updated elsewhere - if ( Node[i].type == STORAGE ) return; - - // --- if non-outfall node is flooded, then use full depth - if ( Node[i].type != OUTFALL && Node[i].degree > 0 && - Node[i].overflow > 0.0 ) y = Node[i].fullDepth; - - // --- if current new depth below y - if ( Node[i].newDepth < y ) - { - // --- update new depth - Node[i].newDepth = y; - - // --- depth cannot exceed full depth (if value exists) - if ( Node[i].fullDepth > 0.0 && y > Node[i].fullDepth ) - { - Node[i].newDepth = Node[i].fullDepth; - } - } -} - -//============================================================================= - -int steadyflow_execute(int j, double* qin, double* qout, double tStep) -// -// Input: j = link index -// qin = inflow to link (cfs) -// tStep = time step (sec) -// Output: qin = adjusted inflow to link (limited by flow capacity) (cfs) -// qout = link's outflow (cfs) -// returns 1 if successful -// Purpose: performs steady flow routing through a single link. -// -{ - int k; - double s; - double q; - - // --- use Manning eqn. to compute flow area for conduits - if ( Link[j].type == CONDUIT ) - { - k = Link[j].subIndex; - q = (*qin) / Conduit[k].barrels; - if ( Link[j].xsect.type == DUMMY ) Conduit[k].a1 = 0.0; - else - { - // --- adjust flow for evap and infil losses - q -= link_getLossRate(j, q); - - // --- flow can't exceed full flow - if ( q > Link[j].qFull ) - { - q = Link[j].qFull; - Conduit[k].a1 = Link[j].xsect.aFull; - (*qin) = q * Conduit[k].barrels; - } - - // --- infer flow area from flow rate - else - { - s = q / Conduit[k].beta; - Conduit[k].a1 = xsect_getAofS(&Link[j].xsect, s); - } - } - Conduit[k].a2 = Conduit[k].a1; - - Conduit[k].q1Old = Conduit[k].q1; - Conduit[k].q2Old = Conduit[k].q2; - - Conduit[k].q1 = q; - Conduit[k].q2 = q; - (*qout) = q * Conduit[k].barrels; - } - else (*qout) = (*qin); - return 1; -} - -//============================================================================= diff --git a/src/forcmain.c b/src/forcmain.c deleted file mode 100644 index a6314bbce..000000000 --- a/src/forcmain.c +++ /dev/null @@ -1,157 +0,0 @@ -//----------------------------------------------------------------------------- -// forcemain.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Special Non-Manning Force Main functions -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double VISCOS = 1.1E-5; // Kinematic viscosity of water - // @ 20 deg C (sq ft/sec) - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// forcemain_getEquivN -// forcemain_getRoughFactor -// forcemain_getFricSlope - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static double forcemain_getFricFactor(double e, double hrad, double re); -static double forcemain_getReynolds(double v, double hrad); - -//============================================================================= - -double forcemain_getEquivN(int j, int k) -// -// Input: j = link index -// k = conduit index -// Output: returns an equivalent Manning's n for a force main -// Purpose: computes a Mannng's n that results in the same normal flow -// value for a force main flowing full under fully turbulent -// conditions using either the Hazen-Williams or Dary-Weisbach -// flow equations. -// -{ - TXsect xsect = Link[j].xsect; - double f; - double d = xsect.yFull; - switch ( ForceMainEqn ) - { - case H_W: - return 1.067 / xsect.rBot * pow(d/Conduit[k].slope, 0.04); - case D_W: - f = forcemain_getFricFactor(xsect.rBot, d/4.0, 1.0e12); - return sqrt(f/185.0) * pow(d, (1./6.)); - } - return Conduit[k].roughness; -} - -//============================================================================= - -double forcemain_getRoughFactor(int j, double lengthFactor) -// -// Input: j = link index -// lengthFactor = factor by which a pipe will be artifically lengthened -// Output: returns a roughness adjustment factor for a force main -// Purpose: computes an adjustment factor for a force main that compensates for -// any artificial lengthening the pipe may have received. -// -{ - TXsect xsect = Link[j].xsect; - double r; - switch ( ForceMainEqn ) - { - case H_W: - r = 1.318*xsect.rBot*pow(lengthFactor, 0.54); - return GRAVITY / pow(r, 1.852); - case D_W: - return 1.0/8.0/lengthFactor; - } - return 0.0; -} - -//============================================================================= - -double forcemain_getFricSlope(int j, double v, double hrad) -// -// Input: j = link index -// v = flow velocity (ft/sec) -// hrad = hydraulic radius (ft) -// Output: returns a force main pipe's friction slope -// Purpose: computes the headloss per unit length used in dynamic wave -// flow routing for a pressurized force main using either the -// Hazen-Williams or Darcy-Weisbach flow equations. -// Note: the pipe's roughness factor was saved in xsect.sBot in -// conduit_validate() in LINK.C. -// -{ - double re, f; - TXsect xsect = Link[j].xsect; - switch ( ForceMainEqn ) - { - case H_W: - return xsect.sBot * pow(v, 0.852) / pow(hrad, 1.1667); - case D_W: - re = forcemain_getReynolds(v, hrad); - f = forcemain_getFricFactor(xsect.rBot, hrad, re); - return f * xsect.sBot * v / hrad; - } - return 0.0; -} - -//============================================================================= - -double forcemain_getReynolds(double v, double hrad) -// -// Input: v = flow velocity (ft/sec) -// hrad = hydraulic radius (ft) -// Output: returns a flow's Reynolds Number -// Purpose: computes a flow's Reynolds Number -// -{ - return 4.0 * hrad * v / VISCOS; -} - -//============================================================================= - -double forcemain_getFricFactor(double e, double hrad, double re) -// -// Input: e = roughness height (ft) -// hrad = hydraulic radius (ft) -// re = Reynolds number -// Output: returns a Darcy-Weisbach friction factor -// Purpose: computes the Darcy-Weisbach friction factor for a force main -// using the Swamee and Jain approximation to the Colebrook-White -// equation. -// -{ - double f; - if ( re < 10.0 ) re = 10.0; - if ( re <= 2000.0 ) f = 64.0 / re; - else if ( re < 4000.0 ) - { - f = forcemain_getFricFactor(e, hrad, 4000.0); - f = 0.032 + (f - 0.032) * ( re - 2000.0) / 2000.0; - } - else - { - f = e/3.7/(4.0*hrad); - if ( re < 1.0e10 ) f += 5.74/pow(re, 0.9); - f = log10(f); - f = 0.25 / f / f; - } - return f; -} diff --git a/src/funcs.h b/src/funcs.h deleted file mode 100644 index 6f9782723..000000000 --- a/src/funcs.h +++ /dev/null @@ -1,547 +0,0 @@ -//----------------------------------------------------------------------------- -// funcs.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// M. Tryby (EPA) -// -// Global interfacing functions. -// -// Update History -// ============== -// Build 5.1.007: -// - climate_readAdjustments() added. -// Build 5.1.008: -// - Function list was re-ordered and blank lines added for readability. -// - Pollutant buildup/washoff functions for the new surfqual.c module added. -// - Several other functions added, re-named or have modified arguments. -// Build 5.1.010: -// - New roadway_getInflow() function added. -// Build 5.1.013: -// - Additional arguments added to function stats_updateSubcatchStats. -// Build 5.1.014: -// - Arguments to link_getLossRate function changed. -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for reporting most frequent non-converging links. -// - Support added for named variables & math expressions in control rules. -// - Support added for tracking a gage's prior n-hour rainfall total. -// - Refactored external inflow code. -//----------------------------------------------------------------------------- - -#ifndef FUNCS_H -#define FUNCS_H - -//----------------------------------------------------------------------------- -// Project Methods -//----------------------------------------------------------------------------- -void project_open(const char *f1, const char *f2, const char *f3); -void project_close(void); - -void project_readInput(void); -int project_readOption(char* s1, char* s2); -void project_validate(void); -int project_init(void); - -int project_addObject(int type, char* id, int n); -int project_findObject(int type, const char* id); -char* project_findID(int type, char* id); - -double** project_createMatrix(int nrows, int ncols); -void project_freeMatrix(double** m); - -//----------------------------------------------------------------------------- -// Input Reader Methods -//----------------------------------------------------------------------------- -int input_countObjects(void); -int input_readData(void); - -//----------------------------------------------------------------------------- -// Report Writer Methods -//----------------------------------------------------------------------------- -int report_readOptions(char* tok[], int ntoks); - -void report_writeLine(const char* line); -void report_writeSysTime(void); -void report_writeLogo(void); -void report_writeTitle(void); -void report_writeOptions(void); -void report_writeReport(void); - -void report_writeRainStats(int gage, TRainStats* rainStats); -void report_writeRdiiStats(double totalRain, double totalRdii); - -void report_writeControlActionsHeading(void); -void report_writeControlAction(DateTime aDate, char* linkID, double value, - char* ruleID); - -void report_writeRunoffError(TRunoffTotals* totals, double area); -void report_writeLoadingError(TLoadingTotals* totals); -void report_writeGwaterError(TGwaterTotals* totals, double area); -void report_writeFlowError(TRoutingTotals* totals); -void report_writeQualError(TRoutingTotals* totals); - -void report_writeMaxStats(TMaxStats massBalErrs[], TMaxStats CourantCrit[], - int nMaxStats); -void report_writeMaxFlowTurns(TMaxStats flowTurns[], int nMaxStats); -void report_writeNonconvergedStats(TMaxStats maxNonconverged[], - int nMaxStats); -void report_writeTimeStepStats(TTimeStepStats* timeStepStats); - -void report_writeErrorMsg(int code, char* msg); -void report_writeErrorCode(void); -void report_writeInputErrorMsg(int k, int sect, char* line, long lineCount); -void report_writeWarningMsg(char* msg, char* id); -void report_writeTseriesErrorMsg(int code, TTable *tseries); - -void inputrpt_writeInput(void); -void statsrpt_writeReport(void); - -//----------------------------------------------------------------------------- -// Temperature/Evaporation Methods -//----------------------------------------------------------------------------- -int climate_readParams(char* tok[], int ntoks); -int climate_readEvapParams(char* tok[], int ntoks); -int climate_readAdjustments(char* tok[], int ntoks); -void climate_validate(void); -void climate_openFile(void); -void climate_initState(void); -void climate_setState(DateTime aDate); -DateTime climate_getNextEvapDate(void); - -//----------------------------------------------------------------------------- -// Rainfall Processing Methods -//----------------------------------------------------------------------------- -void rain_open(void); -void rain_close(void); - -//----------------------------------------------------------------------------- -// Snowmelt Processing Methods -//----------------------------------------------------------------------------- -int snow_readMeltParams(char* tok[], int ntoks); -int snow_createSnowpack(int subcacth, int snowIndex); - -void snow_validateSnowmelt(int snowIndex); -void snow_initSnowpack(int subcatch); -void snow_initSnowmelt(int snowIndex); - -void snow_getState(int subcatch, int subArea, double x[]); -void snow_setState(int subcatch, int subArea, double x[]); - -void snow_setMeltCoeffs(int snowIndex, double season); -void snow_plowSnow(int subcatch, double tStep); -double snow_getSnowMelt(int subcatch, double rainfall, double snowfall, - double tStep, double netPrecip[]); -double snow_getSnowCover(int subcatch); - -//----------------------------------------------------------------------------- -// Runoff Analyzer Methods -//----------------------------------------------------------------------------- -int runoff_open(void); -void runoff_execute(void); -void runoff_close(void); - -//----------------------------------------------------------------------------- -// Conveyance System Routing Methods -//----------------------------------------------------------------------------- -int routing_open(void); -double routing_getRoutingStep(int routingModel, double fixedStep); -void routing_execute(int routingModel, double routingStep); -void routing_close(int routingModel); - -//----------------------------------------------------------------------------- -// Output Filer Methods -//----------------------------------------------------------------------------- -int output_open(void); -void output_end(void); -void output_close(void); -void output_saveResults(double reportTime); -void output_updateAvgResults(void); -void output_readDateTime(long period, DateTime *aDate); -void output_readSubcatchResults(long period, int index); -void output_readNodeResults(int long, int index); -void output_readLinkResults(int long, int index); - -//----------------------------------------------------------------------------- -// Groundwater Methods -//----------------------------------------------------------------------------- -int gwater_readAquiferParams(int aquifer, char* tok[], int ntoks); -int gwater_readGroundwaterParams(char* tok[], int ntoks); -int gwater_readFlowExpression(char* tok[], int ntoks); -void gwater_deleteFlowExpression(int subcatch); - -void gwater_validateAquifer(int aquifer); -void gwater_validate(int subcatch); - -void gwater_initState(int subcatch); -void gwater_getState(int subcatch, double x[]); -void gwater_setState(int subcatch, double x[]); - -void gwater_getGroundwater(int subcatch, double evap, double infil, - double tStep); -double gwater_getVolume(int subcatch); - -//----------------------------------------------------------------------------- -// RDII Methods -//----------------------------------------------------------------------------- -int rdii_readRdiiInflow(char* tok[], int ntoks); -void rdii_deleteRdiiInflow(int node); -void rdii_initUnitHyd(int unitHyd); -int rdii_readUnitHydParams(char* tok[], int ntoks); -void rdii_openRdii(void); -void rdii_closeRdii(void); -int rdii_getNumRdiiFlows(DateTime aDate); -void rdii_getRdiiFlow(int index, int* node, double* q); - -//----------------------------------------------------------------------------- -// Landuse Methods -//----------------------------------------------------------------------------- -int landuse_readParams(int landuse, char* tok[], int ntoks); -int landuse_readPollutParams(int pollut, char* tok[], int ntoks); -int landuse_readBuildupParams(char* tok[], int ntoks); -int landuse_readWashoffParams(char* tok[], int ntoks); - -void landuse_getInitBuildup(TLandFactor* landFactor, double* initBuildup, - double area, double curb); -double landuse_getBuildup(int landuse, int pollut, double area, double curb, - double buildup, double tStep); - -double landuse_getWashoffLoad(int landuse, int p, double area, - TLandFactor landFactor[], double runoff, double vOutflow); -double landuse_getAvgBmpEffic(int j, int p); -double landuse_getCoPollutLoad(int p, double washoff[]); - -//----------------------------------------------------------------------------- -// Flow/Quality Routing Methods -//----------------------------------------------------------------------------- -void flowrout_init(int routingModel); -void flowrout_close(int routingModel); -double flowrout_getRoutingStep(int routingModel, double fixedStep); -int flowrout_execute(int links[], int routingModel, double tStep); - -void toposort_sortLinks(int links[]); -int kinwave_execute(int link, double* qin, double* qout, double tStep); - -void dynwave_validate(void); -void dynwave_init(void); -void dynwave_close(void); -double dynwave_getRoutingStep(double fixedStep); -int dynwave_execute(double tStep); -void dwflow_findConduitFlow(int j, int steps, double omega, double dt); - -void qualrout_init(void); -void qualrout_execute(double tStep); - -//----------------------------------------------------------------------------- -// Treatment Methods -//----------------------------------------------------------------------------- -int treatmnt_open(void); -void treatmnt_close(void); -int treatmnt_readExpression(char* tok[], int ntoks); -void treatmnt_delete(int node); -void treatmnt_treat(int node, double q, double v, double tStep); -void treatmnt_setInflow(double qIn, double wIn[]); - -//----------------------------------------------------------------------------- -// Mass Balance Methods -//----------------------------------------------------------------------------- -int massbal_open(void); -void massbal_close(void); -void massbal_report(void); - -void massbal_updateRunoffTotals(int type, double v); -void massbal_updateLoadingTotals(int type, int pollut, double w); -void massbal_updateGwaterTotals(double vInfil, double vUpperEvap, - double vLowerEvap, double vLowerPerc, double vGwater); -void massbal_updateRoutingTotals(double tStep); - - -void massbal_initTimeStepTotals(void); -void massbal_addInflowFlow(int type, double q); -void massbal_addInflowQual(int type, int pollut, double w); -void massbal_addOutflowFlow(double q, int isFlooded); -void massbal_addOutflowQual(int pollut, double mass, int isFlooded); -void massbal_addNodeLosses(double evapLoss, double infilLoss); -void massbal_addLinkLosses(double evapLoss, double infilLoss); -void massbal_addReactedMass(int pollut, double mass); -void massbal_addSeepageLoss(int pollut, double seepLoss); -void massbal_addToFinalStorage(int pollut, double mass); -double massbal_getStepFlowError(void); -double massbal_getRunoffError(void); -double massbal_getFlowError(void); - -//----------------------------------------------------------------------------- -// Simulation Statistics Methods -//----------------------------------------------------------------------------- -int stats_open(void); -void stats_close(void); -void stats_report(void); - -void stats_updateCriticalTimeCount(int node, int link); -void stats_updateFlowStats(double tStep, DateTime aDate); -void stats_updateTimeStepStats(double tStep, int trialsCount, int steadyState); - -void stats_updateSubcatchStats(int subcatch, double rainVol, - double runonVol, double evapVol, double infilVol, - double impervVol, double pervVol, double runoffVol, double runoff); -void stats_updateGwaterStats(int j, double infil, double evap, - double latFlow, double deepFlow, double theta, double waterTable, - double tStep); -void stats_updateMaxRunoff(void); -void stats_updateMaxNodeDepth(int node, double depth); -void stats_updateConvergenceStats(int node, int converged); - - -//----------------------------------------------------------------------------- -// Raingage Methods -//----------------------------------------------------------------------------- -int gage_readParams(int gage, char* tok[], int ntoks); -void gage_validate(int gage); -void gage_initState(int gage); -void gage_setState(int gage, DateTime aDate); -double gage_getPrecip(int gage, double *rainfall, double *snowfall); -void gage_setReportRainfall(int gage, DateTime aDate); -DateTime gage_getNextRainDate(int gage, DateTime aDate); -void gage_updatePastRain(int j, int tStep); -double gage_getPastRain(int gage, int hrs); - -//----------------------------------------------------------------------------- -// Subcatchment Methods -//----------------------------------------------------------------------------- -int subcatch_readParams(int subcatch, char* tok[], int ntoks); -int subcatch_readSubareaParams(char* tok[], int ntoks); -int subcatch_readLanduseParams(char* tok[], int ntoks); -int subcatch_readInitBuildup(char* tok[], int ntoks); - -void subcatch_validate(int subcatch); -void subcatch_initState(int subcatch); -void subcatch_setOldState(int subcatch); - -double subcatch_getFracPerv(int subcatch); -double subcatch_getStorage(int subcatch); -double subcatch_getDepth(int subcatch); - -void subcatch_getRunon(int subcatch); -void subcatch_addRunonFlow(int subcatch, double flow); -double subcatch_getRunoff(int subcatch, double tStep); - -double subcatch_getWtdOutflow(int subcatch, double wt); -void subcatch_getResults(int subcatch, double wt, float x[]); - -//----------------------------------------------------------------------------- -// Surface Pollutant Buildup/Washoff Methods -//----------------------------------------------------------------------------- -void surfqual_initState(int subcatch); -void surfqual_getWashoff(int subcatch, double runoff, double tStep); -void surfqual_getBuildup(int subcatch, double tStep); -void surfqual_sweepBuildup(int subcatch, DateTime aDate); -double surfqual_getWtdWashoff(int subcatch, int pollut, double wt); - -//----------------------------------------------------------------------------- -// Conveyance System Node Methods -//----------------------------------------------------------------------------- -int node_readParams(int node, int type, int subIndex, char* tok[], int ntoks); -void node_validate(int node); - -void node_initState(int node); -void node_initFlows(int node, double tStep); -void node_setOldHydState(int node); -void node_setOldQualState(int node); -void node_setOutletDepth(int node, double yNorm, double yCrit, double z); - -double node_getSurfArea(int node, double depth); -double node_getDepth(int node, double volume); -double node_getVolume(int node, double depth); -double node_getPondedArea(int node, double depth); - -double node_getOutflow(int node, int link); -double node_getLosses(int node, double tStep); -double node_getMaxOutflow(int node, double q, double tStep); -double node_getSystemOutflow(int node, int *isFlooded); -void node_getResults(int node, double wt, float x[]); - -//----------------------------------------------------------------------------- -// Conveyance System Inflow Methods -//----------------------------------------------------------------------------- -int inflow_readExtInflow(char* tok[], int ntoks); -int inflow_readDwfInflow(char* tok[], int ntoks); -int inflow_readDwfPattern(char* tok[], int ntoks); -int inflow_setExtInflow(int j, int param, int type, int tSeries, - int basePat, double cf, double baseline, double sf); - -void inflow_initDwfInflow(TDwfInflow* inflow); -void inflow_initDwfPattern(int pattern); - -double inflow_getExtInflow(TExtInflow* inflow, DateTime aDate); -double inflow_getDwfInflow(TDwfInflow* inflow, int m, int d, int h); - -void inflow_deleteExtInflows(int node); -void inflow_deleteDwfInflows(int node); - -//----------------------------------------------------------------------------- -// Routing Interface File Methods -//----------------------------------------------------------------------------- -int iface_readFileParams(char* tok[], int ntoks); -void iface_openRoutingFiles(void); -void iface_closeRoutingFiles(void); -int iface_getNumIfaceNodes(DateTime aDate); -int iface_getIfaceNode(int index); -double iface_getIfaceFlow(int index); -double iface_getIfaceQual(int index, int pollut); -void iface_saveOutletResults(DateTime reportDate, FILE* file); - -//----------------------------------------------------------------------------- -// Hot Start File Methods -//----------------------------------------------------------------------------- -int hotstart_open(void); -void hotstart_close(void); - -//----------------------------------------------------------------------------- -// Conveyance System Link Methods -//----------------------------------------------------------------------------- -int link_readParams(int link, int type, int subIndex, char* tok[], int ntoks); -int link_readXsectParams(char* tok[], int ntoks); -int link_readLossParams(char* tok[], int ntoks); - -void link_validate(int link); -void link_initState(int link); -void link_setOldHydState(int link); -void link_setOldQualState(int link); - -void link_setTargetSetting(int j); -void link_setSetting(int j, double tstep); -int link_setFlapGate(int link, int n1, int n2, double q); - -double link_getInflow(int link); -void link_setOutfallDepth(int link); -double link_getLength(int link); -double link_getYcrit(int link, double q); -double link_getYnorm(int link, double q); -double link_getVelocity(int link, double q, double y); -double link_getFroude(int link, double v, double y); -double link_getPower(int link); -double link_getLossRate(int link, double q); -char link_getFullState(double a1, double a2, double aFull); - -void link_getResults(int link, double wt, float x[]); - -//----------------------------------------------------------------------------- -// Link Cross-Section Methods -//----------------------------------------------------------------------------- -int xsect_isOpen(int type); -int xsect_setParams(TXsect *xsect, int type, double p[], double ucf); -void xsect_setIrregXsectParams(TXsect *xsect); -void xsect_setCustomXsectParams(TXsect *xsect); -void xsect_setStreetXsectParams(TXsect *xsect); -double xsect_getAmax(TXsect* xsect); - -double xsect_getSofA(TXsect* xsect, double area); -double xsect_getYofA(TXsect* xsect, double area); -double xsect_getRofA(TXsect* xsect, double area); -double xsect_getAofS(TXsect* xsect, double sFactor); -double xsect_getdSdA(TXsect* xsect, double area); -double xsect_getAofY(TXsect* xsect, double y); -double xsect_getRofY(TXsect* xsect, double y); -double xsect_getWofY(TXsect* xsect, double y); -double xsect_getYcrit(TXsect* xsect, double q); - -//----------------------------------------------------------------------------- -// Culvert/Roadway Methods -//----------------------------------------------------------------------------- -double culvert_getInflow(int link, double q, double h); -double roadway_getInflow(int link, double dir, double hcrest, double h1, - double h2); - -//----------------------------------------------------------------------------- -// Force Main Methods -//----------------------------------------------------------------------------- -double forcemain_getEquivN(int j, int k); -double forcemain_getRoughFactor(int j, double lengthFactor); -double forcemain_getFricSlope(int j, double v, double hrad); - -//----------------------------------------------------------------------------- -// Cross-Section Transect Methods -//----------------------------------------------------------------------------- -int transect_create(int n); -void transect_delete(void); -int transect_readParams(int* count, char* tok[], int ntoks); -void transect_validate(int j); -void transect_createStreetTransect(TStreet* street); - -//----------------------------------------------------------------------------- -// Street Cross-Section Methods -//----------------------------------------------------------------------------- -int street_create(int nStreets); -void street_delete(); -int street_readParams(char* tok[], int ntoks); -double street_getExtentFilled(int link); - -//----------------------------------------------------------------------------- -// Custom Shape Cross-Section Methods -//----------------------------------------------------------------------------- -int shape_validate(TShape *shape, TTable *curve); - -//----------------------------------------------------------------------------- -// Control Rule Methods -//----------------------------------------------------------------------------- -int controls_create(int n); -void controls_delete(void); -void controls_init(void); -void controls_addToCount(char* s); -int controls_addVariable(char* tok[], int ntoks); -int controls_addExpression(char* tok[], int ntoks); -int controls_addRuleClause(int rule, int keyword, char* Tok[], int nTokens); -int controls_evaluate(DateTime currentTime, DateTime elapsedTime, - double tStep); - -//----------------------------------------------------------------------------- -// Table & Time Series Methods -//----------------------------------------------------------------------------- -int table_readCurve(char* tok[], int ntoks); -int table_readTimeseries(char* tok[], int ntoks); - -int table_addEntry(TTable* table, double x, double y); -int table_getFirstEntry(TTable* table, double* x, double* y); -int table_getNextEntry(TTable* table, double* x, double* y); -void table_deleteEntries(TTable* table); - -void table_init(TTable* table); -int table_validate(TTable* table); - -double table_lookup(TTable* table, double x); -double table_lookupEx(TTable* table, double x); -double table_intervalLookup(TTable* table, double x); -double table_inverseLookup(TTable* table, double y); - -double table_getSlope(TTable *table, double x); -double table_getMaxY(TTable *table, double x); -double table_getStorageVolume(TTable* table, double x); -double table_getStorageDepth(TTable* table, double v); - -void table_tseriesInit(TTable *table); -double table_tseriesLookup(TTable* table, double t, char extend); - -//----------------------------------------------------------------------------- -// Utility Methods -//----------------------------------------------------------------------------- -double UCF(int quantity); // units conversion factor -int getInt(char *s, int *y); // get integer from string -int getFloat(char *s, float *y); // get float from string -int getDouble(char *s, double *y); // get double from string -char* getTempFileName(char *s); // get temporary file name -int findmatch(char *s, char *keyword[]); // search for matching keyword -int match(char *str, char *substr); // true if substr matches part of str -int strcomp(const char *s1, const char *s2); // case insensitive string compare -size_t sstrncpy(char *dest, const char *src, - size_t n); // safe string copy -size_t sstrcat(char* dest, const char* src, - size_t destsize); // safe string concatenation -void writecon(const char *s); // writes string to console -DateTime getDateTime(double elapsedMsec); // convert elapsed time to date -void getElapsedTime(DateTime aDate, // convert elapsed date - int* days, int* hrs, int* mins); -char* addAbsolutePath(char *fname); // add full path to a file name - -#endif //FUNCS_H diff --git a/src/gage.c b/src/gage.c deleted file mode 100644 index 031cc017c..000000000 --- a/src/gage.c +++ /dev/null @@ -1,705 +0,0 @@ -//----------------------------------------------------------------------------- -// gage.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Rain gage functions. -// -// Update History -// ============== -// Build 5.1.007: -// - Support for monthly rainfall adjustments added. -// Build 5.1.013: -// - Validation no longer performed on unused gages. -// Build 5.2.0: -// - Support added for tracking a gage's prior n-hour rainfall total. -// - Support added for relative file names. -// - Support added for setting rainfall through API call. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -const double OneSecond = 1.1574074e-5; - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// gage_readParams (called by input_readLine) -// gage_validate (called by project_validate) -// gage_initState (called by project_init) -// gage_setState (called by runoff_execute & getRainfall in rdii.c) -// gage_getPrecip (called by subcatch_getRunoff) -// gage_getNextRainDate (called by runoff_getTimeStep) -// gage_updatePastRain (called by runoff_execute) -// gage_getPastRain (called by getRainValue in controls.c) -// gage_setReportRainfall (called by output_saveSubcatchResults) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int readGageSeriesFormat(char* tok[], int ntoks, double x[]); -static int readGageFileFormat(char* tok[], int ntoks, double x[]); -static int getFirstRainfall(int gage); -static int getNextRainfall(int gage); -static double convertRainfall(int gage, double rain); -static void initPastRain(int gage); - -//============================================================================= - -int gage_readParams(int j, char* tok[], int ntoks) -// -// Input: j = rain gage index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads rain gage parameters from a line of input data -// -// Data formats are: -// Name RainType RecdFreq SCF TIMESERIES SeriesName -// Name RainType RecdFreq SCF FILE FileName Station Units StartDate -// -{ - int k, err; - char *id; - char fname[MAXFNAME+1]; - char staID[MAXMSG+1]; - double x[7]; - - // --- check that gage exists - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(GAGE, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - - // --- assign default parameter values - x[0] = -1.0; // No time series index - x[1] = 1.0; // Rain type is volume - x[2] = 3600.0; // Recording freq. is 3600 sec - x[3] = 1.0; // Snow catch deficiency factor - x[4] = NO_DATE; // Default is no start/end date - x[5] = NO_DATE; - x[6] = 0.0; // US units - fname[0] = '\0'; - staID[0] = '\0'; - - if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); - k = findmatch(tok[4], GageDataWords); - if ( k == RAIN_TSERIES ) - { - err = readGageSeriesFormat(tok, ntoks, x); - } - else if ( k == RAIN_FILE ) - { - if ( ntoks < 8 ) return error_setInpError(ERR_ITEMS, ""); - sstrncpy(fname, tok[5], MAXFNAME); - sstrncpy(staID, tok[6], MAXMSG); - err = readGageFileFormat(tok, ntoks, x); - } - else return error_setInpError(ERR_KEYWORD, tok[4]); - - // --- save parameters to rain gage object - if ( err > 0 ) return err; - Gage[j].ID = id; - Gage[j].tSeries = (int)x[0]; - Gage[j].rainType = (int)x[1]; - Gage[j].rainInterval = (int)x[2]; - Gage[j].snowFactor = x[3]; - Gage[j].rainUnits = (int)x[6]; - if ( Gage[j].tSeries >= 0 ) Gage[j].dataSource = RAIN_TSERIES; - else Gage[j].dataSource = RAIN_FILE; - if ( Gage[j].dataSource == RAIN_FILE ) - { - sstrncpy(Gage[j].fname, addAbsolutePath(fname), MAXFNAME); - sstrncpy(Gage[j].staID, staID, MAXMSG); - Gage[j].startFileDate = x[4]; - Gage[j].endFileDate = x[5]; - } - Gage[j].unitsFactor = 1.0; - Gage[j].coGage = -1; - Gage[j].isUsed = FALSE; - return 0; -} - -//============================================================================= - -int readGageSeriesFormat(char* tok[], int ntoks, double x[]) -{ - int m, ts; - DateTime aTime; - - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - - // --- determine type of rain data - m = findmatch(tok[1], RainTypeWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - x[1] = (double)m; - - // --- get data time interval & convert to seconds - if ( getDouble(tok[2], &x[2]) ) x[2] = floor(x[2]*3600 + 0.5); - else if ( datetime_strToTime(tok[2], &aTime) ) - { - x[2] = floor(aTime*SECperDAY + 0.5); - } - else return error_setInpError(ERR_DATETIME, tok[2]); - if ( x[2] <= 0.0 ) return error_setInpError(ERR_DATETIME, tok[2]); - - // --- get snow catch deficiency factor - if ( !getDouble(tok[3], &x[3]) ) - return error_setInpError(ERR_DATETIME, tok[3]);; - - // --- get time series index - ts = project_findObject(TSERIES, tok[5]); - if ( ts < 0 ) return error_setInpError(ERR_NAME, tok[5]); - x[0] = (double)ts; - sstrncpy(tok[2], "", 0); - return 0; -} - -//============================================================================= - -int readGageFileFormat(char* tok[], int ntoks, double x[]) -{ - int m, u; - DateTime aDate; - DateTime aTime; - - // --- determine type of rain data - m = findmatch(tok[1], RainTypeWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - x[1] = (double)m; - - // --- get data time interval & convert to seconds - if ( getDouble(tok[2], &x[2]) ) x[2] *= 3600; - else if ( datetime_strToTime(tok[2], &aTime) ) - { - x[2] = floor(aTime*SECperDAY + 0.5); - } - else return error_setInpError(ERR_DATETIME, tok[2]); - if ( x[2] <= 0.0 ) return error_setInpError(ERR_DATETIME, tok[2]); - - // --- get snow catch deficiency factor - if ( !getDouble(tok[3], &x[3]) ) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- get rain depth units - u = findmatch(tok[7], RainUnitsWords); - if ( u < 0 ) return error_setInpError(ERR_KEYWORD, tok[7]); - x[6] = (double)u; - - // --- get start date (if present) - if ( ntoks > 8 && *tok[8] != '*') - { - if ( !datetime_strToDate(tok[8], &aDate) ) - return error_setInpError(ERR_DATETIME, tok[8]); - x[4] = (float) aDate; - } - return 0; -} - -//============================================================================= - -void gage_validate(int j) -// -// Input: j = rain gage index -// Output: none -// Purpose: checks for valid rain gage parameters -// -// NOTE: assumes that any time series used by a rain gage has been -// previously validated. -// -{ - int i, k; - int gageInterval; - - // --- for gage with time series data: - if ( Gage[j].dataSource == RAIN_TSERIES ) - { - // --- no validation for an unused gage - if ( !Gage[j].isUsed ) return; - - // --- see if gage uses same time series as another gage - k = Gage[j].tSeries; - for (i=0; i= 0 ) - { - report_writeErrorMsg(ERR_RAIN_GAGE_TSERIES, Gage[j].ID); - } - gageInterval = (int)(floor(Tseries[k].dxMin*SECperDAY + 0.5)); - if ( gageInterval > 0 && Gage[j].rainInterval > gageInterval ) - { - report_writeErrorMsg(ERR_RAIN_GAGE_INTERVAL, Gage[j].ID); - } - if ( Gage[j].rainInterval < gageInterval ) - { - report_writeWarningMsg(WARN09, Gage[j].ID); - } - if ( Gage[j].rainInterval < WetStep ) - { - report_writeWarningMsg(WARN01, Gage[j].ID); - WetStep = Gage[j].rainInterval; - } - } -} - -//============================================================================= - -void gage_initState(int j) -// -// Input: j = rain gage index -// Output: none -// Purpose: initializes state of rain gage. -// -{ - // --- initialize actual and reported rainfall - Gage[j].rainfall = 0.0; - Gage[j].apiRainfall = MISSING; - Gage[j].reportRainfall = 0.0; - if ( IgnoreRainfall ) return; - - // --- for gage with file data: - if ( Gage[j].dataSource == RAIN_FILE ) - { - // --- set current file position to start of period of record - Gage[j].currentFilePos = Gage[j].startFilePos; - - // --- assign units conversion factor - // (rain depths on interface file are in inches) - if ( UnitSystem == SI ) Gage[j].unitsFactor = MMperINCH; - } - - // --- get first & next rainfall values - if ( getFirstRainfall(j) ) - { - // --- find date at end of starting rain interval - Gage[j].endDate = datetime_addSeconds( - Gage[j].startDate, Gage[j].rainInterval); - - // --- if rainfall record begins after start of simulation, - if ( Gage[j].startDate > StartDateTime ) - { - // --- make next rainfall date the start of the rain record - Gage[j].nextDate = Gage[j].startDate; - Gage[j].nextRainfall = Gage[j].rainfall; - - // --- make start of current rain interval the simulation start - Gage[j].startDate = StartDateTime; - Gage[j].endDate = Gage[j].nextDate; - Gage[j].rainfall = 0.0; - } - - // --- otherwise find next recorded rainfall - else if ( !getNextRainfall(j) ) Gage[j].nextDate = NO_DATE; - } - else Gage[j].startDate = NO_DATE; - initPastRain(j); -} - -//============================================================================= - -void gage_setState(int j, DateTime t) -// -// Input: j = rain gage index -// t = a calendar date/time -// Output: none -// Purpose: updates state of rain gage for specified date. -// -{ - // --- return if gage not used by any subcatchment - if ( Gage[j].isUsed == FALSE ) return; - - // --- set rainfall to zero if disabled - if ( IgnoreRainfall ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- use rainfall from co-gage (gage with lower index that uses - // same rainfall time series or file) if it exists - if ( Gage[j].coGage >= 0) - { - Gage[j].rainfall = Gage[Gage[j].coGage].rainfall; - return; - } - - // --- use rainfall supplied by API function call - // (where constant ZERO (1.e-10) is used for 0 rainfall) - if (Gage[j].apiRainfall != MISSING) - { - Gage[j].rainfall = Gage[j].apiRainfall; - return; - } - - // --- otherwise march through rainfall record until date t is bracketed - t += OneSecond; - for (;;) - { - // --- no rainfall if no interval start date - if ( Gage[j].startDate == NO_DATE ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- no rainfall if time is before interval start date - if ( t < Gage[j].startDate ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- use current rainfall if time is before interval end date - if ( t < Gage[j].endDate ) - { - return; - } - - // --- no rainfall if t >= interval end date & no next interval exists - if ( Gage[j].nextDate == NO_DATE ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- no rainfall if t > interval end date & < next interval date - if ( t < Gage[j].nextDate ) - { - Gage[j].rainfall = 0.0; - return; - } - - // --- otherwise update next rainfall interval date - Gage[j].startDate = Gage[j].nextDate; - Gage[j].endDate = datetime_addSeconds(Gage[j].startDate, - Gage[j].rainInterval); - Gage[j].rainfall = Gage[j].nextRainfall; - if ( !getNextRainfall(j) ) Gage[j].nextDate = NO_DATE; - } -} - -//============================================================================= - -void initPastRain(int j) -{ - // --- initialize past hourly rain accumulation - int i; - for (i = 0; i <= MAXPASTRAIN; i++) - Gage[j].pastRain[i] = 0.0; - Gage[j].pastInterval = 0; -} - -//============================================================================= - -void gage_updatePastRain(int j, int tStep) -// -// Input: j = rain gage index -// tStep = current runoff time step (sec) -// Output: none -// Purpose: updates past MAXPASTRAIN hourly rain totals. -// -// Note: pastRain[0] is past rain volume prior to 1 hour, -// pastRain[n] is past rain volume after n hours, -// pastInterval is time since last hour was reached. -{ - int i, t; - double r; - - // --- current rainfall intensity (in/sec or mm/sec) - r = Gage[j].rainfall / 3600.; - - // --- process each hourly interval of current time step - while (tStep > 0) - { - // --- time for most recent rainfall interval to reach 1 hr - t = 3600 - Gage[j].pastInterval; - - // --- remaining time step is greater than this time - if (tStep > t) - { - // --- add current rain to most recent interval - Gage[j].pastRain[0] += t * r; - - // --- shift all prior hourly rain amounts by 1 hour - for (i = MAXPASTRAIN; i > 0; i-- ) - Gage[j].pastRain[i] = Gage[j].pastRain[i-1]; - - // --- begin a new most recent interval - Gage[j].pastInterval = 0; - Gage[j].pastRain[0] = 0.0; - tStep -= t; - } - // --- time to reach 1 hr in most recent interval is greater - // than remaining time step so update most recent interval - else - { - Gage[j].pastRain[0] += tStep * r; - Gage[j].pastInterval += tStep; - tStep = 0; - } - } -} - -//============================================================================= - -double gage_getPastRain(int j, int n) -// -// Input: j = rain gage index -// n = number of hours prior to current date -// Output: cumulative rain volume (inches or mm) in last n hours -// Purpose: retrieves rainfall total over some previous number of hours. -// -{ - int i; - double result = 0.0; - if (n < 1 || n > MAXPASTRAIN) return 0.0; - for (i = 1; i <= n; i++) - result += Gage[j].pastRain[i]; - return result; -} - -//============================================================================= - -DateTime gage_getNextRainDate(int j, DateTime aDate) -// -// Input: j = rain gage index -// aDate = calendar date/time -// Output: next date with rainfall occurring -// Purpose: finds the next date from specified date when rainfall occurs. -// -{ - if ( Gage[j].isUsed == FALSE ) return aDate; - aDate += OneSecond; - if ( aDate < Gage[j].startDate ) return Gage[j].startDate; - if ( aDate < Gage[j].endDate ) return Gage[j].endDate; - return Gage[j].nextDate; -} - -//============================================================================= - -double gage_getPrecip(int j, double *rainfall, double *snowfall) -// -// Input: j = rain gage index -// Output: rainfall = rainfall rate (ft/sec) -// snowfall = snow fall rate (ft/sec) -// returns total precipitation (ft/sec) -// Purpose: determines whether gage's recorded rainfall is rain or snow. -// -{ - *rainfall = 0.0; - *snowfall = 0.0; - if ( !IgnoreSnowmelt && Temp.ta <= Snow.snotmp ) - { - *snowfall = Gage[j].rainfall * Gage[j].snowFactor / UCF(RAINFALL); - } - else *rainfall = Gage[j].rainfall / UCF(RAINFALL); - return (*rainfall) + (*snowfall); -} - -//============================================================================= - -void gage_setReportRainfall(int j, DateTime reportDate) -// -// Input: j = rain gage index -// reportDate = date/time value of current reporting time -// Output: none -// Purpose: sets the rainfall value reported at the current reporting time. -// -{ - double result; - - // --- use value from co-gage if it exists - if ( Gage[j].coGage >= 0) - { - Gage[j].reportRainfall = Gage[Gage[j].coGage].reportRainfall; - return; - } - - // --- rainfall set by API call - if (Gage[j].apiRainfall != MISSING) - { - Gage[j].reportRainfall = Gage[j].apiRainfall; - return; - } - - // --- otherwise increase reporting time by 1 second to avoid - // roundoff problems - reportDate += OneSecond; - - // --- use current rainfall if report date/time is before end - // of current rain interval - if ( reportDate < Gage[j].endDate ) result = Gage[j].rainfall; - - // --- use 0.0 if report date/time is before start of next rain interval - else if ( reportDate < Gage[j].nextDate ) result = 0.0; - - // --- otherwise report date/time falls right on end of current rain - // interval and start of next interval so use next interval's rainfall - else result = Gage[j].nextRainfall; - Gage[j].reportRainfall = result; -} - -//============================================================================= - -int getFirstRainfall(int j) -// -// Input: j = rain gage index -// Output: returns TRUE if successful -// Purpose: positions rainfall record to date with first rainfall. -// -{ - int k; // time series index - float vFirst; // first rain volume (ft or m) - double rFirst; // first rain intensity (in/hr or mm/hr) - - // --- assign default values to date & rainfall - Gage[j].startDate = NO_DATE; - Gage[j].rainfall = 0.0; - - // --- initialize internal cumulative rainfall value - Gage[j].rainAccum = 0; - - // --- use rain interface file if applicable - if ( Gage[j].dataSource == RAIN_FILE ) - { - if ( Frain.file && Gage[j].endFilePos > Gage[j].startFilePos ) - { - // --- retrieve 1st date & rainfall volume from file - fseek(Frain.file, Gage[j].startFilePos, SEEK_SET); - fread(&Gage[j].startDate, sizeof(DateTime), 1, Frain.file); - fread(&vFirst, sizeof(float), 1, Frain.file); - Gage[j].currentFilePos = ftell(Frain.file); - - // --- convert rainfall to intensity - Gage[j].rainfall = convertRainfall(j, (double)vFirst); - return 1; - } - return 0; - } - - // --- otherwise access user-supplied rainfall time series - else - { - k = Gage[j].tSeries; - if ( k >= 0 ) - { - // --- retrieve first rainfall value from time series - if ( table_getFirstEntry(&Tseries[k], &Gage[j].startDate, - &rFirst) ) - { - // --- convert rainfall to intensity - Gage[j].rainfall = convertRainfall(j, rFirst); - return 1; - } - } - return 0; - } -} - -//============================================================================= - -int getNextRainfall(int j) -// -// Input: j = rain gage index -// Output: returns 1 if successful; 0 if not -// Purpose: positions rainfall record to date with next non-zero rainfall -// while updating the gage's next rain intensity value. -// -// Note: zero rainfall values explicitly entered into a rain file or -// time series are skipped over so that a proper accounting of -// wet and dry periods can be maintained. -// -{ - int k; // time series index - float vNext; // next rain volume (ft or m) - double rNext; // next rain intensity (in/hr or mm/hr) - - Gage[j].nextRainfall = 0.0; - do - { - if ( Gage[j].dataSource == RAIN_FILE ) - { - if ( Frain.file && Gage[j].currentFilePos < Gage[j].endFilePos ) - { - fseek(Frain.file, Gage[j].currentFilePos, SEEK_SET); - fread(&Gage[j].nextDate, sizeof(DateTime), 1, Frain.file); - fread(&vNext, sizeof(float), 1, Frain.file); - Gage[j].currentFilePos = ftell(Frain.file); - rNext = convertRainfall(j, (double)vNext); - } - else return 0; - } - - else - { - k = Gage[j].tSeries; - if ( k >= 0 ) - { - if ( !table_getNextEntry(&Tseries[k], - &Gage[j].nextDate, &rNext) ) return 0; - rNext = convertRainfall(j, rNext); - } - else return 0; - } - } while (rNext == 0.0); - Gage[j].nextRainfall = rNext; - return 1; -} - -//============================================================================= - -double convertRainfall(int j, double r) -// -// Input: j = rain gage index -// r = rainfall value (user units) -// Output: returns rainfall intensity (user units) -// Purpose: converts rainfall value to an intensity (depth per hour). -// -{ - double r1; - switch ( Gage[j].rainType ) - { - case RAINFALL_INTENSITY: - r1 = r; - break; - - case RAINFALL_VOLUME: - r1 = r / Gage[j].rainInterval * 3600.0; - break; - - case CUMULATIVE_RAINFALL: - if ( r < Gage[j].rainAccum ) - r1 = r / Gage[j].rainInterval * 3600.0; - else r1 = (r - Gage[j].rainAccum) / Gage[j].rainInterval * 3600.0; - Gage[j].rainAccum = r; - break; - - default: r1 = r; - } - return r1 * Gage[j].unitsFactor * Adjust.rainFactor; -} - -//============================================================================= diff --git a/src/globals.h b/src/globals.h deleted file mode 100644 index 70f2431aa..000000000 --- a/src/globals.h +++ /dev/null @@ -1,172 +0,0 @@ -//----------------------------------------------------------------------------- -// globals.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Global Variables -// -// Update History -// ============== -// Build 5.1.004: -// - Ignore RDII option added. -// Build 5.1.007: -// - Monthly climate variable adjustments added. -// Build 5.1.008: -// - Number of parallel threads for dynamic wave routing added. -// - Minimum dynamic wave routing variable time step added. -// Build 5.1.011: -// - Changed WarningCode to Warnings (# warnings issued) -// - Added error message text as a variable. -// - Added elapsed simulation time (in decimal days) variable. -// - Added variables associated with detailed routing events. -// Build 5.1.012: -// - InSteadyState variable made local to routing_execute in routing.c. -// Build 5.1.013: -// - CrownCutoff and RuleStep added as analysis option variables. -// Build 5.1.015: -// - Fixes bug in summary statistics when Report Start date > Start Date. -// Build 5.2.0: -// - Support for relative file names added. -//----------------------------------------------------------------------------- - -#ifndef GLOBALS_H -#define GLOBALS_H - - -EXTERN TFile - Finp, // Input file - Fout, // Output file - Frpt, // Report file - Fclimate, // Climate file - Frain, // Rainfall file - Frunoff, // Runoff file - Frdii, // RDII inflow file - Fhotstart1, // Hot start input file - Fhotstart2, // Hot start output file - Finflows, // Inflows routing file - Foutflows; // Outflows routing file - -EXTERN long - Nperiods, // Number of reporting periods - TotalStepCount, // Total routing steps used - ReportStepCount, // Reporting routing steps used - NonConvergeCount; // Number of non-converging steps - -EXTERN char - Msg[MAXMSG+1], // Text of output message - ErrorMsg[MAXMSG+1], // Text of error message - Title[MAXTITLE][MAXMSG+1],// Project title - TempDir[MAXFNAME+1], // Temporary file directory - InpDir[MAXFNAME+1]; // Input file directory - -EXTERN TRptFlags - RptFlags; // Reporting options - -EXTERN int - Nobjects[MAX_OBJ_TYPES], // Number of each object type - Nnodes[MAX_NODE_TYPES], // Number of each node sub-type - Nlinks[MAX_LINK_TYPES], // Number of each link sub-type - UnitSystem, // Unit system - FlowUnits, // Flow units - InfilModel, // Infiltration method - RouteModel, // Flow routing method - ForceMainEqn, // Flow equation for force mains - LinkOffsets, // Link offset convention - SurchargeMethod, // EXTRAN or SLOT method - AllowPonding, // Allow water to pond at nodes - InertDamping, // Degree of inertial damping - NormalFlowLtd, // Normal flow limited - SlopeWeighting, // Use slope weighting - Compatibility, // SWMM 5/3/4 compatibility - SkipSteadyState, // Skip over steady state periods - IgnoreRainfall, // Ignore rainfall/runoff - IgnoreRDII, // Ignore RDII - IgnoreSnowmelt, // Ignore snowmelt - IgnoreGwater, // Ignore groundwater - IgnoreRouting, // Ignore flow routing - IgnoreQuality, // Ignore water quality - ErrorCode, // Error code number - Warnings, // Number of warning messages - WetStep, // Runoff wet time step (sec) - DryStep, // Runoff dry time step (sec) - ReportStep, // Reporting time step (sec) - RuleStep, // Rule evaluation time step (sec) - SweepStart, // Day of year when sweeping starts - SweepEnd, // Day of year when sweeping ends - MaxTrials, // Max. trials for DW routing - NumThreads, // Number of parallel threads used - NumEvents; // Number of detailed events - -EXTERN double - RouteStep, // Routing time step (sec) - MinRouteStep, // Minimum variable time step (sec) - LengtheningStep, // Time step for lengthening (sec) - StartDryDays, // Antecedent dry days - CourantFactor, // Courant time step factor - MinSurfArea, // Minimum nodal surface area - MinSlope, // Minimum conduit slope - RunoffError, // Runoff continuity error - GwaterError, // Groundwater continuity error - FlowError, // Flow routing error - QualError, // Quality routing error - HeadTol, // DW routing head tolerance (ft) - SysFlowTol, // Tolerance for steady system flow - LatFlowTol, // Tolerance for steady nodal inflow - CrownCutoff; // Fractional pipe crown cutoff - -EXTERN DateTime - StartDate, // Starting date - StartTime, // Starting time - StartDateTime, // Starting Date+Time - EndDate, // Ending date - EndTime, // Ending time - EndDateTime, // Ending Date+Time - ReportStartDate, // Report start date - ReportStartTime, // Report start time - ReportStart; // Report start Date+Time - -EXTERN double - ReportTime, // Current reporting time (msec) - OldRunoffTime, // Previous runoff time (msec) - NewRunoffTime, // Current runoff time (msec) - OldRoutingTime, // Previous routing time (msec) - NewRoutingTime, // Current routing time (msec) - TotalDuration, // Simulation duration (msec) - ElapsedTime; // Current elapsed time (days) - -EXTERN TTemp Temp; // Temperature data -EXTERN TEvap Evap; // Evaporation data -EXTERN TWind Wind; // Wind speed data -EXTERN TSnow Snow; // Snow melt data -EXTERN TAdjust Adjust; // Climate adjustments - -EXTERN TSnowmelt* Snowmelt; // Array of snow melt objects -EXTERN TGage* Gage; // Array of rain gages -EXTERN TSubcatch* Subcatch; // Array of subcatchments -EXTERN TAquifer* Aquifer; // Array of groundwater aquifers -EXTERN TUnitHyd* UnitHyd; // Array of unit hydrographs -EXTERN TNode* Node; // Array of nodes -EXTERN TOutfall* Outfall; // Array of outfall nodes -EXTERN TDivider* Divider; // Array of divider nodes -EXTERN TStorage* Storage; // Array of storage nodes -EXTERN TLink* Link; // Array of links -EXTERN TConduit* Conduit; // Array of conduit links -EXTERN TPump* Pump; // Array of pump links -EXTERN TOrifice* Orifice; // Array of orifice links -EXTERN TWeir* Weir; // Array of weir links -EXTERN TOutlet* Outlet; // Array of outlet device links -EXTERN TPollut* Pollut; // Array of pollutants -EXTERN TLanduse* Landuse; // Array of landuses -EXTERN TPattern* Pattern; // Array of time patterns -EXTERN TTable* Curve; // Array of curve tables -EXTERN TTable* Tseries; // Array of time series tables -EXTERN TTransect* Transect; // Array of transect data -EXTERN TStreet* Street; // Array of defined Street cross-sections -EXTERN TShape* Shape; // Array of custom conduit shapes -EXTERN TEvent* Event; // Array of routing events - - -#endif //GLOBALS_H diff --git a/src/gwater.c b/src/gwater.c deleted file mode 100644 index 48db9de17..000000000 --- a/src/gwater.c +++ /dev/null @@ -1,872 +0,0 @@ -//----------------------------------------------------------------------------- -// gwater.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Groundwater functions. -// -// Update History -// ============== -// Build 5.1.007: -// - User-supplied function for deep GW seepage flow added. -// - New variable names for use in user-supplied GW flow equations added. -// Build 5.1.008: -// - More variable names for user-supplied GW flow equations added. -// - Subcatchment area made into a shared variable. -// - Evaporation loss initialized to 0. -// - Support for collecting GW statistics added. -// Build 5.1.010: -// - Unsaturated hydraulic conductivity added to GW flow equation variables. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" -#include "odesolve.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double GWTOL = 0.0001; // ODE solver tolerance -static const double XTOL = 0.001; // tolerance for moisture & depth - -enum GWstates {THETA, // moisture content of upper GW zone - LOWERDEPTH}; // depth of lower saturated GW zone - -enum GWvariables { - gwvHGW, // water table height (ft) - gwvHSW, // surface water height (ft) - gwvHCB, // channel bottom height (ft) - gwvHGS, // ground surface height (ft) - gwvKS, // sat. hyd. condutivity (ft/s) - gwvK, // unsat. hyd. conductivity (ft/s) - gwvTHETA, // upper zone moisture content - gwvPHI, // soil porosity - gwvFI, // surface infiltration (ft/s) - gwvFU, // uper zone percolation rate (ft/s) - gwvA, // subcatchment area (ft2) - gwvMAX}; - -// Names of GW variables that can be used in GW outflow expression -static char* GWVarWords[] = {"HGW", "HSW", "HCB", "HGS", "KS", "K", - "THETA", "PHI", "FI", "FU", "A", NULL}; - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -// NOTE: all flux rates are in ft/sec, all depths are in ft. -static double Area; // subcatchment area (ft2) -static double Infil; // infiltration rate from surface -static double MaxEvap; // max. evaporation rate -static double AvailEvap; // available evaporation rate -static double UpperEvap; // evaporation rate from upper GW zone -static double LowerEvap; // evaporation rate from lower GW zone -static double UpperPerc; // percolation rate from upper to lower zone -static double LowerLoss; // loss rate from lower GW zone -static double GWFlow; // flow rate from lower zone to conveyance node -static double MaxUpperPerc; // upper limit on UpperPerc -static double MaxGWFlowPos; // upper limit on GWFlow when its positve -static double MaxGWFlowNeg; // upper limit on GWFlow when its negative -static double FracPerv; // fraction of surface that is pervious -static double TotalDepth; // total depth of GW aquifer -static double Theta; // moisture content of upper zone -static double HydCon; // unsaturated hydraulic conductivity (ft/s) -static double Hgw; // ht. of saturated zone -static double Hstar; // ht. from aquifer bottom to node invert -static double Hsw; // ht. from aquifer bottom to water surface -static double Tstep; // current time step (sec) -static TAquifer A; // aquifer being analyzed -static TGroundwater* GW; // groundwater object being analyzed -static MathExpr* LatFlowExpr; // user-supplied lateral GW flow expression -static MathExpr* DeepFlowExpr; // user-supplied deep GW flow expression - -//----------------------------------------------------------------------------- -// External Functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// gwater_readAquiferParams (called by input_readLine) -// gwater_readGroundwaterParams (called by input_readLine) -// gwater_readFlowExpression (called by input_readLine) -// gwater_deleteFlowExpression (called by deleteObjects in project.c) -// gwater_validateAquifer (called by swmm_open) -// gwater_validate (called by subcatch_validate) -// gwater_initState (called by subcatch_initState) -// gwater_getVolume (called by massbal_open & massbal_getGwaterError) -// gwater_getGroundwater (called by getSubareaRunoff in subcatch.c) -// gwater_getState (called by saveRunoff in hotstart.c) -// gwater_setState (called by readRunoff in hotstart.c) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static void getDxDt(double t, double* x, double* dxdt); -static void getFluxes(double upperVolume, double lowerDepth); -static void getEvapRates(double theta, double upperDepth); -static double getUpperPerc(double theta, double upperDepth); -static double getGWFlow(double lowerDepth); -static void updateMassBal(double area, double tStep); - -// Used to process custom GW outflow equations -static int getVariableIndex(char* s); -static double getVariableValue(int varIndex); - -//============================================================================= - -int gwater_readAquiferParams(int j, char* tok[], int ntoks) -// -// Input: j = aquifer index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns error message -// Purpose: reads aquifer parameter values from line of input data -// -// Data line contains following parameters: -// ID, porosity, wiltingPoint, fieldCapacity, conductivity, -// conductSlope, tensionSlope, upperEvapFraction, lowerEvapDepth, -// gwRecession, bottomElev, waterTableElev, upperMoisture -// (evapPattern) -// -{ - int i, p; - double x[12]; - char *id; - - // --- check that aquifer exists - if ( ntoks < 13 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(AQUIFER, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - - // --- read remaining tokens as numbers - for (i = 0; i < 11; i++) x[i] = 0.0; - for (i = 1; i < 13; i++) - { - if ( ! getDouble(tok[i], &x[i-1]) ) - return error_setInpError(ERR_NUMBER, tok[i]); - } - - // --- read upper evap pattern if present - p = -1; - if ( ntoks > 13 ) - { - p = project_findObject(TIMEPATTERN, tok[13]); - if ( p < 0 ) return error_setInpError(ERR_NAME, tok[13]); - } - - // --- assign parameters to aquifer object - Aquifer[j].ID = id; - Aquifer[j].porosity = x[0]; - Aquifer[j].wiltingPoint = x[1]; - Aquifer[j].fieldCapacity = x[2]; - Aquifer[j].conductivity = x[3] / UCF(RAINFALL); - Aquifer[j].conductSlope = x[4]; - Aquifer[j].tensionSlope = x[5] / UCF(LENGTH); - Aquifer[j].upperEvapFrac = x[6]; - Aquifer[j].lowerEvapDepth = x[7] / UCF(LENGTH); - Aquifer[j].lowerLossCoeff = x[8] / UCF(RAINFALL); - Aquifer[j].bottomElev = x[9] / UCF(LENGTH); - Aquifer[j].waterTableElev = x[10] / UCF(LENGTH); - Aquifer[j].upperMoisture = x[11]; - Aquifer[j].upperEvapPat = p; - return 0; -} - -//============================================================================= - -int gwater_readGroundwaterParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// Purpose: reads groundwater inflow parameters for a subcatchment from -// a line of input data. -// -// Data format is: -// subcatch aquifer node surfElev a1 b1 a2 b2 a3 fixedDepth + -// (nodeElev bottomElev waterTableElev upperMoisture ) -// -{ - int i, j, k, m, n; - double x[11]; - TGroundwater* gw; - - // --- check that specified subcatchment, aquifer & node exist - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - j = project_findObject(SUBCATCH, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- check for enough tokens - if ( ntoks < 11 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that specified aquifer and node exists - k = project_findObject(AQUIFER, tok[1]); - if ( k < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n = project_findObject(NODE, tok[2]); - if ( n < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // -- read in the flow parameters - for ( i = 0; i < 7; i++ ) - { - if ( ! getDouble(tok[i+3], &x[i]) ) - return error_setInpError(ERR_NUMBER, tok[i+3]); - } - - // --- read in optional depth parameters - for ( i = 7; i < 11; i++) - { - x[i] = MISSING; - m = i + 3; - if ( ntoks > m && *tok[m] != '*' ) - { - if (! getDouble(tok[m], &x[i]) ) - return error_setInpError(ERR_NUMBER, tok[m]); - if ( i < 10 ) x[i] /= UCF(LENGTH); - } - } - - // --- create a groundwater flow object - if ( !Subcatch[j].groundwater ) - { - gw = (TGroundwater *) malloc(sizeof(TGroundwater)); - if ( !gw ) return error_setInpError(ERR_MEMORY, ""); - Subcatch[j].groundwater = gw; - } - else gw = Subcatch[j].groundwater; - - // --- populate the groundwater flow object with its parameters - gw->aquifer = k; - gw->node = n; - gw->surfElev = x[0] / UCF(LENGTH); - gw->a1 = x[1]; - gw->b1 = x[2]; - gw->a2 = x[3]; - gw->b2 = x[4]; - gw->a3 = x[5]; - gw->fixedDepth = x[6] / UCF(LENGTH); - gw->nodeElev = x[7]; //already converted to ft. - gw->bottomElev = x[8]; - gw->waterTableElev = x[9]; - gw->upperMoisture = x[10]; - return 0; -} - -//============================================================================= - -int gwater_readFlowExpression(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// Purpose: reads mathematical expression for lateral or deep groundwater -// flow for a subcatchment from a line of input data. -// -// Format is: subcatch LATERAL/DEEP -// where subcatch is the ID of the subcatchment, LATERAL is for lateral -// GW flow, DEEP is for deep GW flow and is any well-formed math -// expression. -// -{ - int i, j, k; - char exprStr[MAXLINE+1]; - MathExpr* expr; - - // --- return if too few tokens - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that subcatchment exists - j = project_findObject(SUBCATCH, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- check if expression is for lateral or deep GW flow - k = 1; - if ( match(tok[1], "LAT") ) k = 1; - else if ( match(tok[1], "DEEP") ) k = 2; - else return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- concatenate remaining tokens into a single string - sstrncpy(exprStr, tok[2], MAXLINE); - for ( i = 3; i < ntoks; i++) - { - sstrcat(exprStr, " ", MAXLINE+1); - sstrcat(exprStr, tok[i], MAXLINE+1); - } - - // --- delete any previous flow eqn. - if ( k == 1 ) mathexpr_delete(Subcatch[j].gwLatFlowExpr); - else mathexpr_delete(Subcatch[j].gwDeepFlowExpr); - - // --- create a parsed expression tree from the string expr - // (getVariableIndex is the function that converts a GW - // variable's name into an index number) - expr = mathexpr_create(exprStr, getVariableIndex); - if ( expr == NULL ) return error_setInpError(ERR_MATH_EXPR, ""); - - // --- save expression tree with the subcatchment - if ( k == 1 ) Subcatch[j].gwLatFlowExpr = expr; - else Subcatch[j].gwDeepFlowExpr = expr; - return 0; -} - -//============================================================================= - -void gwater_deleteFlowExpression(int j) -// -// Input: j = subcatchment index -// Output: none -// Purpose: deletes a subcatchment's custom groundwater flow expressions. -// -{ - mathexpr_delete(Subcatch[j].gwLatFlowExpr); - mathexpr_delete(Subcatch[j].gwDeepFlowExpr); -} - -//============================================================================= - -void gwater_validateAquifer(int j) -// -// Input: j = aquifer index -// Output: none -// Purpose: validates groundwater aquifer properties . -// -{ - int p; - - if ( Aquifer[j].porosity <= 0.0 - || Aquifer[j].fieldCapacity >= Aquifer[j].porosity - || Aquifer[j].wiltingPoint >= Aquifer[j].fieldCapacity - || Aquifer[j].conductivity <= 0.0 - || Aquifer[j].conductSlope < 0.0 - || Aquifer[j].tensionSlope < 0.0 - || Aquifer[j].upperEvapFrac < 0.0 - || Aquifer[j].lowerEvapDepth < 0.0 - || Aquifer[j].waterTableElev < Aquifer[j].bottomElev - || Aquifer[j].upperMoisture > Aquifer[j].porosity - || Aquifer[j].upperMoisture < Aquifer[j].wiltingPoint ) - report_writeErrorMsg(ERR_AQUIFER_PARAMS, Aquifer[j].ID); - - p = Aquifer[j].upperEvapPat; - if ( p >= 0 && Pattern[p].type != MONTHLY_PATTERN ) - { - report_writeErrorMsg(ERR_AQUIFER_PARAMS, Aquifer[j].ID); - } -} - -//============================================================================= - -void gwater_validate(int j) -{ - TAquifer a; // Aquifer data structure - TGroundwater* gw; // Groundwater data structure - - gw = Subcatch[j].groundwater; - if ( gw ) - { - a = Aquifer[gw->aquifer]; - - // ... use aquifer values for missing groundwater parameters - if ( gw->bottomElev == MISSING ) gw->bottomElev = a.bottomElev; - if ( gw->waterTableElev == MISSING ) gw->waterTableElev = a.waterTableElev; - if ( gw->upperMoisture == MISSING ) gw->upperMoisture = a.upperMoisture; - - // ... ground elevation can't be below water table elevation - if ( gw->surfElev < gw->waterTableElev ) - report_writeErrorMsg(ERR_GROUND_ELEV, Subcatch[j].ID); - } -} - -//============================================================================= - -void gwater_initState(int j) -// -// Input: j = subcatchment index -// Output: none -// Purpose: initializes state of subcatchment's groundwater. -// -{ - TAquifer a; // Aquifer data structure - TGroundwater* gw; // Groundwater data structure - - gw = Subcatch[j].groundwater; - if ( gw ) - { - a = Aquifer[gw->aquifer]; - - // ... initial moisture content - gw->theta = gw->upperMoisture; - if ( gw->theta >= a.porosity ) - { - gw->theta = a.porosity - XTOL; - } - - // ... initial depth of lower (saturated) zone - gw->lowerDepth = gw->waterTableElev - gw->bottomElev; - if ( gw->lowerDepth >= gw->surfElev - gw->bottomElev ) - { - gw->lowerDepth = gw->surfElev - gw->bottomElev - XTOL; - } - - // ... initial lateral groundwater outflow - gw->oldFlow = 0.0; - gw->newFlow = 0.0; - gw->evapLoss = 0.0; - - // ... initial available infiltration volume into upper zone - gw->maxInfilVol = (gw->surfElev - gw->waterTableElev) * - (a.porosity - gw->theta) / - subcatch_getFracPerv(j); - } -} - -//============================================================================= - -void gwater_getState(int j, double x[]) -// -// Input: j = subcatchment index -// Output: x[] = array of groundwater state variables -// Purpose: retrieves state of subcatchment's groundwater. -// -{ - TGroundwater* gw = Subcatch[j].groundwater; - x[0] = gw->theta; - x[1] = gw->bottomElev + gw->lowerDepth; - x[2] = gw->newFlow; - x[3] = gw->maxInfilVol; -} - -//============================================================================= - -void gwater_setState(int j, double x[]) -// -// Input: j = subcatchment index -// x[] = array of groundwater state variables -// Purpose: assigns values to a subcatchment's groundwater state. -// -{ - TGroundwater* gw = Subcatch[j].groundwater; - if ( gw == NULL ) return; - gw->theta = x[0]; - gw->lowerDepth = x[1] - gw->bottomElev; - gw->oldFlow = x[2]; - if ( x[3] != MISSING ) gw->maxInfilVol = x[3]; -} - -//============================================================================= - -double gwater_getVolume(int j) -// -// Input: j = subcatchment index -// Output: returns total volume of groundwater in ft/ft2 -// Purpose: finds volume of groundwater stored in upper & lower zones -// -{ - TAquifer a; - TGroundwater* gw; - double upperDepth; - gw = Subcatch[j].groundwater; - if ( gw == NULL ) return 0.0; - a = Aquifer[gw->aquifer]; - upperDepth = gw->surfElev - gw->bottomElev - gw->lowerDepth; - return (upperDepth * gw->theta) + (gw->lowerDepth * a.porosity); -} - -//============================================================================= - -void gwater_getGroundwater(int j, double evap, double infil, double tStep) -// -// Purpose: computes groundwater flow from subcatchment during current time step. -// Input: j = subcatchment index -// evap = pervious surface evaporation volume consumed (ft3) -// infil = surface infiltration volume (ft3) -// tStep = time step (sec) -// Output: none -// -{ - int n; // node exchanging groundwater - double x[2]; // upper moisture content & lower depth - double vUpper; // upper vol. available for percolation - double nodeFlow; // max. possible GW flow from node - - // --- save subcatchment's groundwater and aquifer objects to - // shared variables - GW = Subcatch[j].groundwater; - if ( GW == NULL ) return; - LatFlowExpr = Subcatch[j].gwLatFlowExpr; - DeepFlowExpr = Subcatch[j].gwDeepFlowExpr; - A = Aquifer[GW->aquifer]; - - // --- get fraction of total area that is pervious - FracPerv = subcatch_getFracPerv(j); - if ( FracPerv <= 0.0 ) return; - Area = Subcatch[j].area; - - // --- convert infiltration volume (ft3) to equivalent rate - // over entire GW (subcatchment) area - infil = infil / Area / tStep; - Infil = infil; - Tstep = tStep; - - // --- convert pervious surface evaporation already exerted (ft3) - // to equivalent rate over entire GW (subcatchment) area - evap = evap / Area / tStep; - - // --- convert max. surface evap rate (ft/sec) to a rate - // that applies to GW evap (GW evap can only occur - // through the pervious land surface area) - MaxEvap = Evap.rate * FracPerv; - - // --- available subsurface evaporation is difference between max. - // rate and pervious surface evap already exerted - AvailEvap = MAX((MaxEvap - evap), 0.0); - - // --- save total depth & outlet node properties to shared variables - TotalDepth = GW->surfElev - GW->bottomElev; - if ( TotalDepth <= 0.0 ) return; - n = GW->node; - - // --- establish min. water table height above aquifer bottom at which - // GW flow can occur (override node's invert if a value was provided - // in the GW object) - if ( GW->nodeElev != MISSING ) Hstar = GW->nodeElev - GW->bottomElev; - else Hstar = Node[n].invertElev - GW->bottomElev; - - // --- establish surface water height (relative to aquifer bottom) - // for drainage system node connected to the GW aquifer - if ( GW->fixedDepth > 0.0 ) - { - Hsw = GW->fixedDepth + Node[n].invertElev - GW->bottomElev; - } - else Hsw = Node[n].newDepth + Node[n].invertElev - GW->bottomElev; - - // --- store state variables (upper zone moisture content, lower zone - // depth) in work vector x - x[THETA] = GW->theta; - x[LOWERDEPTH] = GW->lowerDepth; - - // --- set limit on percolation rate from upper to lower GW zone - vUpper = (TotalDepth - x[LOWERDEPTH]) * (x[THETA] - A.fieldCapacity); - vUpper = MAX(0.0, vUpper); - MaxUpperPerc = vUpper / tStep; - - // --- set limit on GW flow out of aquifer based on volume of lower zone - MaxGWFlowPos = x[LOWERDEPTH]*A.porosity / tStep; - - // --- set limit on GW flow into aquifer from drainage system node - // based on min. of capacity of upper zone and drainage system - // inflow to the node - MaxGWFlowNeg = (TotalDepth - x[LOWERDEPTH]) * (A.porosity - x[THETA]) - / tStep; - nodeFlow = (Node[n].inflow + Node[n].newVolume/tStep) / Area; - MaxGWFlowNeg = -MIN(MaxGWFlowNeg, nodeFlow); - - // --- integrate eqns. for d(Theta)/dt and d(LowerDepth)/dt - // NOTE: ODE solver must have been initialized previously - odesolve_integrate(x, 2, 0, tStep, GWTOL, tStep, getDxDt); - - // --- keep state variables within allowable bounds - x[THETA] = MAX(x[THETA], A.wiltingPoint); - if ( x[THETA] >= A.porosity ) - { - x[THETA] = A.porosity - XTOL; - x[LOWERDEPTH] = TotalDepth - XTOL; - } - x[LOWERDEPTH] = MAX(x[LOWERDEPTH], 0.0); - if ( x[LOWERDEPTH] >= TotalDepth ) - { - x[LOWERDEPTH] = TotalDepth - XTOL; - } - - // --- save new values of state values - GW->theta = x[THETA]; - GW->lowerDepth = x[LOWERDEPTH]; - getFluxes(GW->theta, GW->lowerDepth); - GW->oldFlow = GW->newFlow; - GW->newFlow = GWFlow; - GW->evapLoss = UpperEvap + LowerEvap; - - //--- find max. infiltration volume (as depth over - // the pervious portion of the subcatchment) - // that upper zone can support in next time step - GW->maxInfilVol = (TotalDepth - x[LOWERDEPTH]) * - (A.porosity - x[THETA]) / FracPerv; - - // --- update GW mass balance - updateMassBal(Area, tStep); - - // --- update GW statistics - stats_updateGwaterStats(j, infil, GW->evapLoss, GWFlow, LowerLoss, - GW->theta, GW->lowerDepth + GW->bottomElev, tStep); -} - -//============================================================================= - -void updateMassBal(double area, double tStep) -// -// Input: area = subcatchment area (ft2) -// tStep = time step (sec) -// Output: none -// Purpose: updates GW mass balance with volumes of water fluxes. -// -{ - double vInfil; // infiltration volume - double vUpperEvap; // upper zone evap. volume - double vLowerEvap; // lower zone evap. volume - double vLowerPerc; // lower zone deep perc. volume - double vGwater; // volume of exchanged groundwater - double ft2sec = area * tStep; - - vInfil = Infil * ft2sec; - vUpperEvap = UpperEvap * ft2sec; - vLowerEvap = LowerEvap * ft2sec; - vLowerPerc = LowerLoss * ft2sec; - vGwater = 0.5 * (GW->oldFlow + GW->newFlow) * ft2sec; - massbal_updateGwaterTotals(vInfil, vUpperEvap, vLowerEvap, vLowerPerc, - vGwater); -} - -//============================================================================= - -void getFluxes(double theta, double lowerDepth) -// -// Input: upperVolume = vol. depth of upper zone (ft) -// upperDepth = depth of upper zone (ft) -// Output: none -// Purpose: computes water fluxes into/out of upper/lower GW zones. -// -{ - double upperDepth; - - // --- find upper zone depth - lowerDepth = MAX(lowerDepth, 0.0); - lowerDepth = MIN(lowerDepth, TotalDepth); - upperDepth = TotalDepth - lowerDepth; - - // --- save lower depth and theta to global variables - Hgw = lowerDepth; - Theta = theta; - - // --- find evaporation rate from both zones - getEvapRates(theta, upperDepth); - - // --- find percolation rate from upper to lower zone - UpperPerc = getUpperPerc(theta, upperDepth); - UpperPerc = MIN(UpperPerc, MaxUpperPerc); - - // --- find loss rate to deep GW - if ( DeepFlowExpr != NULL ) - LowerLoss = mathexpr_eval(DeepFlowExpr, getVariableValue) / - UCF(RAINFALL); - else - LowerLoss = A.lowerLossCoeff * lowerDepth / TotalDepth; - LowerLoss = MIN(LowerLoss, lowerDepth/Tstep); - - // --- find GW flow rate from lower zone to drainage system node - GWFlow = getGWFlow(lowerDepth); - if ( LatFlowExpr != NULL ) - { - GWFlow += mathexpr_eval(LatFlowExpr, getVariableValue) / UCF(GWFLOW); - } - if ( GWFlow >= 0.0 ) GWFlow = MIN(GWFlow, MaxGWFlowPos); - else GWFlow = MAX(GWFlow, MaxGWFlowNeg); -} - -//============================================================================= - -void getDxDt(double t, double* x, double* dxdt) -// -// Input: t = current time (not used) -// x = array of state variables -// Output: dxdt = array of time derivatives of state variables -// Purpose: computes time derivatives of upper moisture content -// and lower depth. -// -{ - double qUpper; // inflow - outflow for upper zone (ft/sec) - double qLower; // inflow - outflow for lower zone (ft/sec) - double denom; - - getFluxes(x[THETA], x[LOWERDEPTH]); - qUpper = Infil - UpperEvap - UpperPerc; - qLower = UpperPerc - LowerLoss - LowerEvap - GWFlow; - - // --- d(upper zone moisture)/dt = (net upper zone flow) / - // (upper zone depth) - denom = TotalDepth - x[LOWERDEPTH]; - if (denom > 0.0) - dxdt[THETA] = qUpper / denom; - else - dxdt[THETA] = 0.0; - - // --- d(lower zone depth)/dt = (net lower zone flow) / - // (upper zone moisture deficit) - denom = A.porosity - x[THETA]; - if (denom > 0.0) - dxdt[LOWERDEPTH] = qLower / denom; - else - dxdt[LOWERDEPTH] = 0.0; -} - -//============================================================================= - -void getEvapRates(double theta, double upperDepth) -// -// Input: theta = moisture content of upper zone -// upperDepth = depth of upper zone (ft) -// Output: none -// Purpose: computes evapotranspiration out of upper & lower zones. -// -{ - int p, month; - double f; - double lowerFrac, upperFrac; - - // --- no GW evaporation when infiltration is occurring - UpperEvap = 0.0; - LowerEvap = 0.0; - if ( Infil > 0.0 ) return; - - // --- get monthly-adjusted upper zone evap fraction - upperFrac = A.upperEvapFrac; - f = 1.0; - p = A.upperEvapPat; - if ( p >= 0 ) - { - month = datetime_monthOfYear(getDateTime(NewRunoffTime)); - f = Pattern[p].factor[month-1]; - } - upperFrac *= f; - - // --- upper zone evaporation requires that soil moisture - // be above the wilting point - if ( theta > A.wiltingPoint ) - { - // --- actual evap is upper zone fraction applied to max. potential - // rate, limited by the available rate after any surface evap - UpperEvap = upperFrac * MaxEvap; - UpperEvap = MIN(UpperEvap, AvailEvap); - } - - // --- check if lower zone evaporation is possible - if ( A.lowerEvapDepth > 0.0 ) - { - // --- find the fraction of the lower evaporation depth that - // extends into the saturated lower zone - lowerFrac = (A.lowerEvapDepth - upperDepth) / A.lowerEvapDepth; - lowerFrac = MAX(0.0, lowerFrac); - lowerFrac = MIN(lowerFrac, 1.0); - - // --- make the lower zone evap rate proportional to this fraction - // and the evap not used in the upper zone - LowerEvap = lowerFrac * (1.0 - upperFrac) * MaxEvap; - LowerEvap = MIN(LowerEvap, (AvailEvap - UpperEvap)); - } -} - -//============================================================================= - -double getUpperPerc(double theta, double upperDepth) -// -// Input: theta = moisture content of upper zone -// upperDepth = depth of upper zone (ft) -// Output: returns percolation rate (ft/sec) -// Purpose: finds percolation rate from upper to lower zone. -// -{ - double delta; // unfilled water content of upper zone - double dhdz; // avg. change in head with depth - double hydcon; // unsaturated hydraulic conductivity - - // --- no perc. from upper zone if no depth or moisture content too low - if ( upperDepth <= 0.0 || theta <= A.fieldCapacity ) return 0.0; - - // --- compute hyd. conductivity as function of moisture content - delta = theta - A.porosity; - hydcon = A.conductivity * exp(delta * A.conductSlope); - - // --- compute integral of dh/dz term - delta = theta - A.fieldCapacity; - dhdz = 1.0 + A.tensionSlope * 2.0 * delta / upperDepth; - - // --- compute upper zone percolation rate - HydCon = hydcon; - return hydcon * dhdz; -} - -//============================================================================= - -double getGWFlow(double lowerDepth) -// -// Input: lowerDepth = depth of lower zone (ft) -// Output: returns groundwater flow rate (ft/sec) -// Purpose: finds groundwater outflow from lower saturated zone. -// -{ - double q, t1, t2, t3; - - // --- water table must be above Hstar for flow to occur - if ( lowerDepth <= Hstar ) return 0.0; - - // --- compute groundwater component of flow - if ( GW->b1 == 0.0 ) t1 = GW->a1; - else t1 = GW->a1 * pow( (lowerDepth - Hstar)*UCF(LENGTH), GW->b1); - - // --- compute surface water component of flow - if ( GW->b2 == 0.0 ) t2 = GW->a2; - else if (Hsw > Hstar) - { - t2 = GW->a2 * pow( (Hsw - Hstar)*UCF(LENGTH), GW->b2); - } - else t2 = 0.0; - - // --- compute groundwater/surface water interaction term - t3 = GW->a3 * lowerDepth * Hsw * UCF(LENGTH) * UCF(LENGTH); - - // --- compute total groundwater flow - q = (t1 - t2 + t3) / UCF(GWFLOW); - if ( q < 0.0 && GW->a3 != 0.0 ) q = 0.0; - return q; -} - -//============================================================================= - -int getVariableIndex(char* s) -// -// Input: s = name of a groundwater variable -// Output: returns index of groundwater variable -// Purpose: finds position of GW variable in list of GW variable names. -// -{ - int k; - - k = findmatch(s, GWVarWords); - if ( k >= 0 ) return k; - return -1; -} - -//============================================================================= - -double getVariableValue(int varIndex) -// -// Input: varIndex = index of a GW variable -// Output: returns current value of GW variable -// Purpose: finds current value of a GW variable. -// -{ - switch (varIndex) - { - case gwvHGW: return Hgw * UCF(LENGTH); - case gwvHSW: return Hsw * UCF(LENGTH); - case gwvHCB: return Hstar * UCF(LENGTH); - case gwvHGS: return TotalDepth * UCF(LENGTH); - case gwvKS: return A.conductivity * UCF(RAINFALL); - case gwvK: return HydCon * UCF(RAINFALL); - case gwvTHETA:return Theta; - case gwvPHI: return A.porosity; - case gwvFI: return Infil * UCF(RAINFALL); - case gwvFU: return UpperPerc * UCF(RAINFALL); - case gwvA: return Area * UCF(LANDAREA); - default: return 0.0; - } -} diff --git a/src/hash.c b/src/hash.c deleted file mode 100644 index 4a73e6ccd..000000000 --- a/src/hash.c +++ /dev/null @@ -1,117 +0,0 @@ -//----------------------------------------------------------------------------- -// hash.c -// -// Implementation of a simple Hash Table for string storage & retrieval -// CASE INSENSITIVE -// -// Written by L. Rossman -// Last Updated on 6/19/03 -// -// The hash table data structure (HTable) is defined in "hash.h". -// Interface Functions: -// HTcreate() - creates a hash table -// HTinsert() - inserts a string & its index value into a hash table -// HTfind() - retrieves the index value of a string from a table -// HTfree() - frees a hash table -//----------------------------------------------------------------------------- - -#include -#include -#include "hash.h" -#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) - -/* Case-insensitive comparison of strings s1 and s2 */ -int samestr(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); - return(0); -} /* End of samestr */ - -/* Use Fletcher's checksum to compute 2-byte hash of string */ -unsigned int hash(const char *str) -{ - unsigned int sum1= 0, check1; - unsigned long sum2= 0L; - while( '\0' != *str ) - { - sum1 += UCHAR(*str); - str++; - if ( 255 <= sum1 ) sum1 -= 255; - sum2 += sum1; - } - check1= sum2; - check1 %= 255; - check1= 255 - (sum1+check1) % 255; - sum1= 255 - (sum1+check1) % 255; - return( ( ( check1 << 8 ) | sum1 ) % HTMAXSIZE); -} - -HTtable *HTcreate() -{ - int i; - HTtable *ht = (HTtable *) calloc(HTMAXSIZE, sizeof(HTtable)); - if (ht != NULL) for (i=0; i= HTMAXSIZE ) return(0); - entry = (struct HTentry *) malloc(sizeof(struct HTentry)); - if (entry == NULL) return(0); - entry->key = key; - entry->data = data; - entry->next = ht[i]; - ht[i] = entry; - return(1); -} - -int HTfind(HTtable *ht, const char *key) -{ - unsigned int i = hash(key); - struct HTentry *entry; - if ( i >= HTMAXSIZE ) return(NOTFOUND); - entry = ht[i]; - while (entry != NULL) - { - if ( samestr(entry->key,key) ) return(entry->data); - entry = entry->next; - } - return(NOTFOUND); -} - -char *HTfindKey(HTtable *ht, const char *key) -{ - unsigned int i = hash(key); - struct HTentry *entry; - if ( i >= HTMAXSIZE ) return(NULL); - entry = ht[i]; - while (entry != NULL) - { - if ( samestr(entry->key,key) ) return(entry->key); - entry = entry->next; - } - return(NULL); -} - -void HTfree(HTtable *ht) -{ - struct HTentry *entry, - *nextentry; - int i; - for (i=0; inext; - free(entry); - entry = nextentry; - } - } - free(ht); -} diff --git a/src/hash.h b/src/hash.h deleted file mode 100644 index ba42be00e..000000000 --- a/src/hash.h +++ /dev/null @@ -1,30 +0,0 @@ -//----------------------------------------------------------------------------- -// hash.h -// -// Header file for Hash Table module hash.c. -//----------------------------------------------------------------------------- - -#ifndef HASH_H -#define HASH_H - - -#define HTMAXSIZE 1999 -#define NOTFOUND -1 - -struct HTentry -{ - char *key; - int data; - struct HTentry *next; -}; - -typedef struct HTentry *HTtable; - -HTtable* HTcreate(void); -int HTinsert(HTtable *, char *, int); -int HTfind(HTtable *, const char *); -char* HTfindKey(HTtable *, const char *); -void HTfree(HTtable *); - - -#endif //HASH_H diff --git a/src/headers.h b/src/headers.h deleted file mode 100644 index 87139b060..000000000 --- a/src/headers.h +++ /dev/null @@ -1,20 +0,0 @@ -//----------------------------------------------------------------------------- -// headers.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Header files included in most SWMM5 modules. -// -// DO NOT CHANGE THE ORDER OF THE #INCLUDE STATEMENTS -//----------------------------------------------------------------------------- -#include "macros.h" -#include "objects.h" -#define EXTERN extern -#include "globals.h" -#include "funcs.h" -#include "error.h" -#include "text.h" -#include "keywords.h" diff --git a/src/hotstart.c b/src/hotstart.c deleted file mode 100644 index 69f7abcc9..000000000 --- a/src/hotstart.c +++ /dev/null @@ -1,544 +0,0 @@ -//----------------------------------------------------------------------------- -// hotstart.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Hot Start file functions. -// -// A SWMM hot start file contains the state of a SWMM project after -// a simulation has been run, allowing it to be used to initialize -// a subsequent simulation that picks up where the previous run ended. -// -// An abridged version of the hot start file (version 2) is available -// that contains only variables that appear in the binary output file -// (groundwater upper moisture and water table elevation, node depth, -// lateral inflow, and quality, and link flow, depth, setting and quality). -// -// When reading a previously saved hot start file checks are made to -// insure the the current SWMM project has the same number of major -// components (subcatchments, land uses, nodes, links, and pollutants) -// and unit system as does the hot start file. No test is made to -// insure that these components are of the same sub-type and maintain -// the same order as when the hot start file was created. -// -// Update History -// ============== -// Build 5.1.008: -// - Storage node hydraulic residence time (HRT) was added to the file. -// - Link control settings are now applied when reading a hot start file. -// - Runoff read from file assigned to newRunoff property instead of oldRunoff. -// - Array indexing bug when reading snowpack state from file fixed. -// Build 5.1.011: -// - Link control setting bug when reading a hot start file fixed. -// Build 5.1.015: -// - Support added for multiple infiltration methods within a project. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Local Variables -//----------------------------------------------------------------------------- -static int fileVersion; - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// hotstart_open (called by swmm_start in swmm5.c) -// hotstart_close (called by swmm_end in swmm5.c) - -//----------------------------------------------------------------------------- -// Function declarations -//----------------------------------------------------------------------------- -static int openHotstartFile1(void); -static int openHotstartFile2(void); -static void readRunoff(void); -static void saveRunoff(void); -static void readRouting(void); -static void saveRouting(void); -static int readFloat(float *x, FILE* f); -static int readDouble(double* x, FILE* f); - -//============================================================================= - -int hotstart_open() -{ - // --- open hot start files - if ( !openHotstartFile1() ) return FALSE; //input hot start file - if ( !openHotstartFile2() ) return FALSE; //output hot start file - return TRUE; -} - -//============================================================================= - -void hotstart_close() -{ - if ( Fhotstart2.file ) - { - saveRunoff(); - saveRouting(); - fclose(Fhotstart2.file); - } -} - -//============================================================================= - -int openHotstartFile1() -// -// Input: none -// Output: none -// Purpose: opens a previously saved routing hotstart file. -// -{ - int nSubcatch; - int nNodes; - int nLinks; - int nPollut; - int nLandUses; - int flowUnits; - char fStamp[] = "SWMM5-HOTSTART"; - char fileStamp[] = "SWMM5-HOTSTART"; - char fStampx[] = "SWMM5-HOTSTARTx"; - char fileStamp2[] = "SWMM5-HOTSTART2"; - char fileStamp3[] = "SWMM5-HOTSTART3"; - char fileStamp4[] = "SWMM5-HOTSTART4"; - - // --- try to open the file - if ( Fhotstart1.mode != USE_FILE ) return TRUE; - if ( (Fhotstart1.file = fopen(Fhotstart1.name, "r+b")) == NULL) - { - report_writeErrorMsg(ERR_HOTSTART_FILE_OPEN, Fhotstart1.name); - return FALSE; - } - - // --- check that file contains proper header records - fread(fStampx, sizeof(char), strlen(fileStamp2), Fhotstart1.file); - if ( strcmp(fStampx, fileStamp4) == 0 ) fileVersion = 4; - else if ( strcmp(fStampx, fileStamp3) == 0 ) fileVersion = 3; - else if ( strcmp(fStampx, fileStamp2) == 0 ) fileVersion = 2; - else - { - rewind(Fhotstart1.file); - fread(fStamp, sizeof(char), strlen(fileStamp), Fhotstart1.file); - if ( strcmp(fStamp, fileStamp) != 0 ) - { - report_writeErrorMsg(ERR_HOTSTART_FILE_FORMAT, ""); - return FALSE; - } - fileVersion = 1; - } - - nSubcatch = -1; - nNodes = -1; - nLinks = -1; - nPollut = -1; - nLandUses = -1; - flowUnits = -1; - if ( fileVersion >= 2 ) - { - fread(&nSubcatch, sizeof(int), 1, Fhotstart1.file); - } - else nSubcatch = Nobjects[SUBCATCH]; - if ( fileVersion >= 3 ) - { - fread(&nLandUses, sizeof(int), 1, Fhotstart1.file); - } - else nLandUses = Nobjects[LANDUSE]; - fread(&nNodes, sizeof(int), 1, Fhotstart1.file); - fread(&nLinks, sizeof(int), 1, Fhotstart1.file); - fread(&nPollut, sizeof(int), 1, Fhotstart1.file); - fread(&flowUnits, sizeof(int), 1, Fhotstart1.file); - if ( nSubcatch != Nobjects[SUBCATCH] - || nLandUses != Nobjects[LANDUSE] - || nNodes != Nobjects[NODE] - || nLinks != Nobjects[LINK] - || nPollut != Nobjects[POLLUT] - || flowUnits != FlowUnits ) - { - report_writeErrorMsg(ERR_HOTSTART_FILE_FORMAT, ""); - return FALSE; - } - - // --- read contents of the file and close it - if ( fileVersion >= 3 ) readRunoff(); - readRouting(); - fclose(Fhotstart1.file); - if ( ErrorCode ) return FALSE; - else return TRUE; -} - -//============================================================================= - -int openHotstartFile2() -// -// Input: none -// Output: none -// Purpose: opens a new routing hotstart file to save results to. -// -{ - int nSubcatch; - int nLandUses; - int nNodes; - int nLinks; - int nPollut; - int flowUnits; - char fileStamp[] = "SWMM5-HOTSTART4"; - - // --- try to open file - if ( Fhotstart2.mode != SAVE_FILE ) return TRUE; - if ( (Fhotstart2.file = fopen(Fhotstart2.name, "w+b")) == NULL) - { - report_writeErrorMsg(ERR_HOTSTART_FILE_OPEN, Fhotstart2.name); - return FALSE; - } - - // --- write file stamp & number of objects to file - nSubcatch = Nobjects[SUBCATCH]; - nLandUses = Nobjects[LANDUSE]; - nNodes = Nobjects[NODE]; - nLinks = Nobjects[LINK]; - nPollut = Nobjects[POLLUT]; - flowUnits = FlowUnits; - fwrite(fileStamp, sizeof(char), strlen(fileStamp), Fhotstart2.file); - fwrite(&nSubcatch, sizeof(int), 1, Fhotstart2.file); - fwrite(&nLandUses, sizeof(int), 1, Fhotstart2.file); - fwrite(&nNodes, sizeof(int), 1, Fhotstart2.file); - fwrite(&nLinks, sizeof(int), 1, Fhotstart2.file); - fwrite(&nPollut, sizeof(int), 1, Fhotstart2.file); - fwrite(&flowUnits, sizeof(int), 1, Fhotstart2.file); - return TRUE; -} - -//============================================================================= - -void saveRouting() -// -// Input: none -// Output: none -// Purpose: saves current state of all nodes and links to hotstart file. -// -{ - int i, j; - float x[3]; - - for (i = 0; i < Nobjects[NODE]; i++) - { - x[0] = (float)Node[i].newDepth; - x[1] = (float)Node[i].newLatFlow; - fwrite(x, sizeof(float), 2, Fhotstart2.file); - - if ( Node[i].type == STORAGE ) - { - j = Node[i].subIndex; - x[0] = (float)Storage[j].hrt; - fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); - } - - for (j = 0; j < Nobjects[POLLUT]; j++) - { - x[0] = (float)Node[i].newQual[j]; - fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); - } - } - for (i = 0; i < Nobjects[LINK]; i++) - { - x[0] = (float)Link[i].newFlow; - x[1] = (float)Link[i].newDepth; - x[2] = (float)Link[i].setting; - fwrite(x, sizeof(float), 3, Fhotstart2.file); - for (j = 0; j < Nobjects[POLLUT]; j++) - { - x[0] = (float)Link[i].newQual[j]; - fwrite(&x[0], sizeof(float), 1, Fhotstart2.file); - } - } -} - -//============================================================================= - -void readRouting() -// -// Input: none -// Output: none -// Purpose: reads initial state of all nodes, links and groundwater objects -// from hotstart file. -// -{ - int i, j; - float x; - double xgw[4]; - FILE* f = Fhotstart1.file; - - // --- for file format 2, assign GW moisture content and lower depth - if ( fileVersion == 2 ) - { - // --- flow and available upper zone volume not used - xgw[2] = 0.0; - xgw[3] = MISSING; - for (i = 0; i < Nobjects[SUBCATCH]; i++) - { - // --- read moisture content and water table elevation as floats - if ( !readFloat(&x, f) ) return; - xgw[0] = x; - if ( !readFloat(&x, f) ) return; - xgw[1] = x; - - // --- set GW state - if ( Subcatch[i].groundwater != NULL ) gwater_setState(i, xgw); - } - } - - // --- read node states - for (i = 0; i < Nobjects[NODE]; i++) - { - if ( !readFloat(&x, f) ) return; - Node[i].newDepth = x; - if ( !readFloat(&x, f) ) return; - Node[i].newLatFlow = x; - - if ( fileVersion >= 4 && Node[i].type == STORAGE ) - { - if ( !readFloat(&x, f) ) return; - j = Node[i].subIndex; - Storage[j].hrt = x; - } - - for (j = 0; j < Nobjects[POLLUT]; j++) - { - if ( !readFloat(&x, f) ) return; - Node[i].newQual[j] = x; - } - - // --- read in zeros here for backwards compatibility - if ( fileVersion <= 2 ) - { - for (j = 0; j < Nobjects[POLLUT]; j++) - { - if ( !readFloat(&x, f) ) return; - } - } - } - - // --- read link states - for (i = 0; i < Nobjects[LINK]; i++) - { - if ( !readFloat(&x, f) ) return; - Link[i].newFlow = x; - if ( !readFloat(&x, f) ) return; - Link[i].newDepth = x; - if ( !readFloat(&x, f) ) return; - Link[i].setting = x; - - // --- set link's target setting to saved setting - Link[i].targetSetting = x; - link_setTargetSetting(i); - link_setSetting(i, 0.0); - - for (j = 0; j < Nobjects[POLLUT]; j++) - { - if ( !readFloat(&x, f) ) return; - Link[i].newQual[j] = x; - } - - } -} - -//============================================================================= - -void saveRunoff(void) -// -// Input: none -// Output: none -// Purpose: saves current state of all subcatchments to hotstart file. -// -{ - int i, j, k; - double x[6]; - FILE* f = Fhotstart2.file; - - for (i = 0; i < Nobjects[SUBCATCH]; i++) - { - // Ponded depths for each sub-area & total runoff (4 elements) - for (j = 0; j < 3; j++) x[j] = Subcatch[i].subArea[j].depth; - x[3] = Subcatch[i].newRunoff; - fwrite(x, sizeof(double), 4, f); - - // Infiltration state (max. of 6 elements) - for (j=0; j<6; j++) x[j] = 0.0; - infil_getState(i, x); - fwrite(x, sizeof(double), 6, f); - - // Groundwater state (4 elements) - if ( Subcatch[i].groundwater != NULL ) - { - gwater_getState(i, x); - fwrite(x, sizeof(double), 4, f); - } - - // Snowpack state (5 elements for each of 3 snow surfaces) - if ( Subcatch[i].snowpack != NULL ) - { - for (j=0; j<3; j++) - { - snow_getState(i, j, x); - fwrite(x, sizeof(double), 5, f); - } - } - - // Water quality - if ( Nobjects[POLLUT] > 0 ) - { - // Runoff quality - for (j=0; j 0 ) - { - // Runoff quality - for (j=0; j -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// Imported variables -//----------------------------------------------------------------------------- -extern double Qcf[]; // flow units conversion factors - // (see swmm5.c) - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -static int IfaceFlowUnits; // flow units for routing interface file -static int IfaceStep; // interface file time step (sec) -static int NumIfacePolluts; // number of pollutants in interface file -static int* IfacePolluts; // indexes of interface file pollutants -static int NumIfaceNodes; // number of nodes on interface file -static int* IfaceNodes; // indexes of nodes on interface file -static double** OldIfaceValues; // interface flows & WQ at previous time -static double** NewIfaceValues; // interface flows & WQ at next time -static double IfaceFrac; // fraction of interface file time step -static DateTime OldIfaceDate; // previous date of interface values -static DateTime NewIfaceDate; // next date of interface values - -//----------------------------------------------------------------------------- -// External Functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// iface_readFileParams (called by input_readLine) -// iface_openRoutingFiles (called by routing_open) -// iface_closeRoutingFiles (called by routing_close) -// iface_getNumIfaceNodes (called by addIfaceInflows in routing.c) -// iface_getIfaceNode (called by addIfaceInflows in routing.c) -// iface_getIfaceFlow (called by addIfaceInflows in routing.c) -// iface_getIfaceQual (called by addIfaceInflows in routing.c) -// iface_saveOutletResults (called by output_saveResults) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static void openFileForOutput(void); -static void openFileForInput(void); -static int getIfaceFilePolluts(void); -static int getIfaceFileNodes(void); -static void setOldIfaceValues(void); -static void readNewIfaceValues(void); -static int isOutletNode(int node); - -//============================================================================= - -int iface_readFileParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads interface file information from a line of input data. -// -// Data format is: -// USE/SAVE FileType FileName -// -{ - char k; - int j; - char fname[MAXFNAME+1]; - - // --- determine file disposition and type - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - k = (char)findmatch(tok[0], FileModeWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); - j = findmatch(tok[1], FileTypeWords); - if ( j < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - if ( ntoks < 3 ) return 0; - sstrncpy(fname, tok[2], MAXFNAME); - - // --- process file name - switch ( j ) - { - case RAINFALL_FILE: - Frain.mode = k; - sstrncpy(Frain.name, addAbsolutePath(fname), MAXFNAME); - break; - - case RUNOFF_FILE: - Frunoff.mode = k; - sstrncpy(Frunoff.name, addAbsolutePath(fname), MAXFNAME); - break; - - case HOTSTART_FILE: - if ( k == USE_FILE ) - { - Fhotstart1.mode = k; - sstrncpy(Fhotstart1.name, addAbsolutePath(fname), MAXFNAME); - } - else if ( k == SAVE_FILE ) - { - Fhotstart2.mode = k; - sstrncpy(Fhotstart2.name, addAbsolutePath(fname), MAXFNAME); - } - break; - - case RDII_FILE: - Frdii.mode = k; - sstrncpy(Frdii.name, fname, MAXFNAME); - break; - - case INFLOWS_FILE: - if ( k != USE_FILE ) return error_setInpError(ERR_ITEMS, ""); - Finflows.mode = k; - sstrncpy(Finflows.name, addAbsolutePath(fname), MAXFNAME); - break; - - case OUTFLOWS_FILE: - if ( k != SAVE_FILE ) return error_setInpError(ERR_ITEMS, ""); - Foutflows.mode = k; - sstrncpy(Foutflows.name, addAbsolutePath(fname), MAXFNAME); - break; - } - return 0; -} - -//============================================================================= - -void iface_openRoutingFiles() -// -// Input: none -// Output: none -// Purpose: opens routing interface files. -// -{ - // --- initialize shared variables - NumIfacePolluts = 0; - IfacePolluts = NULL; - NumIfaceNodes = 0; - IfaceNodes = NULL; - OldIfaceValues = NULL; - NewIfaceValues = NULL; - - // --- check that inflows & outflows files are not the same - if ( Foutflows.mode != NO_FILE && Finflows.mode != NO_FILE ) - { - if ( strcomp(Foutflows.name, Finflows.name) ) - { - report_writeErrorMsg(ERR_ROUTING_FILE_NAMES, ""); - return; - } - } - - // --- open the file for reading or writing - if ( Foutflows.mode == SAVE_FILE ) openFileForOutput(); - if ( Finflows.mode == USE_FILE ) openFileForInput(); -} - -//============================================================================= - -void iface_closeRoutingFiles() -// -// Input: none -// Output: none -// Purpose: closes routing interface files. -// -{ - FREE(IfacePolluts); - FREE(IfaceNodes); - if ( OldIfaceValues != NULL ) project_freeMatrix(OldIfaceValues); - if ( NewIfaceValues != NULL ) project_freeMatrix(NewIfaceValues); - if ( Finflows.file ) fclose(Finflows.file); - if ( Foutflows.file ) fclose(Foutflows.file); -} - -//============================================================================= - -int iface_getNumIfaceNodes(DateTime currentDate) -// -// Input: currentDate = current date/time -// Output: returns number of interface nodes if data exists or -// 0 otherwise -// Purpose: reads inflow data from interface file at current date. -// -{ - // --- return 0 if file begins after current date - if ( OldIfaceDate > currentDate ) return 0; - - // --- keep updating new interface values until current date bracketed - while ( NewIfaceDate < currentDate && NewIfaceDate != NO_DATE ) - { - setOldIfaceValues(); - readNewIfaceValues(); - } - - // --- return 0 if no data available - if ( NewIfaceDate == NO_DATE ) return 0; - - // --- find fraction current date is bewteen old & new interface dates - IfaceFrac = (currentDate - OldIfaceDate) / (NewIfaceDate - OldIfaceDate); - IfaceFrac = MAX(0.0, IfaceFrac); - IfaceFrac = MIN(IfaceFrac, 1.0); - - // --- return number of interface nodes - return NumIfaceNodes; -} - -//============================================================================= - -int iface_getIfaceNode(int index) -// -// Input: index = interface file node index -// Output: returns project node index -// Purpose: finds index of project node associated with interface node index -// -{ - if ( index >= 0 && index < NumIfaceNodes ) return IfaceNodes[index]; - else return -1; -} - -//============================================================================= - -double iface_getIfaceFlow(int index) -// -// Input: index = interface file node index -// Output: returns inflow to node -// Purpose: finds interface flow for particular node index. -// -{ - double q1, q2; - - if ( index >= 0 && index < NumIfaceNodes ) - { - // --- interpolate flow between old and new values - q1 = OldIfaceValues[index][0]; - q2 = NewIfaceValues[index][0]; - return (1.0 - IfaceFrac)*q1 + IfaceFrac*q2; - } - else return 0.0; -} - -//============================================================================= - -double iface_getIfaceQual(int index, int pollut) -// -// Input: index = index of node on interface file -// pollut = index of pollutant on interface file -// Output: returns inflow pollutant concentration -// Purpose: finds interface concentration for particular node index & pollutant. -// -{ - int j; - double c1, c2; - - if ( index >= 0 && index < NumIfaceNodes ) - { - // --- find index of pollut on interface file - j = IfacePolluts[pollut]; - if ( j < 0 ) return 0.0; - - // --- interpolate flow between old and new values - // (remember that 1st col. of values matrix is for flow) - c1 = OldIfaceValues[index][j+1]; - c2 = NewIfaceValues[index][j+1]; - return (1.0 - IfaceFrac)*c1 + IfaceFrac*c2; - } - else return 0.0; -} - -//============================================================================= - -void iface_saveOutletResults(DateTime reportDate, FILE* file) -// -// Input: reportDate = reporting date/time -// file = ptr. to interface file -// Output: none -// Purpose: saves system outflows to routing interface file. -// -{ - int i, p, yr, mon, day, hr, min, sec; - char theDate[26]; - datetime_decodeDate(reportDate, &yr, &mon, &day); - datetime_decodeTime(reportDate, &hr, &min, &sec); - snprintf(theDate, 26, " %04d %02d %02d %02d %02d %02d ", - yr, mon, day, hr, min, sec); - for (i=0; i 0 ) - { - report_writeErrorMsg(err, Finflows.name); - return; - } - - // --- match nodes in file with those in project - err = getIfaceFileNodes(); - if ( err > 0 ) - { - report_writeErrorMsg(err, Finflows.name); - return; - } - - // --- create matrices for old & new interface flows & WQ values - OldIfaceValues = project_createMatrix(NumIfaceNodes, - 1+NumIfacePolluts); - NewIfaceValues = project_createMatrix(NumIfaceNodes, - 1+NumIfacePolluts); - if ( OldIfaceValues == NULL || NewIfaceValues == NULL ) - { - report_writeErrorMsg(ERR_MEMORY, ""); - return; - } - - // --- read in new interface flows & WQ values - readNewIfaceValues(); - OldIfaceDate = NewIfaceDate; -} - -//============================================================================= - -int getIfaceFilePolluts() -// -// Input: none -// Output: returns an error code -// Purpose: reads names of pollutants saved on the inflows interface file. -// -{ - int i, j; - char line[MAXLINE+1]; // line from inflows interface file - char s1[MAXLINE+1]; // general string variable - char s2[MAXLINE+1]; - - // --- read number of pollutants (minus FLOW) - fgets(line, MAXLINE, Finflows.file); - NumIfacePolluts = -1; - if (sscanf(line, "%d", &NumIfacePolluts)) - NumIfacePolluts--; - if ( NumIfacePolluts < 0 ) return ERR_ROUTING_FILE_FORMAT; - - // --- read flow units - fgets(line, MAXLINE, Finflows.file); - if (sscanf(line, "%s %s", s1, s2) < 2) - return ERR_ROUTING_FILE_FORMAT; - if ( !strcomp(s1, "FLOW") ) - return ERR_ROUTING_FILE_FORMAT; - IfaceFlowUnits = findmatch(s2, FlowUnitWords); - if ( IfaceFlowUnits < 0 ) - return ERR_ROUTING_FILE_FORMAT; - - // --- allocate memory for pollutant index array - if ( Nobjects[POLLUT] > 0 ) - { - IfacePolluts = (int *) calloc(Nobjects[POLLUT], sizeof(int)); - if ( !IfacePolluts ) return ERR_MEMORY; - for (i=0; i 0 && Nobjects[POLLUT] > 0 ) - { - // --- check each pollutant name on file with project's pollutants - for (i=0; i -#include -#include "headers.h" -#include "infil.h" - -//----------------------------------------------------------------------------- -// Local Variables -//----------------------------------------------------------------------------- -typedef union TInfil { - THorton horton; - TGrnAmpt grnAmpt; - TCurveNum curveNum; -} TInfil; -TInfil *Infil; - -static double Fumax; // saturated water volume in upper soil zone (ft) -static double InfilFactor; - -//----------------------------------------------------------------------------- -// External Functions (declared in infil.h) -//----------------------------------------------------------------------------- -// infil_create (called by createObjects in project.c) -// infil_delete (called by deleteObjects in project.c) -// infil_readParams (called by input_readLine) -// infil_initState (called by subcatch_initState) -// infil_getState (called by writeRunoffFile in hotstart.c) -// infil_setState (called by readRunoffFile in hotstart.c) -// infil_getInfil (called by getSubareaRunoff in subcatch.c) - -// Called locally and by storage node methods in node.c -// grnampt_setParams -// grnampt_initState -// grnampt_getInfil - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int horton_setParams(THorton *infil, double p[]); -static void horton_initState(THorton *infil); -static void horton_getState(THorton *infil, double x[]); -static void horton_setState(THorton *infil, double x[]); -static double horton_getInfil(THorton *infil, double tstep, double irate, - double depth); -static double modHorton_getInfil(THorton *infil, double tstep, double irate, - double depth); - -static void grnampt_getState(TGrnAmpt *infil, double x[]); -static void grnampt_setState(TGrnAmpt *infil, double x[]); -static double grnampt_getUnsatInfil(TGrnAmpt *infil, double tstep, - double irate, double depth, int modelType); -static double grnampt_getSatInfil(TGrnAmpt *infil, double tstep, - double irate, double depth); -static double grnampt_getF2(double f1, double c1, double ks, double ts); - -static int curvenum_setParams(TCurveNum *infil, double p[]); -static void curvenum_initState(TCurveNum *infil); -static void curvenum_getState(TCurveNum *infil, double x[]); -static void curvenum_setState(TCurveNum *infil, double x[]); -static double curvenum_getInfil(TCurveNum *infil, double tstep, double irate, - double depth); - -//============================================================================= - -void infil_create(int n) -// -// Purpose: creates an array of infiltration objects. -// Input: n = number of subcatchments -// Output: none -// -{ - Infil = (TInfil *) calloc(n, sizeof(TInfil)); - if (Infil == NULL) ErrorCode = ERR_MEMORY; - InfilFactor = 1.0; - return; -} - -//============================================================================= - -void infil_delete() -// -// Purpose: deletes infiltration objects associated with subcatchments -// Input: none -// Output: none -// -{ - FREE(Infil); -} - -//============================================================================= - -int infil_readParams(int m, char* tok[], int ntoks) -// -// Input: m = default infiltration model -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: sets infiltration parameters from a line of input data. -// -// Format of data line is: -// subcatch p1 p2 ... (infilMethod) -{ - int i, j, n, status; - double x[5]; - - // --- check that subcatchment exists - j = project_findObject(SUBCATCH, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- check for infiltration method keyword is last token - i = findmatch(tok[ntoks-1], InfilModelWords); - if ( i >= 0 ) - { - m = i; - --ntoks; - } - - // --- number of input tokens depends on infiltration model m - if ( m == HORTON ) n = 5; - else if ( m == MOD_HORTON ) n = 5; - else if ( m == GREEN_AMPT ) n = 4; - else if ( m == MOD_GREEN_AMPT ) n = 4; - else if ( m == CURVE_NUMBER ) n = 4; - else return 0; - - if ( ntoks < n ) return error_setInpError(ERR_ITEMS, ""); - - // --- parse numerical values from tokens - for (i = 0; i < 5; i++) x[i] = 0.0; - for (i = 1; i < n; i++) - { - if (!getDouble(tok[i], &x[i - 1])) - return error_setInpError(ERR_NUMBER, tok[i]); - } - - // --- special case for Horton infil. - last parameter is optional - if ( (m == HORTON || m == MOD_HORTON) && ntoks > n ) - { - if ( ! getDouble(tok[n], &x[n-1]) ) - return error_setInpError(ERR_NUMBER, tok[n]); - } - - // --- assign parameter values to infil, infilModel object - Subcatch[j].infil = j; - Subcatch[j].infilModel = m; - switch (m) - { - case HORTON: - case MOD_HORTON: status = horton_setParams(&Infil[j].horton, x); - break; - case GREEN_AMPT: - case MOD_GREEN_AMPT: - status = grnampt_setParams(&Infil[j].grnAmpt, x); - break; - case CURVE_NUMBER: status = curvenum_setParams(&Infil[j].curveNum, x); - break; - default: status = TRUE; - } - if ( !status ) return error_setInpError(ERR_INFIL_PARAMS, ""); - return 0; -} - -//============================================================================= - -void infil_initState(int j) -// -// Input: j = subcatchment index -// Output: none -// Purpose: initializes state of infiltration for a subcatchment. -// -{ - switch (Subcatch[j].infilModel) - { - case HORTON: - case MOD_HORTON: horton_initState(&Infil[j].horton); break; - case GREEN_AMPT: - case MOD_GREEN_AMPT: - grnampt_initState(&Infil[j].grnAmpt); break; - case CURVE_NUMBER: curvenum_initState(&Infil[j].curveNum); break; - } -} - -//============================================================================= - -void infil_getState(int j, double x[]) -// -// Input: j = subcatchment index -// Output: x = subcatchment's infiltration state -// Purpose: retrieves the current infiltration state for a subcatchment. -// -{ - switch (Subcatch[j].infilModel) - { - case HORTON: - case MOD_HORTON: horton_getState(&Infil[j].horton, x); break; - case GREEN_AMPT: - case MOD_GREEN_AMPT: - grnampt_getState(&Infil[j].grnAmpt, x); break; - case CURVE_NUMBER: curvenum_getState(&Infil[j].curveNum, x); break; - } -} - -//============================================================================= - -void infil_setState(int j, double x[]) -// -// Input: j = subcatchment index -// m = infiltration method code -// Output: none -// Purpose: sets the current infiltration state for a subcatchment. -// -{ - switch (Subcatch[j].infilModel) - { - case HORTON: - case MOD_HORTON: horton_setState(&Infil[j].horton, x); break; - case GREEN_AMPT: - case MOD_GREEN_AMPT: - grnampt_setState(&Infil[j].grnAmpt, x); break; - case CURVE_NUMBER: curvenum_setState(&Infil[j].curveNum, x); break; - } -} - -//============================================================================= - -void infil_setInfilFactor(int j) -// -// Input: j = subcatchment index -// Output: none -// Purpose: assigns a value to the infiltration adjustment factor. -{ - int m; - int p; - - // ... set factor to the global conductivity adjustment factor - InfilFactor = Adjust.hydconFactor; - - // ... override global factor with subcatchment's adjustment if assigned - if (j >= 0) - { - p = Subcatch[j].infilPattern; - if (p >= 0 && Pattern[p].type == MONTHLY_PATTERN) - { - m = datetime_monthOfYear(getDateTime(OldRunoffTime)) - 1; - InfilFactor = Pattern[p].factor[m]; - } - } -} - -//============================================================================= - -double infil_getInfil(int j, double tstep, double rainfall, - double runon, double depth) -// -// Input: j = subcatchment index -// tstep = runoff time step (sec) -// rainfall = rainfall rate (ft/sec) -// runon = runon rate from other sub-areas or subcatchments (ft/sec) -// depth = depth of surface water on subcatchment (ft) -// Output: returns infiltration rate (ft/sec) -// Purpose: computes infiltration rate depending on infiltration method. -// -{ - switch (Subcatch[j].infilModel) - { - case HORTON: - return horton_getInfil(&Infil[j].horton, tstep, rainfall+runon, depth); - - case MOD_HORTON: - return modHorton_getInfil(&Infil[j].horton, tstep, rainfall+runon, - depth); - - case GREEN_AMPT: - case MOD_GREEN_AMPT: - return grnampt_getInfil(&Infil[j].grnAmpt, tstep, rainfall+runon, depth, - Subcatch[j].infilModel); - - case CURVE_NUMBER: - depth += runon * tstep; - return curvenum_getInfil(&Infil[j].curveNum, tstep, rainfall, depth); - - default: - return 0.0; - } -} - -//============================================================================= - -int horton_setParams(THorton *infil, double p[]) -// -// Input: infil = ptr. to Horton infiltration object -// p[] = array of parameter values -// Output: returns TRUE if parameters are valid, FALSE otherwise -// Purpose: assigns Horton infiltration parameters to a subcatchment. -// -{ - int k; - for (k = 0; k < 5; k++) if ( p[k] < 0.0 ) return FALSE; - - // --- max. & min. infil rates (ft/sec) - infil->f0 = p[0] / UCF(RAINFALL); - infil->fmin = p[1] / UCF(RAINFALL); - - // --- convert decay const. to 1/sec - infil->decay = p[2] / 3600.; - - // --- convert drying time (days) to a regeneration const. (1/sec) - // assuming that former is time to reach 98% dry along an - // exponential drying curve - if (p[3] == 0.0 ) p[3] = TINY; - infil->regen = -log(1.0-0.98) / p[3] / SECperDAY; - - // --- optional max. infil. capacity (ft) (p[4] = 0 if no value supplied) - infil->Fmax = p[4] / UCF(RAINDEPTH); - if ( infil->f0 < infil->fmin ) return FALSE; - return TRUE; -} - -//============================================================================= - -void horton_initState(THorton *infil) -// -// Input: infil = ptr. to Horton infiltration object -// Output: none -// Purpose: initializes time on Horton infiltration curve for a subcatchment. -// -{ - infil->tp = 0.0; - infil->Fe = 0.0; -} - -//============================================================================= - -void horton_getState(THorton *infil, double x[]) -{ - x[0] = infil->tp; - x[1] = infil->Fe; -} - -void horton_setState(THorton *infil, double x[]) -{ - infil->tp = x[0]; - infil->Fe = x[1]; -} - -//============================================================================= - -double horton_getInfil(THorton *infil, double tstep, double irate, double depth) -// -// Input: infil = ptr. to Horton infiltration object -// tstep = runoff time step (sec), -// irate = net "rainfall" rate (ft/sec), -// = rainfall + snowmelt + runon - evaporation -// depth = depth of ponded water (ft). -// Output: returns infiltration rate (ft/sec) -// Purpose: computes Horton infiltration for a subcatchment. -// -{ - // --- assign local variables - int iter; - double fa, fp = 0.0; - double Fp, F1, t1, tlim, ex, kt; - double FF, FF1, r; - double f0 = infil->f0 * InfilFactor; - double fmin = infil->fmin * InfilFactor; - double Fmax = infil->Fmax; - double tp = infil->tp; - double df = f0 - fmin; - double kd = infil->decay; - double kr = infil->regen * Evap.recoveryFactor; - - // --- special cases of no infil. or constant infil - if ( df < 0.0 || kd < 0.0 || kr < 0.0 ) return 0.0; - if ( df == 0.0 || kd == 0.0 ) - { - fp = f0; - fa = irate + depth / tstep; - if ( fp > fa ) fp = fa; - return MAX(0.0, fp); - } - - // --- compute water available for infiltration - fa = irate + depth / tstep; - - // --- case where there is water to infiltrate - if ( fa > ZERO ) - { - // --- compute average infil. rate over time step - t1 = tp + tstep; // future cumul. time - tlim = 16.0 / kd; // for tp >= tlim, f = fmin - if ( tp >= tlim ) - { - Fp = fmin * tp + df / kd; - F1 = Fp + fmin * tstep; - } - else - { - Fp = fmin * tp + df / kd * (1.0 - exp(-kd * tp)); - F1 = fmin * t1 + df / kd * (1.0 - exp(-kd * t1)); - } - fp = (F1 - Fp) / tstep; - fp = MAX(fp, fmin); - - // --- limit infil rate to available infil - if ( fp > fa ) fp = fa; - - // --- if fp on flat portion of curve then increase tp by tstep - if ( t1 > tlim ) tp = t1; - - // --- if infil < available capacity then increase tp by tstep - else if ( fp < fa ) tp = t1; - - // --- if infil limited by available capacity then - // solve F(tp) - F1 = 0 using Newton-Raphson method - else - { - F1 = Fp + fp * tstep; - tp = tp + tstep / 2.0; - for ( iter=1; iter<=20; iter++ ) - { - kt = MIN( 60.0, kd*tp ); - ex = exp(-kt); - FF = fmin * tp + df / kd * (1.0 - ex) - F1; - FF1 = fmin + df * ex; - r = FF / FF1; - tp = tp - r; - if ( fabs(r) <= 0.001 * tstep ) break; - } - } - - // --- limit cumulative infiltration to Fmax - if ( Fmax > 0.0 ) - { - if ( infil->Fe + fp * tstep > Fmax ) - fp = (Fmax - infil->Fe) / tstep; - fp = MAX(fp, 0.0); - infil->Fe += fp * tstep; - } - } - - // --- case where infil. capacity is regenerating; update tp. - else if (kr > 0.0) - { - r = exp(-kr * tstep); - tp = 1.0 - exp(-kd * tp); - tp = -log(1.0 - r*tp) / kd; - - // reduction in cumulative infiltration - if ( Fmax > 0.0 ) - { - infil->Fe = fmin*tp + (df/kd)*(1.0 - exp(-kd*tp)); - } - } - infil->tp = tp; - return fp; -} - -//============================================================================= - -double modHorton_getInfil(THorton *infil, double tstep, double irate, - double depth) -// -// Input: infil = ptr. to Horton infiltration object -// tstep = runoff time step (sec), -// irate = net "rainfall" rate (ft/sec), -// = rainfall + snowmelt + runon -// depth = depth of ponded water (ft). -// Output: returns infiltration rate (ft/sec) -// Purpose: computes modified Horton infiltration for a subcatchment. -// -{ - // --- assign local variables - double f = 0.0; - double fp, fa; - double f0 = infil->f0 * InfilFactor; - double fmin = infil->fmin * InfilFactor; - double df = f0 - fmin; - double kd = infil->decay; - double kr = infil->regen * Evap.recoveryFactor; - - // --- special cases of no or constant infiltration - if ( df < 0.0 || kd < 0.0 || kr < 0.0 ) return 0.0; - if ( df == 0.0 || kd == 0.0 ) - { - fp = f0; - fa = irate + depth / tstep; - if ( fp > fa ) fp = fa; - return MAX(0.0, fp); - } - - // --- compute water available for infiltration - fa = irate + depth / tstep; - - // --- case where there is water to infiltrate - if ( fa > ZERO ) - { - // --- saturated condition - if ( infil->Fmax > 0.0 && infil->Fe >= infil->Fmax ) return 0.0; - - // --- potential infiltration - fp = f0 - kd * infil->Fe; - fp = MAX(fp, fmin); - - // --- actual infiltration - f = MIN(fa, fp); - - // --- new cumulative infiltration minus seepage - infil->Fe += MAX((f - fmin), 0.0) * tstep; - if ( infil->Fmax > 0.0 ) infil->Fe = MAX(infil->Fe, infil->Fmax); - } - - // --- reduce cumulative infiltration for dry condition - else if (kr > 0.0) - { - infil->Fe *= exp(-kr * tstep); - infil->Fe = MAX(infil->Fe, 0.0); - } - return f; -} - -//============================================================================= - -void grnampt_getParams(int j, double p[]) -// -// Input: j = subcatchment index -// p[] = array of parameter values -// Output: none -// Purpose: retrieves Green-Ampt infiltration parameters for a subcatchment. -// -{ - p[0] = Infil[j].grnAmpt.S * UCF(RAINDEPTH); // Capillary suction head (ft) - p[1] = Infil[j].grnAmpt.Ks * UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) - p[2] = Infil[j].grnAmpt.IMDmax; // Max. init. moisture deficit -} - -//============================================================================= - -int grnampt_setParams(TGrnAmpt *infil, double p[]) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// p[] = array of parameter values -// Output: returns TRUE if parameters are valid, FALSE otherwise -// Purpose: assigns Green-Ampt infiltration parameters to a subcatchment. -// -{ - double ksat; // sat. hyd. conductivity in in/hr - - if ( p[0] < 0.0 || p[1] <= 0.0 || p[2] < 0.0 || p[2] > 1.0) return FALSE; - infil->S = p[0] / UCF(RAINDEPTH); // Capillary suction head (ft) - infil->Ks = p[1] / UCF(RAINFALL); // Sat. hyd. conductivity (ft/sec) - infil->IMDmax = p[2]; // Max. init. moisture deficit - - // --- find depth of upper soil zone (ft) using Mein's eqn. - ksat = infil->Ks * 12. * 3600.; - infil->Lu = 4.0 * sqrt(ksat) / 12.; - return TRUE; -} - -//============================================================================= - -void grnampt_initState(TGrnAmpt *infil) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// Output: none -// Purpose: initializes state of Green-Ampt infiltration for a subcatchment. -// -{ - if (infil == NULL) return; - infil->IMD = infil->IMDmax; - infil->Fu = 0.0; - infil->F = 0.0; - infil->Sat = FALSE; - infil->T = 0.0; -} - -void grnampt_getState(TGrnAmpt *infil, double x[]) -{ - x[0] = infil->IMD; - x[1] = infil->F; - x[2] = infil->Fu; - x[3] = infil->Sat; - x[4] = infil->T; -} - -void grnampt_setState(TGrnAmpt *infil, double x[]) -{ - infil->IMD = x[0]; - infil->F = x[1]; - infil->Fu = x[2]; - infil->Sat = (char)x[3]; - infil->T = x[4]; -} - -//============================================================================= - -double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, - double depth, int modelType) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// tstep = time step (sec), -// irate = net "rainfall" rate to upper zone (ft/sec); -// = rainfall + snowmelt + runon, -// does not include ponded water (added on below) -// depth = depth of ponded water (ft) -// modelType = either GREEN_AMPT or MOD_GREEN_AMPT -// Output: returns infiltration rate (ft/sec) -// Purpose: computes Green-Ampt infiltration for a subcatchment -// or a storage node. -// -{ - // --- find saturated upper soil zone water volume - Fumax = infil->IMDmax * infil->Lu * sqrt(InfilFactor); - - // --- reduce time until next event - infil->T -= tstep; - - // --- use different procedures depending on upper soil zone saturation - if ( infil->Sat ) return grnampt_getSatInfil(infil, tstep, irate, depth); - else return grnampt_getUnsatInfil(infil, tstep, irate, depth, modelType); -} - -//============================================================================= - -double grnampt_getUnsatInfil(TGrnAmpt *infil, double tstep, double irate, - double depth, int modelType) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// tstep = runoff time step (sec), -// irate = net "rainfall" rate to upper zone (ft/sec); -// = rainfall + snowmelt + runon, -// does not include ponded water (added on below) -// depth = depth of ponded water (ft) -// modelType = either GREEN_AMPT or MOD_GREEN_AMPT -// Output: returns infiltration rate (ft/sec) -// Purpose: computes Green-Ampt infiltration when upper soil zone is -// unsaturated. -// -{ - double ia, c1, F2, dF, Fs, kr, ts; - double ks = infil->Ks * InfilFactor; - double lu = infil->Lu * sqrt(InfilFactor); - - // --- get available infiltration rate (rainfall + ponded water) - ia = irate + depth / tstep; - if ( ia < ZERO ) ia = 0.0; - - // --- no rainfall so recover upper zone moisture - if ( ia == 0.0 ) - { - if ( infil->Fu <= 0.0 ) return 0.0; - kr = lu / 90000.0 * Evap.recoveryFactor; - dF = kr * Fumax * tstep; - infil->F -= dF; - infil->Fu -= dF; - if ( infil->Fu <= 0.0 ) - { - infil->Fu = 0.0; - infil->F = 0.0; - infil->IMD = infil->IMDmax; - return 0.0; - } - - // --- if new wet event begins then reset IMD & F - if ( infil->T <= 0.0 ) - { - infil->IMD = (Fumax - infil->Fu) / lu; - infil->F = 0.0; - } - return 0.0; - } - - // --- rainfall does not exceed Ksat - if ( ia <= ks ) - { - dF = ia * tstep; - infil->F += dF; - infil->Fu += dF; - infil->Fu = MIN(infil->Fu, Fumax); - if ( modelType == GREEN_AMPT && infil->T <= 0.0 ) - { - infil->IMD = (Fumax - infil->Fu) / lu; - infil->F = 0.0; - } - return ia; - } - - // --- rainfall exceeds Ksat; renew time to drain upper zone - infil->T = 5400.0 / lu / Evap.recoveryFactor; - - // --- find volume needed to saturate surface layer - Fs = ks * (infil->S + depth) * infil->IMD / (ia - ks); - - // --- surface layer already saturated - if ( infil->F > Fs ) - { - infil->Sat = TRUE; - return grnampt_getSatInfil(infil, tstep, irate, depth); - } - - // --- surface layer remains unsaturated - if ( infil->F + ia*tstep < Fs ) - { - dF = ia * tstep; - infil->F += dF; - infil->Fu += dF; - infil->Fu = MIN(infil->Fu, Fumax); - return ia; - } - - // --- surface layer becomes saturated during time step; - // --- compute portion of tstep when saturated - ts = tstep - (Fs - infil->F) / ia; - if ( ts <= 0.0 ) ts = 0.0; - - // --- compute new total volume infiltrated - c1 = (infil->S + depth) * infil->IMD; - F2 = grnampt_getF2(Fs, c1, ks, ts); - if ( F2 > Fs + ia*ts ) F2 = Fs + ia*ts; - - // --- compute infiltration rate - dF = F2 - infil->F; - infil->F = F2; - infil->Fu += dF; - infil->Fu = MIN(infil->Fu, Fumax); - infil->Sat = TRUE; - return dF / tstep; -} - -//============================================================================= - -double grnampt_getSatInfil(TGrnAmpt *infil, double tstep, double irate, - double depth) -// -// Input: infil = ptr. to Green-Ampt infiltration object -// tstep = runoff time step (sec), -// irate = net "rainfall" rate to upper zone (ft/sec); -// = rainfall + snowmelt + runon, -// does not include ponded water (added on below) -// depth = depth of ponded water (ft). -// Output: returns infiltration rate (ft/sec) -// Purpose: computes Green-Ampt infiltration when upper soil zone is -// saturated. -// -{ - double ia, c1, dF, F2; - double ks = infil->Ks * InfilFactor; - double lu = infil->Lu * sqrt(InfilFactor); - - // --- get available infiltration rate (rainfall + ponded water) - ia = irate + depth / tstep; - if ( ia < ZERO ) return 0.0; - - // --- re-set new event recovery time - infil->T = 5400.0 / lu / Evap.recoveryFactor; - - // --- solve G-A equation for new cumulative infiltration volume (F2) - c1 = (infil->S + depth) * infil->IMD; - F2 = grnampt_getF2(infil->F, c1, ks, tstep); - dF = F2 - infil->F; - - // --- all available water infiltrates -- set saturated state to false - if ( dF > ia * tstep ) - { - dF = ia * tstep; - infil->Sat = FALSE; - } - - // --- update total infiltration and upper zone moisture deficit - infil->F += dF; - infil->Fu += dF; - infil->Fu = MIN(infil->Fu, Fumax); - return dF / tstep; -} - -//============================================================================= - -double grnampt_getF2(double f1, double c1, double ks, double ts) -// -// Input: f1 = old infiltration volume (ft) -// c1 = head * moisture deficit (ft) -// ks = sat. hyd. conductivity (ft/sec) -// ts = time step (sec) -// Output: returns infiltration volume at end of time step (ft) -// Purpose: computes new infiltration volume over a time step -// using Green-Ampt formula for saturated upper soil zone. -// -{ - int i; - double f2 = f1; - double f2min; - double df2; - double c2; - - // --- find min. infil. volume - f2min = f1 + ks * ts; - - // --- use min. infil. volume for 0 moisture deficit - if ( c1 == 0.0 ) return f2min; - - // --- use direct form of G-A equation for small time steps - // and c1/f1 < 100 - if ( ts < 10.0 && f1 > 0.01 * c1 ) - { - f2 = f1 + ks * (1.0 + c1/f1) * ts; - return MAX(f2, f2min); - } - - // --- use Newton-Raphson method to solve integrated G-A equation - // (convergence limit reduced from that used in previous releases) - c2 = c1 * log(f1 + c1) - ks * ts; - for ( i = 1; i <= 20; i++ ) - { - df2 = (f2 - f1 - c1 * log(f2 + c1) + c2) / (1.0 - c1 / (f2 + c1) ); - if ( fabs(df2) < 0.00001 ) - { - return MAX(f2, f2min); - } - f2 -= df2; - } - return f2min; -} - -//============================================================================= - -int curvenum_setParams(TCurveNum *infil, double p[]) -// -// Input: infil = ptr. to Curve Number infiltration object -// p[] = array of parameter values -// Output: returns TRUE if parameters are valid, FALSE otherwise -// Purpose: assigns Curve Number infiltration parameters to a subcatchment. -// -{ - - // --- convert Curve Number to max. infil. capacity - if ( p[0] < 10.0 ) p[0] = 10.0; - if ( p[0] > 99.0 ) p[0] = 99.0; - infil->Smax = (1000.0 / p[0] - 10.0) / 12.0; - if ( infil->Smax < 0.0 ) return FALSE; - - // --- convert drying time (days) to a regeneration const. (1/sec) - if ( p[2] > 0.0 ) infil->regen = 1.0 / (p[2] * SECperDAY); - else return FALSE; - - // --- compute inter-event time from regeneration const. as in Green-Ampt - infil->Tmax = 0.06 / infil->regen; - - return TRUE; -} - -//============================================================================= - -void curvenum_initState(TCurveNum *infil) -// -// Input: infil = ptr. to Curve Number infiltration object -// Output: none -// Purpose: initializes state of Curve Number infiltration for a subcatchment. -// -{ - infil->S = infil->Smax; - infil->P = 0.0; - infil->F = 0.0; - infil->T = 0.0; - infil->Se = infil->Smax; - infil->f = 0.0; -} - -void curvenum_getState(TCurveNum *infil, double x[]) -{ - x[0] = infil->S; - x[1] = infil->P; - x[2] = infil->F; - x[3] = infil->T; - x[4] = infil->Se; - x[5] = infil->f; -} - -void curvenum_setState(TCurveNum *infil, double x[]) -{ - infil->S = x[0]; - infil->P = x[1]; - infil->F = x[2]; - infil->T = x[3]; - infil->Se = x[4]; - infil->f = x[5]; -} - -//============================================================================= - -double curvenum_getInfil(TCurveNum *infil, double tstep, double irate, - double depth) -// -// Input: infil = ptr. to Curve Number infiltration object -// tstep = runoff time step (sec), -// irate = rainfall rate (ft/sec); -// depth = depth of runon + ponded water (ft) -// Output: returns infiltration rate (ft/sec) -// Purpose: computes infiltration rate using the Curve Number method. -// Note: this function treats runon from other subcatchments as part -// of the ponded depth and not as an effective rainfall rate. -{ - double F1; // new cumulative infiltration (ft) - double f1 = 0.0; // new infiltration rate (ft/sec) - double fa = irate + depth/tstep; // max. available infil. rate (ft/sec) - - // --- case where there is rainfall - if ( irate > ZERO ) - { - // --- check if new rain event - if ( infil->T >= infil->Tmax ) - { - infil->P = 0.0; - infil->F = 0.0; - infil->f = 0.0; - infil->Se = infil->S; - } - infil->T = 0.0; - - // --- update cumulative precip. - infil->P += irate * tstep; - - // --- find potential new cumulative infiltration - F1 = infil->P * (1.0 - infil->P / (infil->P + infil->Se)); - - // --- compute potential infiltration rate - f1 = (F1 - infil->F) / tstep; - if ( f1 < 0.0 || infil->S <= 0.0 ) f1 = 0.0; - - } - - // --- case of no rainfall - else - { - // --- if there is ponded water then use previous infil. rate - if ( depth > MIN_TOTAL_DEPTH && infil->S > 0.0 ) - { - f1 = infil->f; - if ( f1*tstep > infil->S ) f1 = infil->S / tstep; - } - - // --- otherwise update inter-event time - else infil->T += tstep; - } - - // --- if there is some infiltration - if ( f1 > 0.0 ) - { - // --- limit infil. rate to max. available rate - f1 = MIN(f1, fa); - f1 = MAX(f1, 0.0); - - // --- update actual cumulative infiltration - infil->F += f1 * tstep; - - // --- reduce infil. capacity if a regen. constant was supplied - if ( infil->regen > 0.0 ) - { - infil->S -= f1 * tstep; - if ( infil->S < 0.0 ) infil->S = 0.0; - } - } - - // --- otherwise regenerate infil. capacity - else - { - infil->S += infil->regen * infil->Smax * tstep * Evap.recoveryFactor; - if ( infil->S > infil->Smax ) infil->S = infil->Smax; - } - infil->f = f1; - return f1; -} diff --git a/src/infil.h b/src/infil.h deleted file mode 100644 index f5ddf31aa..000000000 --- a/src/infil.h +++ /dev/null @@ -1,112 +0,0 @@ -//----------------------------------------------------------------------------- -// infil.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Public interface for infiltration functions. -// -// Update History -// ============== -// Build 5.1.010: -// - New Modified Green Ampt infiltration option added. -// Build 5.1.013: -// - New function infil_setInfilFactor() added. -// Build 5.1.015: -// - Support added for multiple infiltration methods within a project. -//----------------------------------------------------------------------------- - -#ifndef INFIL_H -#define INFIL_H - -//--------------------- -// Enumerated Constants -//--------------------- -enum InfilType { - HORTON, // Horton infiltration - MOD_HORTON, // Modified Horton infiltration - GREEN_AMPT, // Green-Ampt infiltration - MOD_GREEN_AMPT, // Modified Green-Ampt infiltration - CURVE_NUMBER}; // SCS Curve Number infiltration - -//--------------------- -// Horton Infiltration -//--------------------- -typedef struct -{ - double f0; // initial infil. rate (ft/sec) - double fmin; // minimum infil. rate (ft/sec) - double Fmax; // maximum total infiltration (ft); - double decay; // decay coeff. of infil. rate (1/sec) - double regen; // regeneration coeff. of infil. rate (1/sec) - //----------------------------- - double tp; // present time on infiltration curve (sec) - double Fe; // cumulative infiltration (ft) -} THorton; - - -//------------------------- -// Green-Ampt Infiltration -//------------------------- -typedef struct -{ - double S; // avg. capillary suction (ft) - double Ks; // saturated conductivity (ft/sec) - double IMDmax; // max. soil moisture deficit (ft/ft) - //----------------------------- - double IMD; // current initial soil moisture deficit - double F; // current cumulative infiltrated volume (ft) - double Fu; // current upper zone infiltrated volume (ft) - double Lu; // depth of upper soil zone (ft) - double T; // time until start of next rain event (sec) - char Sat; // saturation flag -} TGrnAmpt; - - -//-------------------------- -// Curve Number Infiltration -//-------------------------- -typedef struct -{ - double Smax; // max. infiltration capacity (ft) - double regen; // infil. capacity regeneration constant (1/sec) - double Tmax; // maximum inter-event time (sec) - //----------------------------- - double S; // current infiltration capacity (ft) - double F; // current cumulative infiltration (ft) - double P; // current cumulative precipitation (ft) - double T; // current inter-event time (sec) - double Se; // current event infiltration capacity (ft) - double f; // previous infiltration rate (ft/sec) - -} TCurveNum; - -//----------------------------------------------------------------------------- -// Exported Variables -//----------------------------------------------------------------------------- -extern THorton* HortInfil; -extern TGrnAmpt* GAInfil; -extern TCurveNum* CNInfil; - -//----------------------------------------------------------------------------- -// Infiltration Methods -//----------------------------------------------------------------------------- -void infil_create(int n); -void infil_delete(void); -int infil_readParams(int m, char* tok[], int ntoks); -void infil_initState(int j); -void infil_getState(int j, double x[]); -void infil_setState(int j, double x[]); -void infil_setInfilFactor(int j); -double infil_getInfil(int area, double tstep, double rainfall, double runon, - double depth); - -void grnampt_getParams(int j, double p[]); -int grnampt_setParams(TGrnAmpt *infil, double p[]); -void grnampt_initState(TGrnAmpt *infil); -double grnampt_getInfil(TGrnAmpt *infil, double tstep, double irate, - double depth, int modelType); - -#endif diff --git a/src/inflow.c b/src/inflow.c deleted file mode 100644 index 7cb5e250f..000000000 --- a/src/inflow.c +++ /dev/null @@ -1,484 +0,0 @@ -//----------------------------------------------------------------------------- -// inflow.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Manages any Direct External or Dry Weather Flow inflows -// that have been assigned to nodes of the drainage system. -// -// Update History -// ============== -// Build 5.2.0: -// - Removed references to unused extIfaceInflow member of ExtInflow struct. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// External Functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// inflow_initDwfPattern (called createObjects in project.c) -// inflow_readExtInflow (called by input_readLine) -// inflow_readDwfInflow (called by input_readLine) -// inflow_deleteExtInflows (called by deleteObjects in project.c) -// inflow_deleteDwfInflows (called by deleteObjects in project.c) -// inflow_getExtInflow (called by addExternalInflows in routing.c) -// inflow_setExtInflow (called by setNodeInflow in swmm5.c) -// inflow_getDwfInflow (called by addDryWeatherInflows in routing.c) - -//----------------------------------------------------------------------------- -// Local Functions -//----------------------------------------------------------------------------- -double getPatternFactor(int p, int month, int day, int hour); - - -int inflow_readExtInflow(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error message -// Purpose: reads parameters of a direct external inflow from a line of input. -// -// Formats of data line are: -// nodeID FLOW tSeriesID (FLOW 1.0 scaleFactor baseline basePat) -// nodeID pollutID tSeriesID (CONCEN/MASS unitsFactor scaleFactor baseline basePat) -// -{ - int j; // object index - int param; // FLOW (-1) or pollutant index - int type = CONCEN_INFLOW; // FLOW, CONCEN or MASS inflow - int tseries = -1; // time series index - int basePat = -1; // baseline pattern - double cf = 1.0; // units conversion factor - double sf = 1.0; // scaling factor - double baseline = 0.0; // baseline value - - // --- find index of node receiving the inflow - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - j = project_findObject(NODE, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- find index of inflow pollutant or use -1 for FLOW - param = project_findObject(POLLUT, tok[1]); - if ( param < 0 ) - { - if ( match(tok[1], w_FLOW) ) param = -1; - else return error_setInpError(ERR_NAME, tok[1]); - } - - // --- find index of inflow time series (if supplied) in data base - if ( strlen(tok[2]) > 0 ) - { - tseries = project_findObject(TSERIES, tok[2]); - if ( tseries < 0 ) return error_setInpError(ERR_NAME, tok[2]); - Tseries[tseries].refersTo = EXTERNAL_INFLOW; - } - - // --- assign type & cf values for a FLOW inflow - if (param == -1) - { - type = FLOW_INFLOW; - cf = 1.0/UCF(FLOW); - } - - // --- do the same for a pollutant inflow - if ( ntoks >= 4 && param > -1) - { - if ( match(tok[3], w_CONCEN) ) type = CONCEN_INFLOW; - else if ( match(tok[3], w_MASS) ) type = MASS_INFLOW; - else return error_setInpError(ERR_KEYWORD, tok[3]); - if ( ntoks >= 5 && type == MASS_INFLOW ) - { - if ( ! getDouble(tok[4], &cf) ) - { - return error_setInpError(ERR_NUMBER, tok[4]); - } - if ( cf <= 0.0 ) return error_setInpError(ERR_NUMBER, tok[4]); - } - } - - // --- get sf and baseline values - if ( ntoks >= 6 ) - { - if ( ! getDouble(tok[5], &sf) ) - { - return error_setInpError(ERR_NUMBER, tok[5]); - } - } - if ( ntoks >= 7 ) - { - if ( ! getDouble(tok[6], &baseline) ) - { - return error_setInpError(ERR_NUMBER, tok[6]); - } - } - - // --- get baseline time pattern - if ( ntoks >= 8 ) - { - basePat = project_findObject(TIMEPATTERN, tok[7]); - if ( basePat < 0 ) return error_setInpError(ERR_NAME, tok[7]); - } - - // --- include LperFT3 term in conversion factor for MASS_INFLOW - if ( type == MASS_INFLOW ) cf /= LperFT3; - - return(inflow_setExtInflow(j, param, type, tseries, basePat, - cf, baseline, sf)); -} - -//============================================================================= - -int inflow_setExtInflow(int j, int param, int type, int tseries, int basePat, - double cf, double baseline, double sf) -// Purpose: This function assigns property values to the inflow object -// Inputs: j = Node index -// param = FLOW (-1) or pollutant index -// type = FLOW, CONCEN or MASS inflow -// tSeries = time series index -// basePat = baseline pattern -// cf = units conversion factor -// baseline = baseline inflow value -// sf = scaling factor -// Return: returns Error Code - -{ - TExtInflow* inflow; // external inflow object - - // --- check if an external inflow object for this constituent already exists - inflow = Node[j].extInflow; - while ( inflow ) - { - if ( inflow->param == param ) break; - inflow = inflow->next; - } - - // --- if it doesn't exist, then create it - if ( inflow == NULL ) - { - inflow = (TExtInflow *) malloc(sizeof(TExtInflow)); - if ( inflow == NULL ) - { - return error_setInpError(ERR_MEMORY, ""); - } - inflow->next = Node[j].extInflow; - Node[j].extInflow = inflow; - } - - // --- assign property values to the inflow object - inflow->param = param; - inflow->type = type; - inflow->tSeries = tseries; - inflow->cFactor = cf; - inflow->sFactor = sf; - inflow->baseline = baseline; - inflow->basePat = basePat; - return 0; -} - -//============================================================================= - -void inflow_deleteExtInflows(int j) -// -// Input: j = node index -// Output: none -// Purpose: deletes all time series inflow data for a node. -// -{ - TExtInflow* inflow1; - TExtInflow* inflow2; - inflow1 = Node[j].extInflow; - while ( inflow1 ) - { - inflow2 = inflow1->next; - free(inflow1); - inflow1 = inflow2; - } -} - -//============================================================================= - -double inflow_getExtInflow(TExtInflow* inflow, DateTime aDate) -// -// Input: inflow = external inflow data structure -// aDate = current simulation date/time -// Output: returns current value of external inflow parameter -// Purpose: retrieves the value of an external inflow at a specific -// date and time. -// -{ - int month, day, hour; - int p = inflow->basePat; // baseline pattern - int k = inflow->tSeries; // time series index - double cf = inflow->cFactor; // units conversion factor - double sf = inflow->sFactor; // scaling factor - double blv = inflow->baseline; // baseline value - double tsv = 0.0; // time series value - - if ( p >= 0 ) - { - month = datetime_monthOfYear(aDate) - 1; - day = datetime_dayOfWeek(aDate) - 1; - hour = datetime_hourOfDay(aDate); - blv *= getPatternFactor(p, month, day, hour); - } - if ( k >= 0 ) tsv = table_tseriesLookup(&Tseries[k], aDate, FALSE) * sf; - return cf * (tsv + blv); -} - -//============================================================================= - -int inflow_readDwfInflow(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error message -// Purpose: reads dry weather inflow parameters from line of input data. -// -// Format of data line is: -// nodeID FLOW/pollutID avgValue (pattern1 pattern2 ... pattern4) -// -{ - int i; - int j; // node index - int k; // pollutant index (-1 for flow) - int m; // time pattern index - int pats[4]; // time pattern index array - double x; // avg. DWF value - TDwfInflow* inflow; // dry weather flow inflow object - - // --- find index of node receiving the inflow - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - j = project_findObject(NODE, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- find index of inflow pollutant (-1 for FLOW) - k = project_findObject(POLLUT, tok[1]); - if ( k < 0 ) - { - if ( match(tok[1], w_FLOW) ) k = -1; - else return error_setInpError(ERR_NAME, tok[1]); - } - - // --- get avg. value of DWF inflow - if ( !getDouble(tok[2], &x) ) - return error_setInpError(ERR_NUMBER, tok[2]); - if ( k == -1 ) x /= UCF(FLOW); - - // --- get time patterns assigned to the inflow - for (i=0; i<4; i++) pats[i] = -1; - for (i=3; i<7; i++) - { - if ( i >= ntoks ) break; - if ( strlen(tok[i]) == 0 ) continue; - m = project_findObject(TIMEPATTERN, tok[i]); - if ( m < 0 ) return error_setInpError(ERR_NAME, tok[i]); - pats[i-3] = m; - } - - // --- check if inflow for this constituent already exists - inflow = Node[j].dwfInflow; - while ( inflow ) - { - if ( inflow->param == k ) break; - inflow = inflow->next; - } - - // --- if it doesn't exist, then create it - if ( inflow == NULL ) - { - inflow = (TDwfInflow *) malloc(sizeof(TDwfInflow)); - if ( inflow == NULL ) return error_setInpError(ERR_MEMORY, ""); - inflow->next = Node[j].dwfInflow; - Node[j].dwfInflow = inflow; - } - - // --- assign property values to the inflow object - inflow->param = k; - inflow->avgValue = x; - for (i=0; i<4; i++) inflow->patterns[i] = pats[i]; - return 0; -} - -//============================================================================= - -void inflow_deleteDwfInflows(int j) -// -// Input: j = node index -// Output: none -// Purpose: deletes all dry weather inflow data for a node. -// -{ - TDwfInflow* inflow1; - TDwfInflow* inflow2; - inflow1 = Node[j].dwfInflow; - while ( inflow1 ) - { - inflow2 = inflow1->next; - free(inflow1); - inflow1 = inflow2; - } -} - -//============================================================================= - -void inflow_initDwfInflow(TDwfInflow* inflow) -// -// Input: inflow = dry weather inflow data structure -// Output: none -// Purpose: initialzes a dry weather inflow by ordering its time patterns. -// -// This function sorts the user-supplied time patterns for a dry weather -// inflow in the order of the PatternType enumeration (monthly, daily, -// weekday hourly, weekend hourly) to help speed up pattern processing. -// -{ - int i, p; - int tmpPattern[4]; // index of each type of DWF pattern - - // --- assume no patterns were supplied - for (i=0; i<4; i++) tmpPattern[i] = -1; - - // --- assign supplied patterns to proper position (by type) in tmpPattern - for (i=0; i<4; i++) - { - p = inflow->patterns[i]; - if ( p >= 0 ) tmpPattern[Pattern[p].type] = p; - } - - // --- re-fill inflow pattern array by pattern type - for (i=0; i<4; i++) inflow->patterns[i] = tmpPattern[i]; -} - -//============================================================================= - -double inflow_getDwfInflow(TDwfInflow* inflow, int month, int day, int hour) -// -// Input: inflow = dry weather inflow data structure -// month = current month of year of simulation -// day = current day of week of simulation -// hour = current hour of day of simulation -// Output: returns value of dry weather inflow parameter -// Purpose: computes dry weather inflow value at a specific point in time. -// -{ - int p1, p2; // pattern index - double f = 1.0; // pattern factor - - p1 = inflow->patterns[MONTHLY_PATTERN]; - if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); - p1 = inflow->patterns[DAILY_PATTERN]; - if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); - p1 = inflow->patterns[HOURLY_PATTERN]; - p2 = inflow->patterns[WEEKEND_PATTERN]; - if ( p2 >= 0 ) - { - if ( day == 0 || day == 6 ) - f *= getPatternFactor(p2, month, day, hour); - else if ( p1 >= 0 ) - f *= getPatternFactor(p1, month, day, hour); - } - else if ( p1 >= 0 ) f *= getPatternFactor(p1, month, day, hour); - return f * inflow->avgValue; - -} - -//============================================================================= - -void inflow_initDwfPattern(int j) -// -// Input: j = time pattern index -// Output: none -// Purpose: initialzes a dry weather inflow time pattern. -// -{ - int i; - for (i=0; i<24; i++) Pattern[j].factor[i] = 1.0; - Pattern[j].count = 0; - Pattern[j].type = -1; - Pattern[j].ID = NULL; -} - -//============================================================================= - -int inflow_readDwfPattern(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error message -// Purpose: reads values of a time pattern from a line of input data. -// -// Format of data line is: -// patternID patternType value(1) value(2) ... -// patternID value(n) value(n+1) .... (for continuation lines) -{ - int i, j, k, n = 1; - - // --- check for minimum number of tokens - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that pattern exists in database - j = project_findObject(TIMEPATTERN, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- check if this is first line of pattern - // (ID pointer will not have been assigned yet) - if ( Pattern[j].ID == NULL ) - { - // --- assign ID pointer & pattern type - Pattern[j].ID = project_findID(TIMEPATTERN, tok[0]); - k = findmatch(tok[1], PatternTypeWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - Pattern[j].type = k; - n = 2; - } - - // --- start reading pattern factors from rest of line - while ( ntoks > n && Pattern[j].count < 24 ) - { - i = Pattern[j].count; - if ( !getDouble(tok[n], &Pattern[j].factor[i]) ) - return error_setInpError(ERR_NUMBER, tok[n]); - Pattern[j].count++; - n++; - } - return 0; -} - -//============================================================================= - -double getPatternFactor(int p, int month, int day, int hour) -// -// Input: p = time pattern index -// month = current month of year of simulation -// day = current day of week of simulation -// hour = current hour of day of simulation -// Output: returns value of a time pattern multiplier -// Purpose: computes time pattern multiplier for a specific point in time. -{ - switch ( Pattern[p].type ) - { - case MONTHLY_PATTERN: - if ( month >= 0 && month < 12 ) return Pattern[p].factor[month]; - break; - case DAILY_PATTERN: - if ( day >= 0 && day < 7 ) return Pattern[p].factor[day]; - break; - case HOURLY_PATTERN: - if ( hour >= 0 && hour < 24 ) return Pattern[p].factor[hour]; - break; - case WEEKEND_PATTERN: - if ( day == 0 || day == 6 ) - { - if ( hour >= 0 && hour < 24 ) return Pattern[p].factor[hour]; - } - break; - } - return 1.0; -} diff --git a/src/inlet.c b/src/inlet.c deleted file mode 100644 index 4defe127e..000000000 --- a/src/inlet.c +++ /dev/null @@ -1,1955 +0,0 @@ -//----------------------------------------------------------------------------- -// inlet.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 10/08/22 (Build 5.2.2) -// Author: L. Rossman -// -// Street/Channel Inlet Functions -// -// Computes capture efficiency of inlets placed in Street conduits -// or Rectangular/Trapezoidal channels using FHWA HEC-22 methods (see -// Brown, S.A. et al., Urban Drainage Design Manual, Federal Highway -// Administration Hydraulic Engineering Circular No. 22, 3rd Edition, -// FHWA-NHI-10-009, August 2013). -// -// Build 5.2.1: -// - Substitutes the constant BIG for HUGE. -// Build 5.2.2: -// - Additional statistics added to Street Flow Summary table. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" - -// Grate inlet -typedef struct -{ - int type; // type of grate used - double length; // length (parallel to flow) (ft) - double width; // width (perpendicular to flow) (ft) - double fracOpenArea; // fraction of grate area that is open - double splashVeloc; // splash-over velocity (ft/s) -} TGrateInlet; - -// Slotted drain inlet -typedef struct -{ - double length; // length (parallel to flow) (ft) - double width; // width (perpendicular to flow) (ft) -} TSlottedInlet; - -// Curb opening inlet -typedef struct -{ - double length; // length of curb opening (ft) - double height; // height of curb opening (ft) - int throatAngle; // type of throat angle -} TCurbInlet; - -// Custom inlet -typedef struct -{ - int onGradeCurve; // flow diversion curve index - int onSagCurve; // flow rating curve index -} TCustomInlet; - -// Inlet design object -typedef struct -{ - char * ID; // name assigned to inlet design - int type; // type of inlet used (grate, curb, etc) - TGrateInlet grateInlet; // length = 0 if not used - TSlottedInlet slottedInlet; // length = 0 if not used - TCurbInlet curbInlet; // length = 0 if not used - int customCurve; // curve index = -1 if not used -} TInletDesign; - - -// Inlet performance statistics -typedef struct -{ - int flowPeriods; // # periods with approach flow - int capturePeriods; // # periods with captured flow - int backflowPeriods; // # periods with backflow - double peakFlow; // peak flow seen by inlet (cfs) - double peakFlowCapture; // capture efficiency at peak flow - double avgFlowCapture; // average capture efficiency - double bypassFreq; // frequency of bypass flow -} TInletStats; - -// Inlet list object -struct TInlet -{ - int linkIndex; // index of conduit link with the inlet - int designIndex; // index of inlet's design - int nodeIndex; // index of node receiving captured flow - int numInlets; // # inlets on each side of street or in channel - int placement; // whether inlet is on-grade or on-sag - double clogFactor; // fractional degree of inlet clogging - double flowLimit; // inlet flow restriction (cfs) - double localDepress; // local gutter depression (ft) - double localWidth; // local depression width (ft) - - double flowFactor; // flow = flowFactor * (flow spread)^2.67 - double flowCapture; // captured flow rate (cfs) - double backflow; // backflow from capture node (cfs) - double backflowRatio; // inlet backflow / capture node overflow - TInletStats stats; // inlet performance statistics - TInlet * nextInlet; // next inlet in list -}; - -// Shared inlet variables -TInletDesign * InletDesigns; // array of available inlet designs -int InletDesignCount; // number of inlet designs -int UsesInlets; // TRUE if project uses inlets - -//----------------------------------------------------------------------------- -// Enumerations -//----------------------------------------------------------------------------- - -enum InletType { - GRATE_INLET, CURB_INLET, COMBO_INLET, SLOTTED_INLET, - DROP_GRATE_INLET, DROP_CURB_INLET, CUSTOM_INLET -}; - -enum GrateType { - P50, P50x100, P30, CURVED_VANE, TILT_BAR_45, - TILT_BAR_30, RETICULINE, GENERIC -}; - -enum InletPlacementType { AUTOMATIC, ON_GRADE, ON_SAG }; - -enum ThroatAngleType { HORIZONTAL_THROAT, INCLINED_THROAT, VERTICAL_THROAT }; - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static char* InletTypeWords[] = - {"GRATE", "CURB", "", "SLOTTED", "DROP_GRATE", "DROP_CURB", "CUSTOM", NULL}; - -static char* GrateTypeWords[] = - {"P_BAR-50", "P_BAR-50x100", "P_BAR-30", "CURVED_VANE", "TILT_BAR-45", "TILT_BAR-30", - "RETICULINE", "GENERIC", NULL}; - -static char* ThroatAngleWords[] = - {"HORIZONTAL", "INCLINED", "VERTICAL", NULL}; - -static char *PlacementTypeWords[] = - {"AUTOMATIC", "ON_GRADE", "ON_SAG"}; - -// Coefficients for cubic polynomials fitted to Splash Over Velocity v. -// Grate Length curves in Chart 5B of HEC-22 manual taken from Denver -// UDFCD manual. -static const double SplashCoeffs[][4] = { - {2.22, 4.03, 0.65, 0.06}, //P_BAR-50 - {0.74, 2.44, 0.27, 0.02}, //P_BAR-50x100 - {1.76, 3.12, 0.45, 0.03}, //P_BAR-30 - {0.30, 4.85, 1.31, 0.15}, //Curved_Vane - {0.99, 2.64, 0.36, 0.03}, //Tilt_Bar-45 - {0.51, 2.34, 0.2, 0.01}, //Tilt_Bar-30 - {0.28, 2.28, 0.18, 0.01}}; //Reticuline - -// Grate opening ratios (Chart 9B of HEC-22 manual) -static const double GrateOpeningRatios[] = { - 0.90, //P_BAR-50 - 0.80, //P_BAR-50x100 - 0.60, //P_BAR-30 - 0.35, //Curved_Vane - 0.17, //Tilt_Bar-45 (assumed) - 0.34, //Tilt_Bar-30 - 0.80, //Reticuline - 1.00}; //Generic - -//----------------------------------------------------------------------------- -// Imported Variables -//----------------------------------------------------------------------------- -extern TLinkStats* LinkStats; // defined in STATS.C -extern TNodeStats* NodeStats; // defined in STATS.C - -//----------------------------------------------------------------------------- -// Local Shared Variables -//----------------------------------------------------------------------------- -// Variables as named in the HEC-22 manual. -static double Sx; // street cross slope -static double SL; // conduit longitudinal slope -static double Sw; // gutter + cross slope -static double a; // street gutter depression (ft) -static double W; // street gutter width (ft) -static double T; // top width of flow spread (ft) -static double n; // Manning's roughness coeff. - -// Additional variables -static int Nsides; // 1- or 2-sided street -static double Tcrown; // distance from street curb to crown (ft) -static double Beta; // = 1.486 * sqrt(SL) / n -static double Qfactor; // factor f in Izzard's eqn. Q = f*T^2.67 -static TXsect* xsect; // cross-section data of inlet's conduit -static double* InletFlow; // captured inlet flow received by each node -static TInlet* FirstInlet; // head of list of deployed inlets - -//----------------------------------------------------------------------------- -// External functions (declared in inlet.h) -//----------------------------------------------------------------------------- -// inlet_create called by createObjects in project.c -// inlet_delete called by deleteObjects in project.c -// inlet_readDesignParams called by parseLine in input.c -// inlet_readUsageParams called by parseLine in input.c -// inlet_validate called by project_validate -// inlet_findCapturedFlows called by routing_execute -// inlet_adjustQualInflows called by routing_execute -// inlet_adjustQualOutflows called by routing execute -// inlet_writeStatsReport called by statsrpt_writeReport -// inlet_capturedFlow called by findLinkMassFlow in qualrout.c - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int readGrateInletParams(int inletIndex, char* tok[], int ntoks); -static int readCurbInletParams(int inletIndex, char* tok[], int ntoks); -static int readSlottedInletParams(int inletIndex, char* tok[], int ntoks); -static int readCustomInletParams(int inletIndex, char* tok[], int ntoks); - -static void initInletStats(TInlet* inlet); -static void updateInletStats(TInlet* inlet, double q); -static void writeStreetStatsHeader(); -static void writeStreetStats(int link); - -static void getBackflowRatios(); -static double getInletArea(TInlet* inlet); - -static int getInletPlacement(TInlet* inlet, int node); -static void getConduitGeometry(TInlet* inlet); -static double getFlowSpread(double flow); -static double getEo(double slopeRatio, double spread, double gutterWidth); - -static double getCustomCapturedFlow(TInlet* inlet, double flow, double depth); -static double getOnGradeCapturedFlow(TInlet* inlet, double flow, double depth); -static double getOnGradeInletCapture(int inletIndex, double flow, double depth); -static double getGrateInletCapture(int inletIndex, double flow); -static double getCurbInletCapture(double flow, double length); - -static double getGutterFlowRatio(double gutterWidth); -static double getGutterAreaRatio(double grateWidth, double area); -static double getSplashOverVelocity(int grateType, double grateLength); - -static double getOnSagCapturedFlow(TInlet* inlet, double flow, double depth); -static double getOnSagInletCapture(int inletIndex, double depth); -static void findOnSagGrateFlows(int inletIndex, double depth, - double *weirFlow, double *orificeFlow); -static void findOnSagCurbFlows(int inletIndex, double depth, - double openingLength, double *weirFlow, - double *orificeFlow); -static double getCurbOrificeFlow(double flowDepth, double openingHeight, - double openingLength, int throatAngle); -static double getOnSagSlottedFlow(int inletIndex, double depth); - -//============================================================================= - -int inlet_create(int numInlets) -// -// Input: numInlets = number of inlet designs to create -// Output: none -// Purpose: creats a collection of inlet designs. -// -{ - int i; - - InletDesigns = NULL; - InletFlow = NULL; - InletDesignCount = 0; - UsesInlets = FALSE; - FirstInlet = NULL; - InletDesigns = (TInletDesign *)calloc(numInlets, sizeof(TInletDesign)); - if (InletDesigns == NULL) return ERR_MEMORY; - InletDesignCount = numInlets; - - InletFlow = (double *)calloc(Nobjects[NODE], sizeof(double)); - if (InletFlow == NULL) return ERR_MEMORY; - - for (i = 0; i < InletDesignCount; i++) - { - InletDesigns[i].customCurve = -1; - InletDesigns[i].curbInlet.length = 0.0; - InletDesigns[i].grateInlet.length = 0.0; - InletDesigns[i].slottedInlet.length = 0.0; - InletDesigns[i].type = CUSTOM_INLET; - } - return 0; -} - -//============================================================================= - -void inlet_delete() -// -// Input: none -// Output: none -// Purpose: frees all memory allocated for inlet analysis. -// -{ - TInlet* inlet = FirstInlet; - TInlet* nextInlet; - while (inlet) - { - nextInlet = inlet->nextInlet; - free(inlet); - inlet = nextInlet; - } - FirstInlet = NULL; - FREE(InletFlow); - FREE(InletDesigns); -} - -//============================================================================= - -int inlet_readDesignParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts a set of inlet design parameters from a tokenized line -// of the [INLETS] section of a SWMM input file. -// -// Format of input line is: -// ID GRATE Length Width GrateType (OpenArea) (SplashVeloc) -// ID CURB Length Height (ThroatType) -// ID SLOTTED Length Width -// ID DROP_GRATE Length Width GrateType (OpenArea) (SplashVeloc) -// ID DROP_CURB Length Height -// ID CUSTOM CurveID -// -{ - int i; - - // --- check for minimum number of tokens - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that design ID already registered in project - i = project_findObject(INLET, tok[0]); - if ( i < 0 ) return error_setInpError(ERR_NAME, tok[0]); - InletDesigns[i].ID = project_findID(INLET, tok[0]); - - // --- retrieve type of inlet design - InletDesigns[i].type = findmatch(tok[1], InletTypeWords); - - // --- read inlet's design parameters - switch (InletDesigns[i].type) - { - case GRATE_INLET: - case DROP_GRATE_INLET: - return readGrateInletParams(i, tok, ntoks); - case CURB_INLET: - case DROP_CURB_INLET: - return readCurbInletParams(i, tok, ntoks); - case SLOTTED_INLET: - return readSlottedInletParams(i, tok, ntoks); - case CUSTOM_INLET: - return readCustomInletParams(i, tok, ntoks); - default: return error_setInpError(ERR_KEYWORD, tok[1]); - } - return 0; -} -//============================================================================= - -int inlet_readUsageParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts inlet usage parameters from a tokenized line -// of the [INLET_USAGE] section of a SWMM input file. -// -// Format of input line is: -// linkID inletID nodeID (#Inlets %Clog Qmax aLocal wLocal placement) -// where -// linkID = ID name of link containing the inlet -// inletID = ID name of inlet design being used -// nodeID = ID name of node receiving captured flow -// #Inlets = number of identical inlets used (default = 1) -// %Clog = percent that inlet is clogged -// Qmax = maximum flow that inlet can capture (default = 0 (no limit)) -// aLocal = local gutter depression (ft or m) (default = 0) -// wLocal = width of local gutter depression (ft or m) (default = 0) -// placement = ON_GRADE, ON_SAG, or AUTO (the default) -// -{ - int linkIndex, designIndex, nodeIndex, numInlets = 1; - int placement = AUTOMATIC; - double flowLimit = 0.0, pctClogged = 0.0; - double aLocal = 0.0, wLocal = 0.0; - TInlet* inlet; - - // --- check that inlet's link exists - if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); - linkIndex = project_findObject(LINK, tok[0]); - if (linkIndex < 0) return error_setInpError(ERR_NAME, tok[0]); - - // --- check that inlet design type exists - designIndex = project_findObject(INLET, tok[1]); - if (designIndex < 0) return error_setInpError(ERR_NAME, tok[1]); - - // --- check that receiving node exists - nodeIndex = project_findObject(NODE, tok[2]); - if (nodeIndex < 0) return error_setInpError(ERR_NAME, tok[2]); - - // --- get number of inlets - if (ntoks > 3) - if (!getInt(tok[3], &numInlets) || numInlets < 1) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- get flow limit & percent clogged - if (ntoks > 4) - { - if (!getDouble(tok[4], &pctClogged) || pctClogged < 0.0 - || pctClogged > 99.) - return error_setInpError(ERR_NUMBER, tok[4]); - } - if (ntoks > 5) - if (!getDouble(tok[5], &flowLimit) || flowLimit < 0.0) - return error_setInpError(ERR_NUMBER, tok[5]); - - // --- get local depression parameters - if (ntoks > 6) - if (!getDouble(tok[6], &aLocal) || aLocal < 0.0) - return error_setInpError(ERR_NUMBER, tok[6]); - if (ntoks > 7) - if (!getDouble(tok[7], &wLocal) || wLocal < 0.0) - return error_setInpError(ERR_NUMBER, tok[7]); - - // --- get inlet placement - if (ntoks > 8) - { - placement = findmatch(tok[8], PlacementTypeWords); - if (placement < 0) return error_setInpError(ERR_KEYWORD, tok[8]); - } - - // --- create an inlet usage object for the link - inlet = Link[linkIndex].inlet; - if (inlet == NULL) - { - inlet = (TInlet *)malloc(sizeof(TInlet)); - if (!inlet) return error_setInpError(ERR_MEMORY, ""); - Link[linkIndex].inlet = inlet; - inlet->nextInlet = FirstInlet; - FirstInlet = inlet; - } - - // --- save inlet usage parameters - inlet->linkIndex = linkIndex; - inlet->designIndex = designIndex; - inlet->nodeIndex = nodeIndex; - inlet->numInlets = numInlets; - inlet->placement = placement; - inlet->clogFactor = 1.0 - (pctClogged / 100.); - inlet->flowLimit = flowLimit / UCF(FLOW); - inlet->localDepress = aLocal / UCF(LENGTH); - inlet->localWidth = wLocal / UCF(LENGTH); - inlet->flowFactor = 0.0; - inlet->backflowRatio = 0.0; - initInletStats(inlet); - UsesInlets = TRUE; - return 0; -} - -//============================================================================= - -void inlet_validate() -// -// Input: none -// Output: none -// Purpose: checks that inlets have been assigned to conduits with proper -// cross section shapes and counts the number of inlets that each -// node receives either bypased or captured flow from. -// -{ - int i, j, inletType, inletValid; - TInlet* inlet; - TInlet* prevInlet; - - // --- traverse the list of inlets placed in conduits - if (!UsesInlets) return; - prevInlet = FirstInlet; - inlet = FirstInlet; - while (inlet) - { - // --- check that inlet's conduit can accept the inlet's type - inletValid = FALSE; - i = inlet->linkIndex; - xsect = &Link[i].xsect; - inletType = InletDesigns[inlet->designIndex].type; - if (inletType == CUSTOM_INLET) - { - j = InletDesigns[inlet->designIndex].customCurve; - if (j >= 0) - { - if (Curve[j].curveType == DIVERSION_CURVE || - Curve[j].curveType == RATING_CURVE) - inletValid = TRUE; - } - } - else if ((xsect->type == TRAPEZOIDAL || xsect->type == RECT_OPEN) && - (inletType == DROP_GRATE_INLET || - inletType == DROP_CURB_INLET)) - inletValid = TRUE; - else if (xsect->type == STREET_XSECT && - inletType != DROP_GRATE_INLET && - inletType != DROP_CURB_INLET) - inletValid = TRUE; - - // --- if inlet placement is valid then - if (inletValid) - { - // --- record that receptor node has inlets - Node[Link[i].node2].inlet = BYPASS; - Node[inlet->nodeIndex].inlet = CAPTURE; - - // --- initialize inlet's backflow - inlet->backflow = 0.0; - - // --- compute street inlet's flow factor for Izzard's eqn. - // (used in Q = flowFactor * Spread^2.67 equation) - getConduitGeometry(inlet); - inlet->flowFactor = (0.56/n) * pow(SL,0.5) * pow(Sx,1.67); - - // --- save reference to current inlet & continue to next inlet - prevInlet = inlet; - inlet = inlet->nextInlet; - } - - // --- if inlet placement is not valid then issue a warning message - // and remove the inlet from the conduit - else - { - report_writeWarningMsg(WARN12, Link[i].ID); - if (inlet == FirstInlet) - { - FirstInlet = inlet->nextInlet; - prevInlet = FirstInlet; - free(inlet); - inlet = FirstInlet; - } - else - { - prevInlet->nextInlet = inlet->nextInlet; - free(inlet); - inlet = prevInlet->nextInlet; - } - Link[i].inlet = NULL; - } - } - - // --- determine how capture node's overflow is split between its inlets - getBackflowRatios(); -} - -//============================================================================= - -void inlet_findCapturedFlows(double tStep) -// -// Input: tStep = current flow routing time step (sec) -// Output: none -// Purpose: computes flow captured by each inlet and adjusts the -// lateral flows of the inlet's bypass and capture nodes accordingly. -// -// This function is called after regular lateral flows to all nodes have been -// set but before a flow routing step has been taken. -{ - int i, j, m, placement; - double q; - TInlet *inlet; - - // --- For non-DW routing find conduit flow into each node - // (used to limit max. amount of on-sag capture) - if (!UsesInlets) return; - memset(InletFlow, 0, Nobjects[NODE]*sizeof(double)); - if (RouteModel != DW) - { - for (j = 0; j < Nobjects[NODE]; j++) - Node[j].inflow = MAX(0., Node[j].newLatFlow); - for (i = 0; i < Nobjects[LINK]; i++) - Node[Link[i].node2].inflow += MAX(0.0, Link[i].newFlow); - } - - // --- loop through each inlet - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - // --- identify indexes of inlet's bypass (j) and capture (m) nodes - i = inlet->linkIndex; - j = Link[i].node2; - m = inlet->nodeIndex; - - // --- get inlet's placement (ON_GRADE or ON_SAG) - placement = getInletPlacement(inlet, j); - - // --- find flow captured by a Custom inlet - if (InletDesigns[inlet->designIndex].type == CUSTOM_INLET) - { - q = fabs(Link[i].newFlow); - inlet->flowCapture = getCustomCapturedFlow(inlet, q, Node[j].newDepth); - } - - // --- find flow captured by on-grade inlet - else if (placement == ON_GRADE) - { - q = fabs(Link[i].newFlow); - inlet->flowCapture = getOnGradeCapturedFlow(inlet, q, Node[j].newDepth); - } - - // --- find flow captured by on-sag inlet - else - { - q = Node[j].inflow; - inlet->flowCapture = getOnSagCapturedFlow(inlet, q, Node[j].newDepth); - } - if (fabs(inlet->flowCapture) < FUDGE) inlet->flowCapture = 0.0; - - // --- add to total flow captured by inlet's node - InletFlow[j] += inlet->flowCapture; - - // --- capture node's overflow becomes inlet's backflow - inlet->backflow = Node[m].overflow * inlet->backflowRatio; - if (fabs(inlet->backflow) < FUDGE) inlet->backflow = 0.0; - } - - // --- make second pass through each inlet - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - // --- identify indexes of inlet's bypass (j) and capture (m) nodes - i = inlet->linkIndex; - j = Link[i].node2; - m = inlet->nodeIndex; - - // --- for on-sag placement under non-DW routing, captured flow - // is limited to inlet's share of bypass node's inflow plus - // any stored volume - if (RouteModel != DW && getInletPlacement(inlet, j) == ON_SAG) - { - q = Node[j].newVolume / tStep; - q += MAX(Node[j].inflow, 0.0); - if (InletFlow[j] > q) - inlet->flowCapture *= q / InletFlow[j]; - } - - // --- adjust lateral flows at bypass and capture nodes - // (subtract captured flow from bypass node, add it to capture - // node, and add any backflow to bypass node) - Node[j].newLatFlow -= (inlet->flowCapture - inlet->backflow); - Node[m].newLatFlow += inlet->flowCapture; - - // --- update inlet's performance if reporting has begun - if (getDateTime(NewRoutingTime) > ReportStart) - updateInletStats(inlet, fabs(Link[i].newFlow)); - } -} - -//============================================================================= - -void inlet_adjustQualInflows() -// -// Input: none -// Output: none -// Purpose: adjusts accumulated flow rates and pollutant mass inflows at each -// inlet's bypass and capture nodes after a flow routing step has -// been taken prior to a quality routing step. -// -{ - int i, j, m, p; - double qNet; - TInlet* inlet; - - if (!UsesInlets) return; - if (IgnoreQuality || Nobjects[POLLUT] == 0) return; - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - // --- identify indexes of inlet's bypass (j) and capture (m) nodes - i = inlet->linkIndex; - j = Link[i].node2; - m = inlet->nodeIndex; - - // --- there's a net flow from the bypass to the capture node - qNet = inlet->flowCapture - inlet->backflow; - if (qNet > 0.0) - { - // --- add net capture flow to capture node's accumulated flow - // inflow for quality routing - Node[m].qualInflow += qNet; - - // --- and do the same for pollutant mass flows - // (Node[m].newQual is the mass inflow accumulator for node m) - for (p = 0; p < Nobjects[POLLUT]; p++) - Node[m].newQual[p] += qNet * Node[j].oldQual[p]; - } - - // --- there's a net backflow from the capture to the bypass node - else - { - // --- add the backflow flow rate and pollutant mass flow to the - // bypass node's accumulated flow and pollutant mass inflow - qNet = -qNet; - Node[j].qualInflow += qNet; - for (p = 0; p < Nobjects[POLLUT]; p++) - Node[j].newQual[p] += qNet * Node[m].oldQual[p]; - } - } -} - -//============================================================================= - -void inlet_adjustQualOutflows() -// -// Input: none -// Output: none -// Purpose: adjusts mass balance totals after a complete routing step has been -// taken so as not to treat inlet transfer flows as system outflows. -// -{ - int j, p; - double q, w; - TInlet* inlet; - - // --- these variables, declared in massbal.c, accumulate system-wide flow and - // pollutant mass fluxes over a time step to use in mass balances - extern TRoutingTotals StepFlowTotals; - extern TRoutingTotals* StepQualTotals; - - // --- examine each node - for (j = 0; j < Nobjects[NODE]; j++) - { - // --- node receives captured flow from an inlet - if (Node[j].inlet == CAPTURE) - { - // --- node also has an overflow (e.g., it's a surcharged sewer node) - q = Node[j].overflow; - if (q > 0.0) - { - // --- remove overflow from system flooding total since it does - // not leave the system (it is sent to inlet's bypass node) - StepFlowTotals.flooding -= q; - - // --- also remove pollutant overflow mass from system totals - if (!IgnoreQuality) - for (p = 0; p < Nobjects[POLLUT]; p++) - { - w = q * Node[j].newQual[p]; - StepQualTotals[p].flooding -= w; - } - } - } - } - - // --- for WQ analysis, examine each inlet's bypass node - if (!IgnoreQuality && Nobjects[POLLUT] > 0) - { - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - j = Link[inlet->linkIndex].node2; - - // --- inlet has net positive flow capture leading to - // node having a net negative lateral inflow - q = inlet->flowCapture - inlet->backflow; - if (q > 0.0 && Node[j].newLatFlow < 0.0) - - // --- remove the pollutant mass in the captured flow from - // the system totals since it does not leave the system - // (it is sent to the inlet's capture node) - for (p = 0; p < Nobjects[POLLUT]; p++) - { - w = q * Node[j].newQual[p]; - StepQualTotals[p].outflow -= w; - } - } - } -} - -//============================================================================= - -void inlet_writeStatsReport() -// -// Input: none -// Output: none -// Purpose: writes table of street & inlet flow statistics to SWMM's report file. -// -{ - int j, header = FALSE; - - if (Nobjects[STREET] == 0) return; - for (j = 0; j < Nobjects[LINK]; j++) - { - if (Link[j].xsect.type == STREET_XSECT) - { - if (!header) - { - writeStreetStatsHeader(); - header = TRUE; - } - writeStreetStats(j); - } - } - report_writeLine(""); -} - -//============================================================================= - -double inlet_capturedFlow(int i) -// -// Input: i = a link index -// Output: returns captured flow rate (cfs) -// Purpose: gets the current flow captured by an inlet. -// -{ - if (Link[i].inlet) return Link[i].inlet->flowCapture; - return 0.0; -} - -//============================================================================= - -int readGrateInletParams(int i, char* tok[], int ntoks) -{ -// -// Input: i = inlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts a grate's inlet parameters from a set of string tokens. -// - int grateType; - double width, length, areaRatio = 0.0, vSplash = 0.0; - - // --- check for enough tokens - if (ntoks < 5) return error_setInpError(ERR_ITEMS, ""); - - // --- retrieve length & width - if (!getDouble(tok[2], &length) || length <= 0.0) - return error_setInpError(ERR_NUMBER, tok[2]); - if (!getDouble(tok[3], &width) || width <= 0.0) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- retrieve grate type - grateType = findmatch(tok[4], GrateTypeWords); - if (grateType < 0) return error_setInpError(ERR_KEYWORD, tok[4]); - - // --- only read open area & splash velocity for GENERIC type grate - if (grateType == GENERIC) - { - if (ntoks < 6) return error_setInpError(ERR_ITEMS, ""); - if (!getDouble(tok[5], &areaRatio) || areaRatio <= 0.0 - || areaRatio > 1.0) return error_setInpError(ERR_NUMBER, tok[5]); - if (ntoks > 6) - { - if (!getDouble(tok[6], &vSplash) || vSplash < 0.0) - return error_setInpError(ERR_NUMBER, tok[6]); - } - } - - // --- save grate inlet parameters - InletDesigns[i].grateInlet.length = length / UCF(LENGTH); - InletDesigns[i].grateInlet.width = width / UCF(LENGTH); - InletDesigns[i].grateInlet.type = grateType; - InletDesigns[i].grateInlet.fracOpenArea = areaRatio; - InletDesigns[i].grateInlet.splashVeloc = vSplash / UCF(LENGTH); - - // --- check if grate is part of a combo inlet - if (InletDesigns[i].type == GRATE_INLET && - InletDesigns[i].curbInlet.length > 0.0) - InletDesigns[i].type = COMBO_INLET; - return 0; -} - -//============================================================================= - -int readCurbInletParams(int i, char* tok[], int ntoks) -// -// Input: i = inlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts curb opening inlet parameters from a set of string tokens. -// -{ - int throatAngle; - double height, length; - - // --- check for enough tokens - if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); - - // --- retrieve length & width of opening - if (!getDouble(tok[2], &length) || length <= 0.0) - return error_setInpError(ERR_NUMBER, tok[2]); - if (!getDouble(tok[3], &height) || height <= 0.0) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- retrieve type of throat angle for curb inlet - throatAngle = VERTICAL_THROAT; - if (InletDesigns[i].type == CURB_INLET && ntoks > 4) - { - throatAngle = findmatch(tok[4], ThroatAngleWords); - if (throatAngle < 0) return error_setInpError(ERR_KEYWORD, tok[4]); - } - - // ---- save curb opening inlet parameters - InletDesigns[i].curbInlet.length = length / UCF(LENGTH); - InletDesigns[i].curbInlet.height = height / UCF(LENGTH); - InletDesigns[i].curbInlet.throatAngle = throatAngle; - - // --- check if curb inlet is part of a combo inlet - if (InletDesigns[i].type == CURB_INLET && - InletDesigns[i].grateInlet.length > 0.0) - InletDesigns[i].type = COMBO_INLET; - return 0; -} - -//============================================================================= - -int readSlottedInletParams(int i, char* tok[], int ntoks) -// -// Input: i = inlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts slotted drain inlet parameters from a set of string tokens. -// -{ - double width, length; - - // --- check for enough tokens - if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); - - // --- retrieve length and width - if (!getDouble(tok[2], &length) || length <= 0.0) - return error_setInpError(ERR_NUMBER, tok[2]); - if (!getDouble(tok[3], &width) || width <= 0.0) - return error_setInpError(ERR_NUMBER, tok[3]); - - // --- save slotted inlet parameters - InletDesigns[i].slottedInlet.length = length / UCF(LENGTH); - InletDesigns[i].slottedInlet.width = width / UCF(LENGTH); - return 0; -} - -//============================================================================= - -int readCustomInletParams(int i, char* tok[], int ntoks) -// -// Input: i = inlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: extracts custom inlet parameters from a set of string tokens. -// -{ - int c; // capture curve index - - if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); - else - { - c = project_findObject(CURVE, tok[2]); - if (c < 0) return error_setInpError(ERR_NAME, tok[2]); - } - InletDesigns[i].customCurve = c; - return 0; -} - -//============================================================================= - -void initInletStats(TInlet* inlet) -// -// Input: inlet = an inlet object placed in a conduit link -// Output: none -// Purpose: initializes the performance statistics of an inlet. -// -{ - if (inlet) - { - inlet->flowCapture = 0.0; - inlet->backflow = 0.0; - inlet->stats.flowPeriods = 0; - inlet->stats.capturePeriods = 0; - inlet->stats.backflowPeriods = 0; - inlet->stats.peakFlow = 0.0; - inlet->stats.peakFlowCapture = 0; - inlet->stats.avgFlowCapture = 0; - inlet->stats.bypassFreq = 0; - } -} - -//============================================================================= - -void updateInletStats(TInlet* inlet, double q) -// -// Input: inlet = an inlet object placed in a conduit link -// q = inlet's approach flow (cfs) -// Output: none -// Purpose: updates the performance statistics of an inlet. -// -{ - double qCapture = inlet->flowCapture, - qBackflow = inlet->backflow, - qNet = qCapture - qBackflow, - qBypass = q - qNet, - fCapture = 0.0; - - // --- check for no flow condition - if (q < MIN_RUNOFF_FLOW && qBackflow <= 0.0) return; - inlet->stats.flowPeriods++; - - // --- there is positive net flow from inlet to capture node - if (qNet > 0.0) - { - inlet->stats.capturePeriods++; - fCapture = qNet / q; - fCapture = MIN(fCapture, 1.0); - inlet->stats.avgFlowCapture += fCapture; - if (qBypass > MIN_RUNOFF_FLOW) inlet->stats.bypassFreq++; - } - - // --- otherwise inlet receives backflow from capture node - else inlet->stats.backflowPeriods++; - - // --- update peak flow stats - if (q > inlet->stats.peakFlow) - { - inlet->stats.peakFlow = q; - inlet->stats.peakFlowCapture = fCapture * 100.0; - } -} - -//============================================================================= - -void writeStreetStatsHeader() -// -// Input: none -// Output: none -// Purpose: writes column headers for Street Flow Summary table to SWMM's report file. -// -{ - report_writeLine(""); - report_writeLine("*******************"); - report_writeLine("Street Flow Summary"); - report_writeLine("*******************"); - report_writeLine(""); - fprintf(Frpt.file, -"\n ---------------------------------------------------------------------------------------------------------------------------------------" -"\n Peak Avg. Bypass Back Peak Peak" -"\n Peak Maximum Maximum Flow Flow Flow Flow Capture Bypass" -"\n Flow Spread Depth Inlet Inlet Inlet Capture Capture Freq Freq / Inlet Flow"); - if (UnitSystem == US) fprintf(Frpt.file, -"\n Street Conduit %3s ft ft Design Location Count Pcnt Pcnt Pcnt Pcnt %3s %3s", - FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits]); - else fprintf(Frpt.file, -"\n Street Conduit %3s m m Design Location Pcnt Pcnt Pcnt Pcnt %3s %3s", - FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits], FlowUnitWords[FlowUnits]); - fprintf(Frpt.file, -"\n ---------------------------------------------------------------------------------------------------------------------------------------"); -} - -//============================================================================= - -void writeStreetStats(int link) -// -// Input: link = index of a conduit link containing an inlet -// Output: none -// Purpose: writes flow statistics for a Street conduit and its inlet to -// SWMM's report file. -// -{ - int k, t, placement; - double maxSpread, maxDepth, maxFlow; - double fp, cp, afc = 0.0, bpf = 0.0; - TInlet* inlet; - - // --- retrieve street parameters - k = Link[link].subIndex; - t = Link[link].xsect.transect; - inlet = Link[link].inlet; - - // --- get recorded max flow and depth - maxFlow = LinkStats[link].maxFlow; - maxDepth = LinkStats[link].maxDepth; - - // --- SWMM's spread (flow width) at max depth - maxSpread = xsect_getWofY(&Link[link].xsect, maxDepth) / Street[t].sides; - maxSpread = MIN(maxSpread, Street[t].width); -/* - // HEC-22's spread based on max flow (doesn't account for backwater) - Sx = Street[t].slope; - a = Street[t].gutterDepression; - W = Street[t].gutterWidth; - n = Street[t].roughness; - Qfactor = (0.56 / n) * sqrt(Conduit[k].slope) * pow(Sx, 1.67); - maxSpread = getFlowSpread(maxFlow / Street[t].sides); - maxSpread = MIN(maxSpread, Street[t].width); -*/ - // --- write street stats - fprintf(Frpt.file, "\n %-16s", Link[link].ID); - fprintf(Frpt.file, " %9.3f", maxFlow * UCF(FLOW)); - fprintf(Frpt.file, " %9.3f", maxSpread * UCF(LENGTH)); - fprintf(Frpt.file, " %9.3f", maxDepth * UCF(LENGTH)); - - // --- write inlet stats - if (inlet) - { - fprintf(Frpt.file, " %-16s", InletDesigns[inlet->designIndex].ID); - placement = getInletPlacement(inlet, Link[inlet->linkIndex].node2); - if (placement == ON_GRADE) - fprintf(Frpt.file, " ON-GRADE"); - else - fprintf(Frpt.file, " ON-SAG "); - fprintf(Frpt.file, " %5d", inlet->numInlets); - fp = inlet->stats.flowPeriods / 100.0; - if (fp > 0.0) - { - cp = inlet->stats.capturePeriods / 100.0; - fprintf(Frpt.file, " %7.2f", inlet->stats.peakFlowCapture); - if (cp > 0.0) - { - afc = inlet->stats.avgFlowCapture / cp; - bpf = inlet->stats.bypassFreq / cp; - } - fprintf(Frpt.file, " %7.2f", afc); - fprintf(Frpt.file, " %7.2f", bpf); - fprintf(Frpt.file, " %7.2f", inlet->stats.backflowPeriods / fp); - fprintf(Frpt.file, " %7.2f", (maxFlow / Street[t].sides) * UCF(FLOW) * - 0.01 * inlet->stats.peakFlowCapture / inlet->numInlets); - fprintf(Frpt.file, " %7.2f", maxFlow * UCF(FLOW) * 0.01 * - (100.0 - inlet->stats.peakFlowCapture)); - } - } -} - -//============================================================================= - -int getInletPlacement(TInlet* inlet, int j) -// -// Input: inlet = an inlet object placed in a conduit link -// j = index of inlet's bypass node -// Output: returns type of inlet placement -// Purpose: determines actual placement for an inlet with AUTOMATIC placement. -// -{ - if (inlet->placement == AUTOMATIC) - { - if (Node[j].degree > 0) return ON_GRADE; - else return ON_SAG; - } - else return inlet->placement; -} - -//============================================================================= - -void getConduitGeometry(TInlet* inlet) -// -// Input: inlet = an inlet object placed in a conduit link -// Output: none -// Purpose: assigns properties of an inlet's conduit to -// module-level shared variables used by other functions. -// -{ - int linkIndex = inlet->linkIndex; - int t, k = Link[linkIndex].subIndex; - - SL = Conduit[k].slope; // longitudinal slope - Beta = Conduit[k].beta; // 1.486 * sqrt(SL) / n - xsect = &Link[linkIndex].xsect; - - // --- if conduit has a Street cross section - if (xsect->type == STREET_XSECT) - { - t = xsect->transect; - Sx = Street[t].slope; // street cross slope - a = Street[t].gutterDepression; // gutter depression - W = Street[t].gutterWidth; // gutter width - n = Street[t].roughness; // street roughness - Nsides = Street[t].sides; // 1 or 2 sided street - Tcrown = Street[t].width; // distance from curb to crown - Qfactor = inlet->flowFactor; // factor used in Izzard's eqn. - - // --- add inlet's local depression to street's continuous depression - if (inlet && inlet->localDepress * inlet->localWidth > 0) - { - a += inlet->localDepress; // inlet depression - W = inlet->localWidth; // inlet depressed width - } - - // --- slope of depressed gutter section - if (W * a > 0.0) Sw = Sx + a / W; - else Sw = Sx; - } - - // --- conduit has rectangular or trapezoidal cross section - else - { - a = 0.0; - W = 0.0; - n = Conduit[k].roughness; - Nsides = 1; - Sx = 0.01; - Sw = Sx; - } -} - -//============================================================================= - -double getFlowSpread(double Q) -// -// Input: Q = conduit flow rate (cfs) -// Output: returns width of flow spread (ft) -// Purpose: computes width of flow spread across a Street cross section using -// HEC-22 equations derived from Izzard's form of the Manning eqn. -// -{ - int iter; - double f, f1, Sr, Ts1, Ts2, Tw, Qs, Eo; - - f = Qfactor; // = (0.56/n) * SL^0.5 * Sx^1.67 - - // --- no depressed curb - if (a == 0.0) - { - Ts1 = pow(Q / f, 0.375); //HEC-22 Eq(4-2) - } - else - { - // --- check if spread is within curb width - f1 = f * pow((a / W) / Sx, 1.67); - Tw = pow(Q / f1, 0.375); //HEC-22 Eq(4-2) - if (Tw <= W) Ts1 = Tw; - else - { - // --- spread extends beyond curb width - Sr = (Sx + a / W) / Sx; - iter = 1; - Ts1 = pow(Q / f, 0.375) - W; - if (Ts1 <= 0) Ts1 = Tw - W; - while (iter < 11) - { - Eo = getEo(Sr, Ts1, W); - Qs = (1.0 - Eo) * Q; //HEC-22 Eq(4-6) - Ts2 = pow(Qs / f, 0.375); //HEC-22 Eq(4-2) - if (fabs(Ts2 - Ts1) < 0.01) break; - Ts1 = Ts2; - iter++; - } - Ts1 = Ts2 + W; - } - } - return MIN(Ts1, Tcrown); -} - -//============================================================================= - -double getEo(double Sr, double Ts, double w) -// -// Input: Sr = ratio of gutter slope to street cross slope -// Ts = amount of flow spread outside of gutter width (ft) -// w = gutter width (ft) -// Output: returns ratio of gutter flow to total flow in street cross section -// Purpose: solves HEC-22 Eq. (4-4) for Eo with Ts/w substituted for -// (T/w) - 1 where Ts = T - w. -// -{ - double x; - x = Sr / (Ts / w); - x = pow((1.0 + x), 2.67) - 1.0; - x = 1.0 + Sr / x; - return 1.0 / x; -} - -//============================================================================= - -double getOnGradeCapturedFlow(TInlet* inlet, double q, double d) -// -// Input: inlet = an inlet object placed in a conduit link -// q = flow in link prior to any inlet capture (cfs) -// d = flow depth seen by inlet (ft) -// Output: returns flow captured by the inlet (cfs) -// Purpose: computes flow captured by an inlet placed on-grade. -// -// An inlet object placed in a conduit can have multiple inlets of -// the same type distributed along the conduit's length that all -// send their captured flow to the same sewer node. This function -// finds the total captured flow as each individual inlet is analyzed -// sequentially, where its approach flow has been reduced by the -// amount of flow captured by prior inlets. -{ - int i, - linkIndex; // index of link containing inlets - double qApproach, // single inlet's approach flow (cfs) - qc, // single inlet's captured flow (cfs) - qCaptured, // total flow captured by link's inlets (cfs) - qBypassed, // total flow bypassed by link's inlets (cfs) - qMax; // max. flow that a single inlet can capture (cfs) - - if (inlet->numInlets == 0) return 0.0; - linkIndex = inlet->linkIndex; - - // --- check that link has flow - qApproach = q; - if (qApproach < MIN_RUNOFF_FLOW) return 0.0; - - // --- store conduit geometry in shared variables - getConduitGeometry(inlet); - - // --- adjust flow for 2-sided street - qApproach /= Nsides; - qBypassed = qApproach; - qCaptured = 0.0; - - // --- set limit on max. flow captured per inlet - qMax = BIG; - if (inlet->flowLimit > 0.0) qMax = inlet->flowLimit; - - // --- evaluate each inlet - for (i = 1; i <= inlet->numInlets; i++) - { - qc = getOnGradeInletCapture(inlet->designIndex, qBypassed, d) * - inlet->clogFactor; - qc = MIN(qc, qMax); - qc = MIN(qc, qBypassed); - qCaptured += qc; - qBypassed -= qc; - if (qBypassed < MIN_RUNOFF_FLOW) break; - } - return qCaptured *= Nsides; -} - -//============================================================================= - -double getOnGradeInletCapture(int i, double Q, double d) -// -// Input: i = an InletDesigns index -// Q = flow rate seen by inlet (cfs) -// d = flow depth seen by inlet (ft) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by a single on-grade inlet. -// -{ - double Q1 = Q, Qc = 0.0, Lsweep = 0.0, Lcurb = 0.0, Lgrate = 0.0; - - // --- drop curb inlet (in non-Street conduit) only operates in on sag mode - if (InletDesigns[i].type == DROP_CURB_INLET) - { - Qc = getOnSagInletCapture(i, d); - return MIN(Qc, Q); - } - - // --- drop grate inlet (in non-Street conduit) - if (InletDesigns[i].type == DROP_GRATE_INLET) - { - Qc = getGrateInletCapture(i, Q); - return MIN(Qc, Q); - } - - // --- Remaining inlet types apply to Street conduits - - // --- find flow spread - T = getFlowSpread(Q); - - // --- slotted inlet (behaves as a curb opening inlet per HEC-22) - if (InletDesigns[i].type == SLOTTED_INLET) - { - Qc = getCurbInletCapture(Q, InletDesigns[i].slottedInlet.length); - return MIN(Qc, Q); - } - - Lcurb = InletDesigns[i].curbInlet.length; - Lgrate = InletDesigns[i].grateInlet.length; - - // --- curb opening inlet - if (Lcurb > 0.0) - { - Lsweep = Lcurb - Lgrate; - if (Lsweep > 0.0) - { - Qc = getCurbInletCapture(Q1, Lsweep); - Q1 -= Qc; - } - } - - // --- grate inlet - if (Lgrate > 0.0 && Q1 > 0.0) - { - if (Q1 != Q) T = getFlowSpread(Q1); - Qc += getGrateInletCapture(i, Q1); - } - return Qc; -} - -//============================================================================= - -double getGrateInletCapture(int i, double Q) -// -// Input: i = inlet type index -// Q = flow rate seen by inlet (cfs) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by an on-grade grate inlet. -// -{ - int grateType; - double Lg, // grate length (ft) - Wg, // grate width (ft) - A, // total cross section flow area (ft2) - Y, // flow depth (ft) - Eo, // ratio of gutter to total flow - V, // flow velocity (ft/s) - Vo, // splash-over velocity (ft/s) - Qo = Q, // flow over street area (cfs) - Rf = 1.0, // ratio of intercepted to total frontal flow - Rs = 0.0; // ratio of intercepted to total side flow - -// xsect, a, W, & Sx were from getConduitGeometry(). T was from getFlowSpread(). - - Lg = InletDesigns[i].grateInlet.length; - Wg = InletDesigns[i].grateInlet.width; - - // --- flow ratio for drop inlet - if (xsect->type == TRAPEZOIDAL || xsect->type == RECT_OPEN) - { - A = xsect_getAofS(xsect, Q / Beta); - Y = xsect_getYofA(xsect, A); - T = xsect_getWofY(xsect, Y); - Eo = Beta * pow(Y*Wg, 1.67) / pow(Wg + 2*Y, 0.67) / Q; - if (Wg > 0.99*xsect->yBot && xsect->type == TRAPEZOIDAL && xsect->sBot > 0.0) - { - Wg = xsect->yBot; - Sx = 1.0 / xsect->sBot; - } - } - - // --- flow ratio & area for conventional street gutter - else if (a == 0.0) - { - A = T * T * Sx / 2.0; - Eo = getGutterFlowRatio(Wg); // flow ratio based on grate width - if (T >= Tcrown) Qo = Qfactor * pow(Tcrown, 2.67); - } - - // --- flow ratio & area for composite street gutter - else - { - // --- spread confined to gutter - if (T <= W) A = T * T * Sw / 2.0; - - // --- spread beyond gutter width - else A = (T * T * Sx + a * W) / 2.0; - - // flow ratio based on gutter width corrected for grate width - Eo = getGutterFlowRatio(W); - if (Eo < 1.0) - { - if (T >= Tcrown) - Qo = Qfactor * pow(Tcrown, 2.67) / (1.0 - Eo); - Eo = Eo * getGutterAreaRatio(Wg, A); //HEC-22 Eq(4-20a) - } - } - - // --- flow and splash-over velocities - V = Qo / A; - grateType = InletDesigns[i].grateInlet.type; - if (grateType < 0 || grateType == GENERIC) - Vo = InletDesigns[i].grateInlet.splashVeloc; - else - Vo = getSplashOverVelocity(grateType, Lg); - - // --- frontal flow capture efficiency - if (V > Vo) Rf = 1.0 - 0.09 * (V - Vo); //HEC-22 Eq(4-18) - - // --- side flow capture efficiency - if (Eo < 1.0) - { - Rs = 1.0 / (1.0 + (0.15 * pow(V, 1.8) / - Sx / pow(Lg, 2.3))); //HEC-22 Eq(4-19) - } - - // --- return total flow captured - return Q * (Rf * Eo + Rs * (1.0 - Eo)); //HEC-22 Eq(4-21) -} - -//============================================================================= - -double getCurbInletCapture(double Q, double L) -// -// Input: Q = flow rate seen by inlet (cfs) -// L = length of inlet opening (ft) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by an on-sag inlet. -// -{ - double Se = Sx, // equivalent gutter slope - Lt, // length for full capture - Sr, // ratio of gutter slope to cross slope - Eo = 0.0, // ratio of gutter to total flow - E = 1.0; // capture efficiency - -// a, W, Sx, Sw, SL, & n were from getConduitGeometry(). T was from getFlowSpread(). - - // --- for depressed gutter section - if (a > 0.0) - { - Sr = Sw / Sx; - Eo = getEo(Sr, T-W, W); - Se = Sx + Sw * Eo; //HEC-22 Eq(4-24) - } - - // --- opening length for full capture - Lt = 0.6 * pow(Q, 0.42) * pow(SL, 0.3) * - pow(1.0/(n*Se), 0.6); //HEC-22 Eq(4-22a) - - // --- capture efficiency for actual opening length - if (L < Lt) - { - E = 1.0 - (L/Lt); - E = 1 - pow(E, 1.8); //HEC-22 Eq(4-23) - } - E = MIN(E, 1.0); - E = MAX(E, 0.0); - return E * Q; -} - -//============================================================================= - -double getGutterFlowRatio(double w) -// -// Input: w = gutter width (ft) -// Output: returns a flow ratio -// Purpose: computes the ratio of flow over a width of gutter to the total -// flow in a street cross section. -// -{ - if (T <= w) return 1.0; - else if (a > 0.0) - return getEo(Sw / Sx, T - w, w); - else - return 1.0 - pow((1.0 - w / T), 2.67); //HEC-22 Eq(4-16) -} - -//============================================================================= - -double getGutterAreaRatio(double Wg, double A) -// -// Input: Wg = width of grate inlet (ft) -// A = total flow area (ft2) -// Output: returns an area ratio -// Purpose: computes the ratio of the flow area above a grate to the flow -// area above depressed gutter in a street cross section. -// -{ - double As, // flow area beyond gutter width (ft2) - Ag; // flow area over grate width (ft2) - - if (Wg >= W) return 1.0; - if (T <= Wg) return 1.0; - if (T <= W) return Wg / T; - As = 0.5 * SQR((T - W)) * Sx; - Ag = Wg * ( (T * Sx) + a - (Wg * Sw / 2.) ); - return Ag / (A - As); -} - -//============================================================================= - -double getSplashOverVelocity(int grateType, double L) -// -// Input: grateType = grate inlet type code -// L = length of grate inlet (ft) -// Output: returns a splash over velocity -// Purpose: computes the splash over velocity for a standard type of grate -// inlet as a function of its length. -// -{ - return SplashCoeffs[grateType][0] + - SplashCoeffs[grateType][1] * L - - SplashCoeffs[grateType][2] * L * L + - SplashCoeffs[grateType][3] * L * L * L; -} - -//============================================================================= - -double getOnSagCapturedFlow(TInlet* inlet, double q, double d) -// -// Input: inlet = an inlet object placed in a conduit link -// q = flow in link prior to any inlet capture (cfs) -// d = flow depth seen by inlet (ft) -// Output: returns flow captured by the inlet (cfs) -// Purpose: computes flow captured by an inlet placed on-sag. -// -{ - int linkIndex, designIndex, totalInlets; - double qCaptured = 0.0, qMax = BIG; - - if (inlet->numInlets == 0) return 0.0; - totalInlets = Nsides * inlet->numInlets; - linkIndex = inlet->linkIndex; - designIndex = inlet->designIndex; - - // --- store conduit geometry in shared variables - getConduitGeometry(inlet); - - // --- set flow limit per inlet - if (inlet->flowLimit > 0.0) - qMax = inlet->flowLimit; - - // --- find nominal flow captured by inlet - qCaptured = getOnSagInletCapture(designIndex, fabs(d)); - - // --- find actual flow captured by the inlet - qCaptured *= inlet->clogFactor; - qCaptured = MIN(qCaptured, qMax); - qCaptured *= (double)totalInlets; - return qCaptured; -} - -//============================================================================= - -double getOnSagInletCapture(int i, double d) -// -// Input: i = inlet type index -// d = water level seen by inlet (ft) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by an on-sag inlet. -// -{ - double Lsweep = 0.0, Lcurb = 0.0, Lgrate = 0.0; - double Qsw = 0.0, //Sweeper curb opening weir flow - Qso = 0.0, //Sweeper curb opening orifice flow - Qgw = 0.0, //Grate weir flow - Qgo = 0.0, //Grate orifice flow - Qcw = 0.0, //Curb opening weir flow - Qco = 0.0; //Curb opening orifice flow - - if (InletDesigns[i].slottedInlet.length > 0.0) - return getOnSagSlottedFlow(i, d); - - Lgrate = InletDesigns[i].grateInlet.length; - if (Lgrate > 0.0) findOnSagGrateFlows(i, d, &Qgw, &Qgo); - - Lcurb = InletDesigns[i].curbInlet.length; - if (Lcurb > 0.0) - { - Lsweep = Lcurb - Lgrate; - if (Lsweep > 0.0) findOnSagCurbFlows(i, d, Lsweep, &Qsw, &Qso); - if (Qgo > 0.0) findOnSagCurbFlows(i, d, Lgrate, &Qcw, &Qco); - } - return Qgw + Qgo + Qsw + Qso + Qco; -} - -//============================================================================= - -void findOnSagGrateFlows(int i, double d, double *Qw, double *Qo) -// -// Input: i = inlet type index -// d = water level seen by inlet (ft) -// Output: Qw = flow captured in weir mode (cfs) -// Qo = flow captured in orifice mode (cfs) -// Purpose: finds the flow captured by an on-sag grate inlet. -// -{ - int grateType = InletDesigns[i].grateInlet.type; - double Lg = InletDesigns[i].grateInlet.length; - double Wg = InletDesigns[i].grateInlet.width; - double P, // grate perimeter (ft) - Ao, // grate opening area (ft2) - di; // average flow depth across grate (ft) - - // --- for drop grate inlets - if (InletDesigns[i].type == DROP_GRATE_INLET) - { - di = d; - P = 2.0 * (Lg + Wg); - } - - // --- for gutter grate inlets: - else - { - // --- check for spread within grate width - if (d <= Wg * Sw) - Wg = d / Sw; - - // --- avergage depth over grate - di = d - (Wg / 2.0) * Sw; - - // --- effective grate perimeter - P = Lg + 2.0 * Wg; - } - - if (grateType == GENERIC) - Ao = Lg * Wg * InletDesigns[i].grateInlet.fracOpenArea; - else - Ao = Lg * Wg * GrateOpeningRatios[grateType]; - - // --- weir flow applies (based on depth where result of - // weir eqn. equals result of orifice eqn.) - - if (d <= 1.79 * Ao / P) - { - *Qw = 3.0 * P * pow(di, 1.5); //HEC-22 Eq(4-26) - } - - // --- orifice flow applies - else - { - *Qo = 0.67 * Ao * sqrt(2.0 * 32.16 * di); //HEC-22 Eq(4-27) - } -} - -//============================================================================= - -void findOnSagCurbFlows(int i, double d, double L, double *Qw, double *Qo) -// -// Input: i = inlet type index -// d = water level seen by inlet (ft) -// L = length of curb opening (ft) -// Output: Qw = flow captured in weir mode (cfs) -// Qo = flow captured in orifice mode (cfs) -// Purpose: finds the flow captured by an on-sag curb opening inlet. -// -{ - int throatAngle = InletDesigns[i].curbInlet.throatAngle; - double h = InletDesigns[i].curbInlet.height; - double Qweir, Qorif, P; - double dweir, dorif, r; - - // --- check for orifice flow - if (L <= 0.0) return; - if (InletDesigns[i].type == DROP_CURB_INLET) L = L * 4.0; - dorif = 1.4 * h; - if (d > dorif) - { - *Qo = getCurbOrificeFlow(d, h, L, throatAngle); - return; - } - - // --- for uniform cross slope or very long opening - if (a == 0.0 || L > 12.0) - { - // --- check for weir flow - dweir = h; - if (d < dweir) - { - *Qw = 3.0 * L * pow(d, 1.5); //HEC-22 Eq(4-30) - return; - } - else Qweir = 3.0 * L * pow(dweir, 1.5); - } - - // --- for depressed gutter - else - { - // --- check for weir flow - P = L + 1.8 * W; - dweir = h + a; - if (d < dweir) - { - *Qw = 2.3 * P * pow(d, 1.5); //HEC-22 Eq(4-28) - return; - } - else Qweir = 2.3 * P * pow(dweir, 1.5); - } - - // --- interpolate between Qweir at depth dweir and Qorif at depth dorif - Qorif = getCurbOrificeFlow(dorif, h, L, throatAngle); - r = (d - dweir) / (dorif - dweir); - *Qw = (1.0 -r) * Qweir; - *Qo = r * Qorif; -} - -//============================================================================= - -double getCurbOrificeFlow(double di, double h, double L, int throatAngle) -// -// Input: di = water level at lip of inlet opening (ft) -// h = height of curb opening (ft) -// L = length of curb opening (ft) -// throatAngle = type of throat angle in curb opening -// Output: return flow captured by inlet (cfs) -// Purpose: finds the flow captured by an on-sag curb opening inlet under -// orifice flow conditions. -// -{ - double d = di; - if (throatAngle == HORIZONTAL_THROAT) - d = di - h / 2.0; - else if (throatAngle == INCLINED_THROAT) - d = di + (h / 2.0) * 0.7071; - return 0.67 * h * L * sqrt(2.0 * 32.16 * d); //HEC-22 Eq(4-31a) -} - -//============================================================================= - -double getOnSagSlottedFlow(int i, double d) -// -// Input: i = inlet type index -// d = water level seen by inlet (ft) -// Output: returns captured flow rate (cfs) -// Purpose: finds the flow captured by an on-sag slotted inlet. -// -// Note: weir flow = orifice flow at d = 2.587 * inlet width -{ - double L = InletDesigns[i].slottedInlet.length; - double w = InletDesigns[i].slottedInlet.width; - - if (d <= 2.587 * w) - return 2.48 * L * pow(d, 1.5); //HEC-22 Eq(4-32) - else - return 0.8 * L * w * sqrt(64.32 * d); //HEC-22 Eq(4-33) -} - -//============================================================================= - -void getBackflowRatios() -// -// Input: none -// Output: overflow ratio for each inlet -// Purpose: finds the fraction of the overflow produced by an inlet's capture -// node that becomes backflow into the inlet. -// -// Note: when a capture node receives flow from two or more inlets -// its backflow is divided among the inlets based on: -// i) the fraction of total open area for standard inlets -// ii) the fraction of total number of inlets for custom inlets -{ - TInlet* inlet; - double area; - double f; - int n; - - // --- info for each node receiving flow from an inlet - typedef struct - { - int numInletLinks; // total # inlet links - int numStdInletLinks; // total # standard inlet links - int numCustomInlets; // # custom inlets - double totalInletArea; // open area of standard inlets - } TInletNode; - TInletNode* inletNodes = (TInletNode *) calloc(Nobjects[NODE], sizeof(TInletNode)); - if (inletNodes == NULL) return; - - // --- Finds each inlet's contribution to its capture node - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - n = inlet->nodeIndex; - inletNodes[n].numInletLinks++; - area = getInletArea(inlet); - if (area > 0.0) - { - inletNodes[n].numStdInletLinks++; - inletNodes[n].totalInletArea += area; - } - else - inletNodes[n].numCustomInlets += inlet->numInlets; - } - - // --- find fraction of capture node's overflow that becomes inlet backflow - for (inlet = FirstInlet; inlet != NULL; inlet = inlet->nextInlet) - { - // --- f is ratio of links with standard inlets to all inlet links - // connected to receptor node n - n = inlet->nodeIndex; - f = (double) inletNodes[n].numStdInletLinks / - (double) inletNodes[n].numInletLinks; - - // --- backflow ratio depends if inlet is standard or custom (area = 0) - area = getInletArea(inlet); - if (area == 0.0) - inlet->backflowRatio = (double)inlet->numInlets / - (double)inletNodes[n].numCustomInlets * (1. - f); - else - inlet->backflowRatio = area / inletNodes[n].totalInletArea * f; - } - free(inletNodes); -} - -//============================================================================= - -double getInletArea(TInlet* inlet) -// -// Input: inlet = an inlet object placed in a conduit link -// Output: returns the unclogged open area of the inlet (ft2) -// Purpose: finds the total open flow area inlets placed in a conduit. -// -{ - double area = 0.0; - double curbLength; - int i = inlet->designIndex; - int grateType = InletDesigns[i].grateInlet.type; - - if (InletDesigns[i].grateInlet.length > 0.0) - { - area = InletDesigns[i].grateInlet.length * InletDesigns[i].grateInlet.width; - if (grateType == GENERIC) - area *= InletDesigns[i].grateInlet.fracOpenArea; - else - area *= GrateOpeningRatios[grateType]; - } - - curbLength = InletDesigns[i].curbInlet.length - InletDesigns[i].grateInlet.length; - if (curbLength > 0.0) - area += curbLength * InletDesigns[i].curbInlet.height; - - if (InletDesigns[i].slottedInlet.length > 0.0) - area = InletDesigns[i].slottedInlet.length * InletDesigns[i].slottedInlet.width; - return area * inlet->numInlets * inlet->clogFactor; -} - -//============================================================================= - -double getCustomCapturedFlow(TInlet* inlet, double q, double d) -{ - int i = inlet->designIndex; // inlet's position in InletDesigns array - int j; // counter for replicate inlets - int sides = 1; // number of sides for inlet's street (1 or 2) - int c; // an index into the Curve array - double qApproach, // inlet's approach flow (cfs) - qBypassed, // inlet's bypassed flow (cfs) - qCaptured, // inlet's captured flow (cfs) - qIncrement, // increment to captured flow (cfs) - qMax = BIG; // user-supplied flow capture limit (cfs) - - if (inlet->numInlets == 0) return 0.0; - - // --- set limit on max. flow captured per inlet - qMax = BIG; - if (inlet->flowLimit > 0.0) qMax = inlet->flowLimit; - - // --- get number of sides to a street xsection - xsect = &Link[inlet->linkIndex].xsect; - if (xsect->type == STREET_XSECT) - sides = Street[xsect->transect].sides; - - // --- adjust flow for 2-sided street - qApproach = q / sides; - qBypassed = qApproach; - qCaptured = 0.0; - - // --- get index of inlet's capture curve - c = InletDesigns[i].customCurve; - if (c >= 0) - { - // --- curve is captured flow v. approach flow - if (Curve[c].curveType == DIVERSION_CURVE) - { - // --- add up incrmental capture of each replicate inlet - for (j = 1; j <= inlet->numInlets; j++) - { - qIncrement = inlet->clogFactor * - table_lookupEx(&Curve[c], qBypassed * UCF(FLOW)) / UCF(FLOW); - qIncrement = MIN(qIncrement, qMax); - qIncrement = MIN(qIncrement, qBypassed); - qCaptured += qIncrement; - qBypassed -= qIncrement; - if (qBypassed < MIN_RUNOFF_FLOW) break; - } - } - - // --- curve is captured flow v. downstream node depth - else if (Curve[c].curveType == RATING_CURVE) - { - qCaptured = inlet->numInlets * inlet->clogFactor * - table_lookupEx(&Curve[c], d * UCF(LENGTH)) / UCF(FLOW); - } - qCaptured *= sides; - } - return qCaptured; -} diff --git a/src/inlet.h b/src/inlet.h deleted file mode 100644 index ca7830725..000000000 --- a/src/inlet.h +++ /dev/null @@ -1,30 +0,0 @@ -//----------------------------------------------------------------------------- -// inlet.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Street/Channel Inlet Functions -// -//----------------------------------------------------------------------------- -#ifndef INLET_H -#define INLET_H - -typedef struct TInlet TInlet; - -int inlet_create(int nInlets); -void inlet_delete(); -int inlet_readDesignParams(char* tok[], int ntoks); -int inlet_readUsageParams(char* tok[], int ntoks); -void inlet_validate(); - -void inlet_findCapturedFlows(double tStep); -void inlet_adjustQualInflows(); -void inlet_adjustQualOutflows(); - -void inlet_writeStatsReport(); -double inlet_capturedFlow(int link); - -#endif diff --git a/src/input.c b/src/input.c deleted file mode 100644 index c0f7a0d9f..000000000 --- a/src/input.c +++ /dev/null @@ -1,930 +0,0 @@ -//----------------------------------------------------------------------------- -// input.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 06/01/22 (Build 5.2.1) -// Author: L. Rossman -// -// Input data processing functions. -// -// Update History -// ============== -// Build 5.1.007: -// - Support added for climate adjustment input data. -// Build 5.1.011: -// - Support added for reading hydraulic event dates. -// Build 5.1.015: -// - Support added for multiple infiltration methods within a project. -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for named variables & math expressions in control rules. -// Build 5.2.1: -// - Possible integer underflow avoided in getTokens() function. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" -#include "lid.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const int MAXERRS = 100; // Max. input errors reported - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -static char *Tok[MAXTOKS]; // String tokens from line of input -static int Ntokens; // Number of tokens in line of input -static int Mobjects[MAX_OBJ_TYPES]; // Working number of objects of each type -static int Mnodes[MAX_NODE_TYPES]; // Working number of node objects -static int Mlinks[MAX_LINK_TYPES]; // Working number of link objects -static int Mevents; // Working number of event periods - -//----------------------------------------------------------------------------- -// External Functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// input_countObjects (called by swmm_open in swmm5.c) -// input_readData (called by swmm_open in swmm5.c) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int addObject(int objType, char* id); -static int getTokens(char *s); -static int parseLine(int sect, char* line); -static int readOption(char* line); -static int readTitle(char* line); -static int readControl(char* tok[], int ntoks); -static int readNode(int type); -static int readLink(int type); -static int readEvent(char* tok[], int ntoks); - -//============================================================================= - -int input_countObjects() -// -// Input: none -// Output: returns error code -// Purpose: reads input file to determine number of system objects. -// -{ - char line[MAXLINE+1]; // line from input data file - char wLine[MAXLINE+1]; // working copy of input line - char *tok; // first string token of line - int sect = -1, newsect; // input data sections - int errcode = 0; // error code - int errsum = 0; // number of errors found - int i; - long lineCount = 0; - - // --- initialize number of objects & set default values - if ( ErrorCode ) return ErrorCode; - error_setInpError(0, ""); - for (i = 0; i < MAX_OBJ_TYPES; i++) Nobjects[i] = 0; - for (i = 0; i < MAX_NODE_TYPES; i++) Nnodes[i] = 0; - for (i = 0; i < MAX_LINK_TYPES; i++) Nlinks[i] = 0; - controls_init(); - - // --- make pass through data file counting number of each object - while ( fgets(line, MAXLINE, Finp.file) != NULL ) - { - // --- skip blank lines & those beginning with a comment - lineCount++; - sstrncpy(wLine, line, MAXLINE); // make working copy of line - tok = strtok(wLine, SEPSTR); // get first text token on line - if ( tok == NULL ) continue; - if ( *tok == ';' ) continue; - - // --- check if line begins with a new section heading - if ( *tok == '[' ) - { - // --- look for heading in list of section keywords - newsect = findmatch(tok, SectWords); - if ( newsect >= 0 ) - { - sect = newsect; - continue; - } - else - { - sect = -1; - errcode = ERR_KEYWORD; - } - } - - // --- if in OPTIONS section then read the option setting - // otherwise add object and its ID name (tok) to project - if ( sect == s_OPTION ) errcode = readOption(line); - else if ( sect >= 0 ) errcode = addObject(sect, tok); - - // --- report any error found - if ( errcode ) - { - report_writeInputErrorMsg(errcode, sect, line, lineCount); - errsum++; - if (errsum >= MAXERRS ) break; - } - } - - // --- set global error code if input errors were found - if ( errsum > 0 ) ErrorCode = ERR_INPUT; - return ErrorCode; -} - -//============================================================================= - -int input_readData() -// -// Input: none -// Output: returns error code -// Purpose: reads input file to determine input parameters for each object. -// -{ - char line[MAXLINE+1]; // line from input data file - char wLine[MAXLINE+1]; // working copy of input line - char* comment; // ptr. to start of comment in input line - int sect, newsect; // data sections - int inperr, errsum; // error code & total error count - int lineLength; // number of characters in input line - int i; - long lineCount = 0; - - // --- initialize working item count arrays - // (final counts in Mobjects, Mnodes & Mlinks should - // match those in Nobjects, Nnodes and Nlinks). - if ( ErrorCode ) return ErrorCode; - error_setInpError(0, ""); - for (i = 0; i < MAX_OBJ_TYPES; i++) Mobjects[i] = 0; - for (i = 0; i < MAX_NODE_TYPES; i++) Mnodes[i] = 0; - for (i = 0; i < MAX_LINK_TYPES; i++) Mlinks[i] = 0; - Mevents = 0; - - // --- initialize starting date for all time series - for ( i = 0; i < Nobjects[TSERIES]; i++ ) - { - Tseries[i].lastDate = StartDate + StartTime; - } - - // --- read each line from input file - sect = 0; - errsum = 0; - rewind(Finp.file); - while ( fgets(line, MAXLINE, Finp.file) != NULL ) - { - // --- make copy of line and scan for tokens - lineCount++; - sstrncpy(wLine, line, MAXLINE); - Ntokens = getTokens(wLine); - - // --- skip blank lines and comments - if ( Ntokens == 0 ) continue; - if ( *Tok[0] == ';' ) continue; - - // --- check if max. line length exceeded - lineLength = (int)strlen(line); - if ( lineLength >= MAXLINE ) - { - // --- don't count comment if present - comment = strchr(line, ';'); - if ( comment ) lineLength = (int)(comment - line); // Pointer math here - if ( lineLength >= MAXLINE ) - { - inperr = ERR_LINE_LENGTH; - report_writeInputErrorMsg(inperr, sect, line, lineCount); - errsum++; - } - } - - // --- check if at start of a new input section - if (*Tok[0] == '[') - { - // --- match token against list of section keywords - newsect = findmatch(Tok[0], SectWords); - if (newsect >= 0) - { - // --- SPECIAL CASE FOR TRANSECTS - // finish processing the last set of transect data - if ( sect == s_TRANSECT ) - transect_validate(Nobjects[TRANSECT]-1); - - // --- begin a new input section - sect = newsect; - continue; - } - else - { - inperr = error_setInpError(ERR_KEYWORD, Tok[0]); - report_writeInputErrorMsg(inperr, sect, line, lineCount); - errsum++; - break; - } - } - - // --- otherwise parse tokens from input line - else - { - inperr = parseLine(sect, line); - if ( inperr > 0 ) - { - errsum++; - if ( errsum > MAXERRS ) report_writeLine(FMT19); - else report_writeInputErrorMsg(inperr, sect, line, lineCount); - } - } - - // --- stop if reach end of file or max. error count - if (errsum > MAXERRS) break; - } /* End of while */ - - // --- check for errors - if (errsum > 0) ErrorCode = ERR_INPUT; - return ErrorCode; -} - -//============================================================================= - -int addObject(int objType, char* id) -// -// Input: objType = object type index -// id = object's ID string -// Output: returns an error code -// Purpose: adds a new object to the project. -// -{ - int errcode = 0; - switch( objType ) - { - case s_RAINGAGE: - if ( !project_addObject(GAGE, id, Nobjects[GAGE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[GAGE]++; - break; - - case s_SUBCATCH: - if ( !project_addObject(SUBCATCH, id, Nobjects[SUBCATCH]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[SUBCATCH]++; - break; - - case s_AQUIFER: - if ( !project_addObject(AQUIFER, id, Nobjects[AQUIFER]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[AQUIFER]++; - break; - - case s_UNITHYD: - // --- the same Unit Hydrograph can span several lines - if ( project_findObject(UNITHYD, id) < 0 ) - { - if ( !project_addObject(UNITHYD, id, Nobjects[UNITHYD]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[UNITHYD]++; - } - break; - - case s_SNOWMELT: - // --- the same Snowmelt object can appear on several lines - if ( project_findObject(SNOWMELT, id) < 0 ) - { - if ( !project_addObject(SNOWMELT, id, Nobjects[SNOWMELT]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[SNOWMELT]++; - } - break; - - case s_JUNCTION: - if ( !project_addObject(NODE, id, Nobjects[NODE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[NODE]++; - Nnodes[JUNCTION]++; - break; - - case s_OUTFALL: - if ( !project_addObject(NODE, id, Nobjects[NODE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[NODE]++; - Nnodes[OUTFALL]++; - break; - - case s_STORAGE: - if ( !project_addObject(NODE, id, Nobjects[NODE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[NODE]++; - Nnodes[STORAGE]++; - break; - - case s_DIVIDER: - if ( !project_addObject(NODE, id, Nobjects[NODE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[NODE]++; - Nnodes[DIVIDER]++; - break; - - case s_CONDUIT: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[CONDUIT]++; - break; - - case s_PUMP: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[PUMP]++; - break; - - case s_ORIFICE: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[ORIFICE]++; - break; - - case s_WEIR: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[WEIR]++; - break; - - case s_OUTLET: - if ( !project_addObject(LINK, id, Nobjects[LINK]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LINK]++; - Nlinks[OUTLET]++; - break; - - case s_POLLUTANT: - if ( !project_addObject(POLLUT, id, Nobjects[POLLUT]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[POLLUT]++; - break; - - case s_LANDUSE: - if ( !project_addObject(LANDUSE, id, Nobjects[LANDUSE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[LANDUSE]++; - break; - - case s_PATTERN: - // --- a time pattern can span several lines - if ( project_findObject(TIMEPATTERN, id) < 0 ) - { - if ( !project_addObject(TIMEPATTERN, id, Nobjects[TIMEPATTERN]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[TIMEPATTERN]++; - } - break; - - case s_CURVE: - // --- a Curve can span several lines - if ( project_findObject(CURVE, id) < 0 ) - { - if ( !project_addObject(CURVE, id, Nobjects[CURVE]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[CURVE]++; - - // --- check for a conduit shape curve - id = strtok(NULL, SEPSTR); - if ( findmatch(id, CurveTypeWords) == SHAPE_CURVE ) - Nobjects[SHAPE]++; - } - break; - - case s_TIMESERIES: - // --- a Time Series can span several lines - if ( project_findObject(TSERIES, id) < 0 ) - { - if ( !project_addObject(TSERIES, id, Nobjects[TSERIES]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[TSERIES]++; - } - break; - - case s_CONTROL: - if ( match(id, w_RULE) ) Nobjects[CONTROL]++; - else controls_addToCount(id); - break; - - case s_TRANSECT: - // --- for TRANSECTS, ID name appears as second entry on X1 line - if ( match(id, "X1") ) - { - id = strtok(NULL, SEPSTR); - if ( id ) - { - if ( !project_addObject(TRANSECT, id, Nobjects[TRANSECT]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[TRANSECT]++; - } - } - break; - - case s_LID_CONTROL: - // --- an LID object can span several lines - if ( project_findObject(LID, id) < 0 ) - { - if ( !project_addObject(LID, id, Nobjects[LID]) ) - { - errcode = error_setInpError(ERR_DUP_NAME, id); - } - Nobjects[LID]++; - } - break; - - case s_EVENT: NumEvents++; break; - - case s_STREET: - if ( !project_addObject(STREET, id, Nobjects[STREET]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[STREET]++; - break; - - case s_INLET: - // --- an INLET object can span several lines - if (project_findObject(INLET, id) < 0) - { - if ( !project_addObject(INLET, id, Nobjects[INLET]) ) - errcode = error_setInpError(ERR_DUP_NAME, id); - Nobjects[INLET]++; - } - break; - } - return errcode; -} - -//============================================================================= - -int parseLine(int sect, char *line) -// -// Input: sect = current section of input file -// *line = line of text read from input file -// Output: returns error code or 0 if no error found -// Purpose: parses contents of a tokenized line of text read from input file. -// -{ - int j, err; - switch (sect) - { - case s_TITLE: - return readTitle(line); - - case s_RAINGAGE: - j = Mobjects[GAGE]; - err = gage_readParams(j, Tok, Ntokens); - Mobjects[GAGE]++; - return err; - - case s_TEMP: - return climate_readParams(Tok, Ntokens); - - case s_EVAP: - return climate_readEvapParams(Tok, Ntokens); - - case s_ADJUST: - return climate_readAdjustments(Tok, Ntokens); - - case s_SUBCATCH: - j = Mobjects[SUBCATCH]; - err = subcatch_readParams(j, Tok, Ntokens); - Mobjects[SUBCATCH]++; - return err; - - case s_SUBAREA: - return subcatch_readSubareaParams(Tok, Ntokens); - - case s_INFIL: - return infil_readParams(InfilModel, Tok, Ntokens); - - case s_AQUIFER: - j = Mobjects[AQUIFER]; - err = gwater_readAquiferParams(j, Tok, Ntokens); - Mobjects[AQUIFER]++; - return err; - - case s_GROUNDWATER: - return gwater_readGroundwaterParams(Tok, Ntokens); - - case s_GWF: - return gwater_readFlowExpression(Tok, Ntokens); - - case s_SNOWMELT: - return snow_readMeltParams(Tok, Ntokens); - - case s_JUNCTION: - return readNode(JUNCTION); - - case s_OUTFALL: - return readNode(OUTFALL); - - case s_STORAGE: - return readNode(STORAGE); - - case s_DIVIDER: - return readNode(DIVIDER); - - case s_CONDUIT: - return readLink(CONDUIT); - - case s_PUMP: - return readLink(PUMP); - - case s_ORIFICE: - return readLink(ORIFICE); - - case s_WEIR: - return readLink(WEIR); - - case s_OUTLET: - return readLink(OUTLET); - - case s_XSECTION: - return link_readXsectParams(Tok, Ntokens); - - case s_TRANSECT: - return transect_readParams(&Mobjects[TRANSECT], Tok, Ntokens); - - case s_LOSSES: - return link_readLossParams(Tok, Ntokens); - - case s_POLLUTANT: - j = Mobjects[POLLUT]; - err = landuse_readPollutParams(j, Tok, Ntokens); - Mobjects[POLLUT]++; - return err; - - case s_LANDUSE: - j = Mobjects[LANDUSE]; - err = landuse_readParams(j, Tok, Ntokens); - Mobjects[LANDUSE]++; - return err; - - case s_BUILDUP: - return landuse_readBuildupParams(Tok, Ntokens); - - case s_WASHOFF: - return landuse_readWashoffParams(Tok, Ntokens); - - case s_COVERAGE: - return subcatch_readLanduseParams(Tok, Ntokens); - - case s_INFLOW: - return inflow_readExtInflow(Tok, Ntokens); - - case s_DWF: - return inflow_readDwfInflow(Tok, Ntokens); - - case s_PATTERN: - return inflow_readDwfPattern(Tok, Ntokens); - - case s_RDII: - return rdii_readRdiiInflow(Tok, Ntokens); - - case s_UNITHYD: - return rdii_readUnitHydParams(Tok, Ntokens); - - case s_LOADING: - return subcatch_readInitBuildup(Tok, Ntokens); - - case s_TREATMENT: - return treatmnt_readExpression(Tok, Ntokens); - - case s_CURVE: - return table_readCurve(Tok, Ntokens); - - case s_TIMESERIES: - return table_readTimeseries(Tok, Ntokens); - - case s_CONTROL: - return readControl(Tok, Ntokens); - - case s_REPORT: - return report_readOptions(Tok, Ntokens); - - case s_FILE: - return iface_readFileParams(Tok, Ntokens); - - case s_LID_CONTROL: - return lid_readProcParams(Tok, Ntokens); - - case s_LID_USAGE: - return lid_readGroupParams(Tok, Ntokens); - - case s_EVENT: - return readEvent(Tok, Ntokens); - - case s_STREET: - return street_readParams(Tok, Ntokens); - - case s_INLET: - return inlet_readDesignParams(Tok, Ntokens); - - case s_INLET_USAGE: - return inlet_readUsageParams(Tok, Ntokens); - - default: return 0; - } -} - -//============================================================================= - -int readControl(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// Purpose: reads a line of input for a control rule. -// -{ - int index; - int keyword; - - // --- check for minimum number of tokens - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - - if (match(tok[0], w_VARIABLE)) - return controls_addVariable(tok, ntoks); - if (match(tok[0], w_EXPRESSION)) - return controls_addExpression(tok, ntoks); - - // --- get index of control rule keyword - keyword = findmatch(tok[0], RuleKeyWords); - if ( keyword < 0 ) return error_setInpError(ERR_KEYWORD, tok[0]); - - // --- if line begins a new control rule, add rule ID to the database - if ( keyword == 0 ) - { - if ( !project_addObject(CONTROL, tok[1], Mobjects[CONTROL]) ) - { - return error_setInpError(ERR_DUP_NAME, Tok[1]); - } - Mobjects[CONTROL]++; - } - - // --- get index of last control rule processed - index = Mobjects[CONTROL] - 1; - if ( index < 0 ) return error_setInpError(ERR_RULE, ""); - - // --- add current line as a new clause to the control rule - return controls_addRuleClause(index, keyword, Tok, Ntokens); -} - -//============================================================================= - -int readOption(char* line) -// -// Input: line = line of input data -// Output: returns error code -// Purpose: reads an input line containing a project option. -// -{ - Ntokens = getTokens(line); - if ( Ntokens < 2 ) return 0; - return project_readOption(Tok[0], Tok[1]); -} - -//============================================================================= - -int readTitle(char* line) -// -// Input: line = line from input file -// Output: returns error code -// Purpose: reads project title from line of input. -// -{ - int i, n; - for (i = 0; i < MAXTITLE; i++) - { - // --- find next empty Title entry - if ( strlen(Title[i]) == 0 ) - { - // --- strip line feed character from input line - n = (int)strlen(line); - if (line[n-1] == 10) line[n-1] = ' '; - - // --- copy input line into Title entry - sstrncpy(Title[i], line, MAXMSG); - break; - } - } - return 0; -} - -//============================================================================= - -int readNode(int type) -// -// Input: type = type of node -// Output: returns error code -// Purpose: reads data for a node from a line of input. -// -{ - int j = Mobjects[NODE]; - int k = Mnodes[type]; - int err = node_readParams(j, type, k, Tok, Ntokens); - Mobjects[NODE]++; - Mnodes[type]++; - return err; -} - -//============================================================================= - -int readLink(int type) -// -// Input: type = type of link -// Output: returns error code -// Purpose: reads data for a link from a line of input. -// -{ - int j = Mobjects[LINK]; - int k = Mlinks[type]; - int err = link_readParams(j, type, k, Tok, Ntokens); - Mobjects[LINK]++; - Mlinks[type]++; - return err; -} - -//============================================================================= - -int readEvent(char* tok[], int ntoks) -{ - DateTime x[4]; - - if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); - if ( !datetime_strToDate(tok[0], &x[0]) ) - return error_setInpError(ERR_DATETIME, tok[0]); - if ( !datetime_strToTime(tok[1], &x[1]) ) - return error_setInpError(ERR_DATETIME, tok[1]); - if ( !datetime_strToDate(tok[2], &x[2]) ) - return error_setInpError(ERR_DATETIME, tok[2]); - if ( !datetime_strToTime(tok[3], &x[3]) ) - return error_setInpError(ERR_DATETIME, tok[3]); - - Event[Mevents].start = x[0] + x[1]; - Event[Mevents].end = x[2] + x[3]; - if ( Event[Mevents].start >= Event[Mevents].end ) - return error_setInpError(ERR_DATETIME, " - start date exceeds end date"); - Mevents++; - return 0; -} - -//============================================================================= - -int findmatch(char *s, char *keyword[]) -// -// Input: s = character string -// keyword = array of keyword strings -// Output: returns index of matching keyword or -1 if no match found -// Purpose: finds match between string and array of keyword strings. -// -{ - int i = 0; - while (keyword[i] != NULL) - { - if (match(s, keyword[i])) return(i); - i++; - } - return(-1); -} - -//============================================================================= - -int match(char *str, char *substr) -// -// Input: str = character string being searched -// substr = sub-string being searched for -// Output: returns 1 if sub-string found, 0 if not -// Purpose: sees if a sub-string of characters appears in a string -// (not case sensitive). -// -{ - int i,j,k; - - // --- fail if substring is empty - if (!substr[0]) return(0); - - // --- skip leading blanks of str - for (k = 0; str[k]; k++) - { - if (str[k] != ' ') break; - } - - // --- check if substr matches remainder of str - for (i = k,j = 0; substr[j]; i++,j++) - { - if (!str[i] || UCHAR(str[i]) != UCHAR(substr[j])) return(0); - } - return(1); -} - -//============================================================================= - -int getInt(char *s, int *y) -// -// Input: s = a character string -// Output: y = converted value of s, -// returns 1 if conversion successful, 0 if not -// Purpose: converts a string to an integer number. -// -{ - double x; - if ( getDouble(s, &x) ) - { - if ( x < 0.0 ) x -= 0.01; - else x += 0.01; - *y = (int)x; - return 1; - } - *y = 0; - return 0; -} - -//============================================================================= - -int getFloat(char *s, float *y) -// -// Input: s = a character string -// Output: y = converted value of s, -// returns 1 if conversion successful, 0 if not -// Purpose: converts a string to a single precision floating point number. -// -{ - char *endptr; - *y = (float) strtod(s, &endptr); - if (*endptr > 0) return(0); - return(1); -} - -//============================================================================= - -int getDouble(char *s, double *y) -// -// Input: s = a character string -// Output: y = converted value of s, -// returns 1 if conversion successful, 0 if not -// Purpose: converts a string to a double precision floating point number. -// -{ - char *endptr; - *y = strtod(s, &endptr); - if (*endptr > 0) return(0); - return(1); -} - -//============================================================================= - -int getTokens(char *s) -// -// Input: s = a character string -// Output: returns number of tokens found in s -// Purpose: scans a string for tokens, saving pointers to them -// in shared variable Tok[]. -// -// Notes: Tokens can be separated by the characters listed in SEPSTR -// (spaces, tabs, newline, carriage return) which is defined -// in CONSTS.H. Text between quotes is treated as a single token. -// -{ - int len, n; - int m; - char *c; - - // --- begin with no tokens - for (n = 0; n < MAXTOKS; n++) Tok[n] = NULL; - n = 0; - - // --- truncate s at start of comment - c = strchr(s,';'); - if (c) *c = '\0'; - len = (int)strlen(s); - - // --- scan s for tokens until nothing left - while (len > 0 && n < MAXTOKS) - { - m = (int)strcspn(s,SEPSTR); // find token length - if (m == 0) s++; // no token found - else - { - if (*s == '"') // token begins with quote - { - s++; // start token after quote - len--; // reduce length of s - m = (int)strcspn(s,"\"\n"); // find end quote or new line - } - s[m] = '\0'; // null-terminate the token - Tok[n] = s; // save pointer to token - n++; // update token count - s += m+1; // begin next token - } - len -= m+1; // update length of s - } - return n; -} - -//============================================================================= diff --git a/src/inputrpt.c b/src/inputrpt.c deleted file mode 100644 index 46a6f952f..000000000 --- a/src/inputrpt.c +++ /dev/null @@ -1,354 +0,0 @@ -//----------------------------------------------------------------------------- -// inputrpt.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Report writing functions for input data summary. -// -// Update History -// ============== -// Build 5.2.0: -// - Support added for reporting Street geometry tables. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" -#include "lid.h" - -#define WRITE(x) (report_writeLine((x))) - -//============================================================================= - -void inputrpt_writeInput() -// -// Input: none -// Output: none -// Purpose: writes summary of input data to report file. -// -{ - int m; - int i, k; - int lidCount = 0; - if ( ErrorCode ) return; - - WRITE(""); - WRITE("*************"); - WRITE("Element Count"); - WRITE("*************"); - fprintf(Frpt.file, "\n Number of rain gages ...... %d", Nobjects[GAGE]); - fprintf(Frpt.file, "\n Number of subcatchments ... %d", Nobjects[SUBCATCH]); - fprintf(Frpt.file, "\n Number of nodes ........... %d", Nobjects[NODE]); - fprintf(Frpt.file, "\n Number of links ........... %d", Nobjects[LINK]); - fprintf(Frpt.file, "\n Number of pollutants ...... %d", Nobjects[POLLUT]); - fprintf(Frpt.file, "\n Number of land uses ....... %d", Nobjects[LANDUSE]); - - if ( Nobjects[POLLUT] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("*****************"); - WRITE("Pollutant Summary"); - WRITE("*****************"); - fprintf(Frpt.file, - "\n Ppt. GW Kdecay"); - fprintf(Frpt.file, - "\n Name Units Concen. Concen. 1/days CoPollutant"); - fprintf(Frpt.file, - "\n -----------------------------------------------------------------------"); - for (i = 0; i < Nobjects[POLLUT]; i++) - { - fprintf(Frpt.file, "\n %-20s %5s%10.2f%10.2f%10.2f", Pollut[i].ID, - QualUnitsWords[Pollut[i].units], Pollut[i].pptConcen, - Pollut[i].gwConcen, Pollut[i].kDecay*SECperDAY); - if ( Pollut[i].coPollut >= 0 ) - fprintf(Frpt.file, " %-s (%.2f)", - Pollut[Pollut[i].coPollut].ID, Pollut[i].coFraction); - } - } - - if ( Nobjects[LANDUSE] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("***************"); - WRITE("Landuse Summary"); - WRITE("***************"); - fprintf(Frpt.file, - "\n Sweeping Maximum Last"); - fprintf(Frpt.file, - "\n Name Interval Removal Swept"); - fprintf(Frpt.file, - "\n ---------------------------------------------------"); - for (i=0; i 0 ) - { - WRITE(""); - WRITE(""); - WRITE("****************"); - WRITE("Raingage Summary"); - WRITE("****************"); - fprintf(Frpt.file, -"\n Data Recording"); - fprintf(Frpt.file, -"\n Name Data Source Type Interval "); - fprintf(Frpt.file, -"\n ------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[GAGE]; i++) - { - if ( Gage[i].tSeries >= 0 ) - { - fprintf(Frpt.file, "\n %-20s %-30s ", - Gage[i].ID, Tseries[Gage[i].tSeries].ID); - fprintf(Frpt.file, "%-10s %3d min.", - RainTypeWords[Gage[i].rainType], - (Gage[i].rainInterval)/60); - } - else fprintf(Frpt.file, "\n %-20s %-30s", - Gage[i].ID, Gage[i].fname); - } - } - - if ( Nobjects[SUBCATCH] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("********************"); - WRITE("Subcatchment Summary"); - WRITE("********************"); - fprintf(Frpt.file, -"\n Name Area Width %%Imperv %%Slope Rain Gage Outlet "); - fprintf(Frpt.file, -"\n -----------------------------------------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[SUBCATCH]; i++) - { - fprintf(Frpt.file,"\n %-20s %10.2f%10.2f%10.2f%10.4f %-20s ", - Subcatch[i].ID, Subcatch[i].area*UCF(LANDAREA), - Subcatch[i].width*UCF(LENGTH), Subcatch[i].fracImperv*100.0, - Subcatch[i].slope*100.0, Gage[Subcatch[i].gage].ID); - if ( Subcatch[i].outNode >= 0 ) - { - fprintf(Frpt.file, "%-20s", Node[Subcatch[i].outNode].ID); - } - else if ( Subcatch[i].outSubcatch >= 0 ) - { - fprintf(Frpt.file, "%-20s", Subcatch[Subcatch[i].outSubcatch].ID); - } - if ( Subcatch[i].lidArea ) lidCount++; - } - } - if ( lidCount > 0 ) lid_writeSummary(); - - if ( Nobjects[NODE] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("************"); - WRITE("Node Summary"); - WRITE("************"); - fprintf(Frpt.file, -"\n Invert Max. Ponded External"); - fprintf(Frpt.file, -"\n Name Type Elev. Depth Area Inflow "); - fprintf(Frpt.file, -"\n -------------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[NODE]; i++) - { - fprintf(Frpt.file, "\n %-20s %-16s%10.2f%10.2f%10.1f", Node[i].ID, - NodeTypeWords[Node[i].type-JUNCTION], - Node[i].invertElev*UCF(LENGTH), - Node[i].fullDepth*UCF(LENGTH), - Node[i].pondedArea*UCF(LENGTH)*UCF(LENGTH)); - if ( Node[i].extInflow || Node[i].dwfInflow || Node[i].rdiiInflow ) - { - fprintf(Frpt.file, " Yes"); - } - } - } - - if ( Nobjects[LINK] > 0 ) - { - WRITE(""); - WRITE(""); - WRITE("************"); - WRITE("Link Summary"); - WRITE("************"); - fprintf(Frpt.file, -"\n Name From Node To Node Type Length %%Slope Roughness"); - fprintf(Frpt.file, -"\n ---------------------------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[LINK]; i++) - { - // --- list end nodes in their original orientation - if ( Link[i].direction == 1 ) - fprintf(Frpt.file, "\n %-16s %-16s %-16s ", - Link[i].ID, Node[Link[i].node1].ID, Node[Link[i].node2].ID); - else - fprintf(Frpt.file, "\n %-16s %-16s %-16s ", - Link[i].ID, Node[Link[i].node2].ID, Node[Link[i].node1].ID); - - // --- list link type - if ( Link[i].type == PUMP ) - { - k = Link[i].subIndex; - fprintf(Frpt.file, "%-5s PUMP ", - PumpTypeWords[Pump[k].type]); - } - else fprintf(Frpt.file, "%-12s", - LinkTypeWords[Link[i].type-CONDUIT]); - - // --- list length, slope and roughness for conduit links - if (Link[i].type == CONDUIT) - { - k = Link[i].subIndex; - fprintf(Frpt.file, "%10.1f%10.4f%10.4f", - Conduit[k].length*UCF(LENGTH), - Conduit[k].slope*100.0*Link[i].direction, - Conduit[k].roughness); - } - } - - WRITE(""); - WRITE(""); - WRITE("*********************"); - WRITE("Cross Section Summary"); - WRITE("*********************"); - fprintf(Frpt.file, -"\n Full Full Hyd. Max. No. of Full"); - fprintf(Frpt.file, -"\n Conduit Shape Depth Area Rad. Width Barrels Flow"); - fprintf(Frpt.file, -"\n ---------------------------------------------------------------------------------------"); - for (i = 0; i < Nobjects[LINK]; i++) - { - if (Link[i].type == CONDUIT) - { - k = Link[i].subIndex; - fprintf(Frpt.file, "\n %-16s ", Link[i].ID); - if ( Link[i].xsect.type == CUSTOM ) - fprintf(Frpt.file, "%-16s ", Curve[Link[i].xsect.transect].ID); - else if ( Link[i].xsect.type == IRREGULAR ) - fprintf(Frpt.file, "%-16s ", - Transect[Link[i].xsect.transect].ID); - else if ( Link[i].xsect.type == STREET_XSECT ) - fprintf(Frpt.file, "%-16s ", - Street[Link[i].xsect.transect].ID); - else fprintf(Frpt.file, "%-16s ", - XsectTypeWords[Link[i].xsect.type]); - fprintf(Frpt.file, "%8.2f %8.2f %8.2f %8.2f %3d %8.2f", - Link[i].xsect.yFull*UCF(LENGTH), - Link[i].xsect.aFull*UCF(LENGTH)*UCF(LENGTH), - Link[i].xsect.rFull*UCF(LENGTH), - Link[i].xsect.wMax*UCF(LENGTH), - Conduit[k].barrels, - Link[i].qFull*UCF(FLOW)); - } - } - } - - if (Nobjects[SHAPE] > 0) - { - WRITE(""); - WRITE(""); - WRITE("*************"); - WRITE("Shape Summary"); - WRITE("*************"); - for (i = 0; i < Nobjects[SHAPE]; i++) - { - k = Shape[i].curve; - fprintf(Frpt.file, "\n\n Shape %s", Curve[k].ID); - fprintf(Frpt.file, "\n Area: "); - for ( m = 1; m < N_SHAPE_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Shape[i].areaTbl[m]); - } - fprintf(Frpt.file, "\n Hrad: "); - for ( m = 1; m < N_SHAPE_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Shape[i].hradTbl[m]); - } - fprintf(Frpt.file, "\n Width: "); - for ( m = 1; m < N_SHAPE_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Shape[i].widthTbl[m]); - } - } - } - - if (Nobjects[TRANSECT] > 0) - { - WRITE(""); - WRITE(""); - WRITE("****************"); - WRITE("Transect Summary"); - WRITE("****************"); - for (i = 0; i < Nobjects[TRANSECT]; i++) - { - fprintf(Frpt.file, "\n\n Transect %s", Transect[i].ID); - fprintf(Frpt.file, "\n Area: "); - for ( m = 1; m < N_TRANSECT_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Transect[i].areaTbl[m]); - } - fprintf(Frpt.file, "\n Hrad: "); - for ( m = 1; m < N_TRANSECT_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Transect[i].hradTbl[m]); - } - fprintf(Frpt.file, "\n Width: "); - for ( m = 1; m < N_TRANSECT_TBL; m++) - { - if ( m % 5 == 1 ) fprintf(Frpt.file,"\n "); - fprintf(Frpt.file, "%10.4f ", Transect[i].widthTbl[m]); - } - } - } - - if (Nobjects[STREET] > 0) - { - WRITE(""); - WRITE(""); - WRITE("**************"); - WRITE("Street Summary"); - WRITE("**************"); - for (i = 0; i < Nobjects[STREET]; i++) - { - fprintf(Frpt.file, "\n\n Street %s", Street[i].ID); - fprintf(Frpt.file, "\n Area: "); - for (m = 1; m < Street[i].transect.nTbl; m++) - { - if (m % 5 == 1) fprintf(Frpt.file, "\n "); - fprintf(Frpt.file, "%10.4f ", Street[i].transect.areaTbl[m]); - } - fprintf(Frpt.file, "\n Hrad: "); - for (m = 1; m < Street[i].transect.nTbl; m++) - { - if (m % 5 == 1) fprintf(Frpt.file, "\n "); - fprintf(Frpt.file, "%10.4f ", Street[i].transect.hradTbl[m]); - } - fprintf(Frpt.file, "\n Width: "); - for (m = 1; m < Street[i].transect.nTbl; m++) - { - if (m % 5 == 1) fprintf(Frpt.file, "\n "); - fprintf(Frpt.file, "%10.4f ", Street[i].transect.widthTbl[m]); - } - } - } - WRITE(""); -} diff --git a/src/keywords.c b/src/keywords.c deleted file mode 100644 index 5f4b4e443..000000000 --- a/src/keywords.c +++ /dev/null @@ -1,172 +0,0 @@ -//----------------------------------------------------------------------------- -// keywords.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 06/01/22 (Build 5.2.1) -// Author: L. Rossman -// -// Exportable keyword dictionary -// -// NOTE: the keywords in each list must appear in same order used -// by its complementary enumerated variable in enums.h and -// must be terminated by NULL. The actual text of each keyword -// is defined in text.h. -// -// Update History -// ============== -// Build 5.1.007: -// - Keywords for Ignore RDII option and groundwater flow equation -// and climate adjustment input sections added. -// Build 5.1.008: -// - Keyword arrays placed in alphabetical order for better readability. -// - Keywords added for Minimum Routing Step and Number of Threads options. -// Build 5.1.010: -// - New Modified Green Ampt keyword added to InfilModelWords. -// - New Roadway weir keyword added to WeirTypeWords. -// Build 5.1.011: -// - New section keyword for [EVENTS] added. -// Build 5.1.013: -// - New option keywords w_SURCHARGE_METHOD, w_RULE_STEP, w_AVERAGES -// and w_WEIR added. -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for variable speed pumps. -// - Support added for analytical storage shapes. -// - Support added for RptFlags.disabled option. -// Build 5.2.1: -// - Adds NONE to the list of NormalFlowWords. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include // need this to define NULL -#include "text.h" - -char* BuildupTypeWords[] = { w_NONE, w_POW, w_EXP, w_SAT, w_EXT, NULL}; -char* CurveTypeWords[] = { w_STORAGE, w_DIVERSION, w_TIDAL, w_RATING, - w_CONTROLS, w_SHAPE, w_WEIR, - w_PUMP1, w_PUMP2, w_PUMP3, w_PUMP4, - w_PUMP5, NULL}; -char* DividerTypeWords[] = { w_CUTOFF, w_TABULAR, w_WEIR, w_OVERFLOW, NULL}; -char* EvapTypeWords[] = { w_CONSTANT, w_MONTHLY, w_TIMESERIES, - w_TEMPERATURE, w_FILE, w_RECOVERY, - w_DRYONLY, NULL}; -char* FileTypeWords[] = { w_RAINFALL, w_RUNOFF, w_HOTSTART, w_RDII, - w_INFLOWS, w_OUTFLOWS, NULL}; -char* FileModeWords[] = { w_NO, w_SCRATCH, w_USE, w_SAVE, NULL}; -char* FlowUnitWords[] = { w_CFS, w_GPM, w_MGD, w_CMS, w_LPS, w_MLD, NULL}; -char* ForceMainEqnWords[] = { w_H_W, w_D_W, NULL}; -char* GageDataWords[] = { w_TIMESERIES, w_FILE, NULL}; -char* InfilModelWords[] = { w_HORTON, w_MOD_HORTON, w_GREEN_AMPT, - w_MOD_GREEN_AMPT, w_CURVE_NUMEBR, NULL}; -char* InertDampingWords[] = { w_NONE, w_PARTIAL, w_FULL, NULL}; -char* LinkOffsetWords[] = { w_DEPTH, w_ELEVATION, NULL}; -char* LinkTypeWords[] = { w_CONDUIT, w_PUMP, w_ORIFICE, - w_WEIR, w_OUTLET }; -char* LoadUnitsWords[] = { w_LBS, w_KG, w_LOGN }; -char* NodeTypeWords[] = { w_JUNCTION, w_OUTFALL, - w_STORAGE, w_DIVIDER }; -char* NoneAllWords[] = { w_NONE, w_ALL, NULL}; -char* NormalFlowWords[] = { w_SLOPE, w_FROUDE, w_BOTH, w_NONE, NULL}; -char* NormalizerWords[] = { w_PER_AREA, w_PER_CURB, NULL}; -char* NoYesWords[] = { w_NO, w_YES, NULL}; -char* OffOnWords[] = { w_OFF, w_ON, NULL}; -char* OldRouteModelWords[] = { w_NONE, w_NF, w_KW, w_EKW, w_DW, NULL}; -char* OptionWords[] = { w_FLOW_UNITS, w_INFIL_MODEL, - w_ROUTE_MODEL, w_START_DATE, - w_START_TIME, w_END_DATE, - w_END_TIME, w_REPORT_START_DATE, - w_REPORT_START_TIME, w_SWEEP_START, - w_SWEEP_END, w_START_DRY_DAYS, - w_WET_STEP, w_DRY_STEP, - w_ROUTE_STEP, w_RULE_STEP, - w_REPORT_STEP, - w_ALLOW_PONDING, w_INERT_DAMPING, - w_SLOPE_WEIGHTING, w_VARIABLE_STEP, - w_NORMAL_FLOW_LTD, w_LENGTHENING_STEP, - w_MIN_SURFAREA, w_COMPATIBILITY, - w_SKIP_STEADY_STATE, w_TEMPDIR, - w_IGNORE_RAINFALL, w_FORCE_MAIN_EQN, - w_LINK_OFFSETS, w_MIN_SLOPE, - w_IGNORE_SNOWMELT, w_IGNORE_GWATER, - w_IGNORE_ROUTING, w_IGNORE_QUALITY, - w_MAX_TRIALS, w_HEAD_TOL, - w_SYS_FLOW_TOL, w_LAT_FLOW_TOL, - w_IGNORE_RDII, w_MIN_ROUTE_STEP, - w_NUM_THREADS, w_SURCHARGE_METHOD, - NULL }; -char* OrificeTypeWords[] = { w_SIDE, w_BOTTOM, NULL}; -char* OutfallTypeWords[] = { w_FREE, w_NORMAL, w_FIXED, w_TIDAL, - w_TIMESERIES, NULL}; -char* PatternTypeWords[] = { w_MONTHLY, w_DAILY, w_HOURLY, w_WEEKEND, NULL}; -char* PondingUnitsWords[] = { w_PONDED_FEET, w_PONDED_METERS }; -char* ProcessVarWords[] = { w_HRT, w_DT, w_FLOW, w_DEPTH, w_AREA, NULL}; -char* PumpTypeWords[] = { w_TYPE1, w_TYPE2, w_TYPE3, w_TYPE4, w_TYPE5, w_IDEAL }; -char* QualUnitsWords[] = { w_MGperL, w_UGperL, w_COUNTperL, NULL}; -char* RainTypeWords[] = { w_INTENSITY, w_VOLUME, w_CUMULATIVE, NULL}; -char* RainUnitsWords[] = { w_INCHES, w_MMETER, NULL}; -char* RelationWords[] = { w_TABULAR, w_FUNCTIONAL, - w_CYLINDRICAL, w_CONICAL, w_PARABOLIC, - w_PYRAMIDAL, NULL}; -char* ReportWords[] = { w_DISABLED, w_INPUT, w_SUBCATCH, w_NODE, w_LINK, - w_CONTINUITY, w_FLOWSTATS,w_CONTROLS, - w_AVERAGES, w_NODESTATS, NULL}; -char* RouteModelWords[] = { w_NONE, w_STEADY, w_KINWAVE, w_XKINWAVE, - w_DYNWAVE, NULL}; -char* RuleKeyWords[] = { w_RULE, w_IF, w_AND, w_OR, w_THEN, w_ELSE, - w_PRIORITY, NULL}; -char* SectWords[] = { ws_TITLE, ws_OPTION, - ws_FILE, ws_RAINGAGE, - ws_TEMP, ws_EVAP, - ws_SUBCATCH, ws_SUBAREA, - ws_INFIL, ws_AQUIFER, - ws_GROUNDWATER, ws_SNOWMELT, - ws_JUNCTION, ws_OUTFALL, - ws_STORAGE, ws_DIVIDER, - ws_CONDUIT, ws_PUMP, - ws_ORIFICE, ws_WEIR, - ws_OUTLET, ws_XSECTION, - ws_TRANSECT, ws_LOSS, - ws_CONTROL, ws_POLLUTANT, - ws_LANDUSE, ws_BUILDUP, - ws_WASHOFF, ws_COVERAGE, - ws_INFLOW, ws_DWF, - ws_PATTERN, ws_RDII, - ws_UNITHYD, ws_LOADING, - ws_TREATMENT, ws_CURVE, - ws_TIMESERIES, ws_REPORT, - ws_COORDINATE, ws_VERTICES, - ws_POLYGON, ws_LABEL, - ws_SYMBOL, ws_BACKDROP, - ws_TAG, ws_PROFILE, - ws_MAP, ws_LID_CONTROL, - ws_LID_USAGE, ws_GWF, - ws_ADJUST, ws_EVENT, - ws_STREET, ws_INLET_USAGE, - ws_INLET, NULL}; -char* SnowmeltWords[] = { w_PLOWABLE, w_IMPERV, w_PERV, w_REMOVAL, NULL}; -char* SurchargeWords[] = { w_EXTRAN, w_SLOT, NULL}; -char* TempKeyWords[] = { w_TIMESERIES, w_FILE, w_WINDSPEED, w_SNOWMELT, - w_ADC, NULL}; -char* TransectKeyWords[] = { w_NC, w_X1, w_GR, NULL}; -char* TreatTypeWords[] = { w_REMOVAL, w_CONCEN, NULL}; -char* UHTypeWords[] = { w_SHORT, w_MEDIUM, w_LONG, NULL}; -char* VolUnitsWords[] = { w_MGAL, w_MLTRS }; -char* VolUnitsWords2[] = { w_GAL, w_LTR }; -char* WashoffTypeWords[] = { w_NONE, w_EXP, w_RC, w_EMC, NULL}; -char* WeirTypeWords[] = { w_TRANSVERSE, w_SIDEFLOW, w_VNOTCH, - w_TRAPEZOIDAL, w_ROADWAY, NULL}; -char* XsectTypeWords[] = { w_DUMMY, w_CIRCULAR, - w_FILLED_CIRCULAR, w_RECT_CLOSED, - w_RECT_OPEN, w_TRAPEZOIDAL, - w_TRIANGULAR, w_PARABOLIC, - w_POWERFUNC, w_RECT_TRIANG, - w_RECT_ROUND, w_MOD_BASKET, - w_HORIZELLIPSE, w_VERTELLIPSE, - w_ARCH, w_EGGSHAPED, - w_HORSESHOE, w_GOTHIC, - w_CATENARY, w_SEMIELLIPTICAL, - w_BASKETHANDLE, w_SEMICIRCULAR, - w_IRREGULAR, w_CUSTOM, - w_FORCE_MAIN, w_STREET, - NULL}; diff --git a/src/keywords.h b/src/keywords.h deleted file mode 100644 index e330c59bc..000000000 --- a/src/keywords.h +++ /dev/null @@ -1,73 +0,0 @@ -//----------------------------------------------------------------------------- -// keywords.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Exportable keyword dictionary -// -// Update History -// ============== -// Build 5.1.008: -// - Keyword arrays listed in alphabetical order. -// Build 5.1.013: -// - New keyword array defined for surcharge method. -//----------------------------------------------------------------------------- - -#ifndef KEYWORDS_H -#define KEYWORDS_H - - -extern char* BuildupTypeWords[]; -extern char* CurveTypeWords[]; -extern char* DividerTypeWords[]; -extern char* DynWaveMethodWords[]; -extern char* EvapTypeWords[]; -extern char* FileModeWords[]; -extern char* FileTypeWords[]; -extern char* FlowUnitWords[]; -extern char* ForceMainEqnWords[]; -extern char* GageDataWords[]; -extern char* InertDampingWords[]; -extern char* InfilModelWords[]; -extern char* LinkOffsetWords[]; -extern char* LinkTypeWords[]; -extern char* LoadUnitsWords[]; -extern char* NodeTypeWords[]; -extern char* NoneAllWords[]; -extern char* NormalFlowWords[]; -extern char* NormalizerWords[]; -extern char* NoYesWords[]; -extern char* OldRouteModelWords[]; -extern char* OffOnWords[]; -extern char* OptionWords[]; -extern char* OrificeTypeWords[]; -extern char* OutfallTypeWords[]; -extern char* PatternTypeWords[]; -extern char* PondingUnitsWords[]; -extern char* ProcessVarWords[]; -extern char* PumpTypeWords[]; -extern char* QualUnitsWords[]; -extern char* RainTypeWords[]; -extern char* RainUnitsWords[]; -extern char* ReportWords[]; -extern char* RelationWords[]; -extern char* RouteModelWords[]; -extern char* RuleKeyWords[]; -extern char* SectWords[]; -extern char* SnowmeltWords[]; -extern char* SurchargeWords[]; -extern char* TempKeyWords[]; -extern char* TransectKeyWords[]; -extern char* TreatTypeWords[]; -extern char* UHTypeWords[]; -extern char* VolUnitsWords[]; -extern char* VolUnitsWords2[]; -extern char* WashoffTypeWords[]; -extern char* WeirTypeWords[]; -extern char* XsectTypeWords[]; - - -#endif //KEYWORDS_H diff --git a/src/kinwave.c b/src/kinwave.c deleted file mode 100644 index b137e4ce9..000000000 --- a/src/kinwave.c +++ /dev/null @@ -1,272 +0,0 @@ -//----------------------------------------------------------------------------- -// kinwave.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// M. Tryby (EPA) -// -// Kinematic wave flow routing functions. -// -// Update History -// ============== -// Build 5.1.008: -// - Conduit inflow passed to function that computes conduit losses. -// Build 5.1.014: -// - Arguments to function link_getLossRate changed. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "headers.h" -#include "findroot.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double WX = 0.6; // distance weighting -static const double WT = 0.6; // time weighting -static const double EPSIL = 0.001; // convergence criterion - -//----------------------------------------------------------------------------- -// Shared variables -//----------------------------------------------------------------------------- -static double Beta1; -static double C1; -static double C2; -static double Afull; -static double Qfull; -static TXsect* pXsect; - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// kinwave_execute (called by flowrout_execute) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static int solveContinuity(double qin, double ain, double* aout); -static void evalContinuity(double a, double* f, double* df, void* p); - -//============================================================================= - -int kinwave_execute(int j, double* qinflow, double* qoutflow, double tStep) -// -// Input: j = link index -// qinflow = inflow at current time (cfs) -// tStep = time step (sec) -// Output: qoutflow = outflow at current time (cfs), -// returns number of iterations used -// Purpose: finds outflow over time step tStep given flow entering a -// conduit using Kinematic Wave flow routing. -// -// -// ^ q3 -// t | -// | qin, ain |-------------------| qout, aout -// | | Flow ---> | -// |----> x q1, a1 |-------------------| q2, a2 -// -// -{ - int k; - int result = 1; - double dxdt, dq; - double ain, aout; - double qin, qout; - double a1, a2, q1, q2, q3; - - // --- no routing for non-conduit link - (*qoutflow) = (*qinflow); - if ( Link[j].type != CONDUIT ) return result; - - // --- no routing for dummy xsection - if ( Link[j].xsect.type == DUMMY ) return result; - - // --- assign module-level variables - pXsect = &Link[j].xsect; - Qfull = Link[j].qFull; - Afull = Link[j].xsect.aFull; - k = Link[j].subIndex; - Beta1 = Conduit[k].beta / Qfull; - - // --- normalize previous flows - q1 = Conduit[k].q1 / Qfull; - q2 = Conduit[k].q2 / Qfull; - - // --- normalize inflow - qin = (*qinflow) / Conduit[k].barrels / Qfull; - - // --- compute evaporation and infiltration loss rate - q3 = link_getLossRate(j, qin*Qfull) / Qfull; - - // --- normalize previous areas - a1 = Conduit[k].a1 / Afull; - a2 = Conduit[k].a2 / Afull; - - // --- use full area when inlet flow >= full flow - if ( qin >= 1.0 ) ain = 1.0; - - // --- get normalized inlet area corresponding to inlet flow - else ain = xsect_getAofS(pXsect, qin/Beta1) / Afull; - - // --- check for no flow - if ( qin <= TINY && q2 <= TINY ) - { - qout = 0.0; - aout = 0.0; - } - - // --- otherwise solve finite difference form of continuity eqn. - else - { - // --- compute constant factors - dxdt = link_getLength(j) / tStep * Afull / Qfull; - dq = q2 - q1; - C1 = dxdt * WT / WX; - C2 = (1.0 - WT) * (ain - a1); - C2 = C2 - WT * a2; - C2 = C2 * dxdt / WX; - C2 = C2 + (1.0 - WX) / WX * dq - qin; - C2 = C2 + q3 / WX; - - // --- starting guess for aout is value from previous time step - aout = a2; - - // --- solve continuity equation for aout - result = solveContinuity(qin, ain, &aout); - - // --- report error if continuity eqn. not solved - if ( result == -1 ) - { - report_writeErrorMsg(ERR_KINWAVE, Link[j].ID); - return 1; - } - if ( result <= 0 ) result = 1; - - // --- compute normalized outlet flow from outlet area - qout = Beta1 * xsect_getSofA(pXsect, aout*Afull); - if ( qin > 1.0 ) qin = 1.0; - } - - // --- save new flows and areas - Conduit[k].q1 = qin * Qfull; - Conduit[k].a1 = ain * Afull; - Conduit[k].q2 = qout * Qfull; - Conduit[k].a2 = aout * Afull; - Conduit[k].fullState = - link_getFullState(Conduit[k].a1, Conduit[k].a2, Afull); - (*qinflow) = Conduit[k].q1 * Conduit[k].barrels; - (*qoutflow) = Conduit[k].q2 * Conduit[k].barrels; - return result; -} - -//============================================================================= - -int solveContinuity(double qin, double ain, double* aout) -// -// Input: qin = upstream normalized flow -// ain = upstream normalized area -// aout = downstream normalized area -// Output: new value for aout; returns an error code -// Purpose: solves continuity equation f(a) = Beta1*S(a) + C1*a + C2 = 0 -// for 'a' using the Newton-Raphson root finder function. -// Return code has the following meanings: -// >= 0 number of function evaluations used -// -1 Newton function failed -// -2 flow always above max. flow -// -3 flow always below zero -// -// Note: pXsect (pointer to conduit's cross-section), and constants Beta1, -// C1, and C2 are module-level shared variables assigned values -// in kinwave_execute(). -// -{ - int n; // # evaluations or error code - double aLo, aHi, aTmp; // lower/upper bounds on a - double fLo, fHi; // lower/upper bounds on f - double tol = EPSIL; // absolute convergence tol. - - // --- first determine bounds on 'a' so that f(a) passes through 0. - - // --- set upper bound to area at full flow - aHi = 1.0; - fHi = 1.0 + C1 + C2; - - // --- try setting lower bound to area where section factor is maximum - aLo = xsect_getAmax(pXsect) / Afull; - if ( aLo < aHi ) - { - fLo = ( Beta1 * pXsect->sMax ) + (C1 * aLo) + C2; - } - else fLo = fHi; - - // --- if fLo and fHi have same sign then set lower bound to 0 - if ( fHi*fLo > 0.0 ) - { - aHi = aLo; - fHi = fLo; - aLo = 0.0; - fLo = C2; - } - - // --- proceed with search for root if fLo and fHi have different signs - if ( fHi*fLo <= 0.0 ) - { - // --- start search at midpoint of lower/upper bounds - // if initial value outside of these bounds - if ( *aout < aLo || *aout > aHi ) *aout = 0.5*(aLo + aHi); - - // --- if fLo > fHi then switch aLo and aHi - if ( fLo > fHi ) - { - aTmp = aLo; - aLo = aHi; - aHi = aTmp; - } - - // --- call the Newton root finder method passing it the - // evalContinuity function to evaluate the function - // and its derivatives - n = findroot_Newton(aLo, aHi, aout, tol, evalContinuity, NULL); - - // --- check if root finder succeeded - if ( n <= 0 ) n = -1; - } - - // --- if lower/upper bound functions both negative then use full flow - else if ( fLo < 0.0 ) - { - if ( qin > 1.0 ) *aout = ain; - else *aout = 1.0; - n = -2; - } - - // --- if lower/upper bound functions both positive then use no flow - else if ( fLo > 0 ) - { - *aout = 0.0; - n = -3; - } - else n = -1; - return n; -} - -//============================================================================= - -void evalContinuity(double a, double* f, double* df, void* p) -// -// Input: a = outlet normalized area -// Output: f = value of continuity eqn. -// df = derivative of continuity eqn. -// Purpose: computes value of continuity equation (f) and its derivative (df) -// w.r.t. normalized area for link with normalized outlet area 'a'. -// -{ - *f = (Beta1 * xsect_getSofA(pXsect, a*Afull)) + (C1 * a) + C2; - *df = (Beta1 * Afull * xsect_getdSdA(pXsect, a*Afull)) + C1; -} - -//============================================================================= diff --git a/src/landuse.c b/src/landuse.c deleted file mode 100644 index 4c8402790..000000000 --- a/src/landuse.c +++ /dev/null @@ -1,723 +0,0 @@ -//----------------------------------------------------------------------------- -// landuse.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Pollutant buildup and washoff functions. -// -// Update History -// ============== -// Build 5.1.008: -// - landuse_getWashoffMass() re-named to landuse_getWashoffQual() and -// modified to return concentration instead of mass load. -// - landuse_getRunoffLoad() re-named to landuse_getWashoffLoad() and -// modified to work with landuse_getWashoffQual(). -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include "headers.h" - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// landuse_readParams (called by parseLine in input.c) -// landuse_readPollutParams (called by parseLine in input.c) -// landuse_readBuildupParams (called by parseLine in input.c) -// landuse_readWashoffParams (called by parseLine in input.c) - -// landuse_getInitBuildup (called by subcatch_initState) -// landuse_getBuildup (called by surfqual_getBuildup) -// landuse_getWashoffLoad (called by surfqual_getWashoff) -// landuse_getCoPollutLoad (called by surfqual_getwashoff)); -// landuse_getAvgBMPEffic (called by updatePondedQual in surfqual.c) - -//----------------------------------------------------------------------------- -// Function declarations -//----------------------------------------------------------------------------- -static double landuse_getBuildupDays(int landuse, int pollut, double buildup); -static double landuse_getBuildupMass(int landuse, int pollut, double days); -static double landuse_getWashoffQual(int landuse, int pollut, double buildup, - double runoff, double area); -static double landuse_getExternalBuildup(int i, int p, double buildup, - double tStep); - -//============================================================================= - -int landuse_readParams(int j, char* tok[], int ntoks) -// -// Input: j = land use index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads landuse parameters from a tokenized line of input. -// -// Data format is: -// landuseID (sweepInterval sweepRemoval sweepDays0) -// -{ - char *id; - if ( ntoks < 1 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LANDUSE, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - Landuse[j].ID = id; - if ( ntoks > 1 ) - { - if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); - if ( ! getDouble(tok[1], &Landuse[j].sweepInterval) ) - return error_setInpError(ERR_NUMBER, tok[1]); - if ( ! getDouble(tok[2], &Landuse[j].sweepRemoval) ) - return error_setInpError(ERR_NUMBER, tok[2]); - if ( ! getDouble(tok[3], &Landuse[j].sweepDays0) ) - return error_setInpError(ERR_NUMBER, tok[3]); - } - else - { - Landuse[j].sweepInterval = 0.0; - Landuse[j].sweepRemoval = 0.0; - Landuse[j].sweepDays0 = 0.0; - } - if ( Landuse[j].sweepRemoval < 0.0 - || Landuse[j].sweepRemoval > 1.0 ) - return error_setInpError(ERR_NUMBER, tok[2]); - return 0; -} - -//============================================================================= - -int landuse_readPollutParams(int j, char* tok[], int ntoks) -// -// Input: j = pollutant index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads pollutant parameters from a tokenized line of input. -// -// Data format is: -// ID Units cRain cGW cRDII kDecay (snowOnly coPollut coFrac cDWF cInit) -// -{ - int i, k, coPollut, snowFlag; - double x[4], coFrac, cDWF, cInit; - char *id; - - // --- extract pollutant name & units - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(POLLUT, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - k = findmatch(tok[1], QualUnitsWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- extract concen. in rain, gwater, & I&I - for ( i = 2; i <= 4; i++ ) - { - if ( ! getDouble(tok[i], &x[i-2]) || x[i-2] < 0.0 ) - { - return error_setInpError(ERR_NUMBER, tok[i]); - } - } - - // --- extract decay coeff. (which can be negative for growth) - if ( ! getDouble(tok[5], &x[3]) ) - { - return error_setInpError(ERR_NUMBER, tok[5]); - } - - // --- set defaults for snow only flag & co-pollut. parameters - snowFlag = 0; - coPollut = -1; - coFrac = 0.0; - cDWF = 0.0; - cInit = 0.0; - - // --- check for snow only flag - if ( ntoks >= 7 ) - { - snowFlag = findmatch(tok[6], NoYesWords); - if ( snowFlag < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); - } - - // --- check for co-pollutant - if ( ntoks >= 9 ) - { - if ( !strcomp(tok[7], "*") ) - { - coPollut = project_findObject(POLLUT, tok[7]); - if ( coPollut < 0 ) return error_setInpError(ERR_NAME, tok[7]); - if ( ! getDouble(tok[8], &coFrac) || coFrac < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[8]); - } - } - - // --- check for DWF concen. - if ( ntoks >= 10 ) - { - if ( ! getDouble(tok[9], &cDWF) || cDWF < 0.0) - return error_setInpError(ERR_NUMBER, tok[9]); - } - - // --- check for initial concen. - if ( ntoks >= 11 ) - { - if ( ! getDouble(tok[10], &cInit) || cInit < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[9]); - } - - // --- save values for pollutant object - Pollut[j].ID = id; - Pollut[j].units = k; - if ( Pollut[j].units == MG ) Pollut[j].mcf = UCF(MASS); - else if ( Pollut[j].units == UG ) Pollut[j].mcf = UCF(MASS) / 1000.0; - else Pollut[j].mcf = 1.0; - Pollut[j].pptConcen = x[0]; - Pollut[j].gwConcen = x[1]; - Pollut[j].rdiiConcen = x[2]; - Pollut[j].kDecay = x[3]/SECperDAY; - Pollut[j].snowOnly = snowFlag; - Pollut[j].coPollut = coPollut; - Pollut[j].coFraction = coFrac; - Pollut[j].dwfConcen = cDWF; - Pollut[j].initConcen = cInit; - return 0; -} - -//============================================================================= - -int landuse_readBuildupParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads pollutant buildup parameters from a tokenized line of input. -// -// Data format is: -// landuseID pollutID buildupType c1 c2 c3 normalizerType -// -{ - int i, j, k, n, p; - double c[3] = {0, 0, 0}, tmax; - - if ( ntoks < 3 ) return 0; - j = project_findObject(LANDUSE, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - p = project_findObject(POLLUT, tok[1]); - if ( p < 0 ) return error_setInpError(ERR_NAME, tok[1]); - k = findmatch(tok[2], BuildupTypeWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[2]); - Landuse[j].buildupFunc[p].funcType = k; - if ( k > NO_BUILDUP ) - { - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - if ( k != EXTERNAL_BUILDUP ) for (i=0; i<3; i++) - { - if ( ! getDouble(tok[i+3], &c[i]) || c[i] < 0.0 ) - { - return error_setInpError(ERR_NUMBER, tok[i+3]); - } - } - n = findmatch(tok[6], NormalizerWords); - if (n < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); - Landuse[j].buildupFunc[p].normalizer = n; - } - - // Find time until max. buildup (or time series for external buildup) - switch (Landuse[j].buildupFunc[p].funcType) - { - case POWER_BUILDUP: - // --- check for too small or large an exponent - if ( c[2] > 0.0 && (c[2] < 0.01 || c[2] > 10.0) ) - return error_setInpError(ERR_KEYWORD, tok[5]); - - // --- find time to reach max. buildup - // --- use zero if coeffs. are 0 - if ( c[1]*c[2] == 0.0 ) tmax = 0.0; - - // --- use 10 years if inverse power function tends to blow up - else if ( log10(c[0]) / c[2] > 3.5 ) tmax = 3650.0; - - // --- otherwise use inverse power function - else tmax = pow(c[0]/c[1], 1.0/c[2]); - break; - - case EXPON_BUILDUP: - if ( c[1] == 0.0 ) tmax = 0.0; - else tmax = -log(0.001)/c[1]; - break; - - case SATUR_BUILDUP: - tmax = 1000.0*c[2]; - break; - - case EXTERNAL_BUILDUP: - if ( !getDouble(tok[3], &c[0]) || c[0] < 0.0 ) //max. buildup - return error_setInpError(ERR_NUMBER, tok[3]); - if ( !getDouble(tok[4], &c[1]) || c[1] < 0.0 ) //scaling factor - return error_setInpError(ERR_NUMBER, tok[3]); - n = project_findObject(TSERIES, tok[5]); //time series - if ( n < 0 ) return error_setInpError(ERR_NAME, tok[4]); - Tseries[n].refersTo = EXTERNAL_BUILDUP; - c[2] = n; - tmax = 0.0; - break; - - default: - tmax = 0.0; - } - - // Assign parameters to buildup object - Landuse[j].buildupFunc[p].coeff[0] = c[0]; - Landuse[j].buildupFunc[p].coeff[1] = c[1]; - Landuse[j].buildupFunc[p].coeff[2] = c[2]; - Landuse[j].buildupFunc[p].maxDays = tmax; - return 0; -} - -//============================================================================= - -int landuse_readWashoffParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads pollutant washoff parameters from a tokenized line of input. -// -// Data format is: -// landuseID pollutID washoffType c1 c2 sweepEffic bmpRemoval -{ - int i, j, p; - int func; - double x[4]; - - if ( ntoks < 3 ) return 0; - for (i=0; i<4; i++) x[i] = 0.0; - func = NO_WASHOFF; - j = project_findObject(LANDUSE, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - p = project_findObject(POLLUT, tok[1]); - if ( p < 0 ) return error_setInpError(ERR_NAME, tok[1]); - if ( ntoks > 2 ) - { - func = findmatch(tok[2], WashoffTypeWords); - if ( func < 0 ) return error_setInpError(ERR_KEYWORD, tok[2]); - if ( func != NO_WASHOFF ) - { - if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); - if ( ! getDouble(tok[3], &x[0]) ) - return error_setInpError(ERR_NUMBER, tok[3]); - if ( ! getDouble(tok[4], &x[1]) ) - return error_setInpError(ERR_NUMBER, tok[4]); - if ( ntoks >= 6 ) - { - if ( ! getDouble(tok[5], &x[2]) ) - return error_setInpError(ERR_NUMBER, tok[5]); - } - if ( ntoks >= 7 ) - { - if ( ! getDouble(tok[6], &x[3]) ) - return error_setInpError(ERR_NUMBER, tok[6]); - } - } - } - - // --- check for valid parameter values - // x[0] = washoff coeff. - // x[1] = washoff expon. - // x[2] = sweep effic. - // x[3] = BMP effic. - if ( x[0] < 0.0 ) return error_setInpError(ERR_NUMBER, tok[3]); - if ( x[1] < -10.0 || x[1] > 10.0 ) - return error_setInpError(ERR_NUMBER, tok[4]);; - if ( x[2] < 0.0 || x[2] > 100.0 ) - return error_setInpError(ERR_NUMBER, tok[5]); - if ( x[3] < 0.0 || x[3] > 100.0 ) - return error_setInpError(ERR_NUMBER, tok[6]); - - // --- convert units of washoff coeff. - if ( func == EXPON_WASHOFF ) x[0] /= 3600.0; - if ( func == RATING_WASHOFF ) x[0] *= pow(UCF(FLOW), x[1]); - if ( func == EMC_WASHOFF ) x[0] *= LperFT3; - - // --- assign washoff parameters to washoff object - Landuse[j].washoffFunc[p].funcType = func; - Landuse[j].washoffFunc[p].coeff = x[0]; - Landuse[j].washoffFunc[p].expon = x[1]; - Landuse[j].washoffFunc[p].sweepEffic = x[2] / 100.0; - Landuse[j].washoffFunc[p].bmpEffic = x[3] / 100.0; - return 0; -} - -//============================================================================= - -void landuse_getInitBuildup(TLandFactor* landFactor, double* initBuildup, - double area, double curb) -// -// Input: landFactor = array of land use factors -// initBuildup = total initial buildup of each pollutant -// area = subcatchment's area (ft2) -// curb = subcatchment's curb length (users units) -// Output: modifies each land use factor's initial pollutant buildup -// Purpose: determines the initial buildup of each pollutant on -// each land use for a given subcatchment. -// -// Notes: Contributions from co-pollutants to initial buildup are not -// included since the co-pollutant mechanism only applies to -// washoff. -// -{ - int i, p; - double startDrySeconds; // antecedent dry period (sec) - double f; // faction of total land area - double fArea; // area of land use (ft2) - double fCurb; // curb length of land use - double buildup; // pollutant mass buildup - - // --- convert antecedent dry days into seconds - startDrySeconds = StartDryDays*SECperDAY; - - // --- examine each land use - for (i = 0; i < Nobjects[LANDUSE]; i++) - { - // --- initialize date when last swept - landFactor[i].lastSwept = StartDateTime - Landuse[i].sweepDays0; - - // --- determine area and curb length covered by land use - f = landFactor[i].fraction; - fArea = f * area * UCF(LANDAREA); - fCurb = f * curb; - - // --- determine buildup of each pollutant - for (p = 0; p < Nobjects[POLLUT]; p++) - { - // --- if an initial loading was supplied, then use it to - // find the starting buildup over the land use - buildup = 0.0; - if ( initBuildup[p] > 0.0 ) buildup = initBuildup[p] * fArea; - - // --- otherwise use the land use's buildup function to - // compute a buildup over the antecedent dry period - else buildup = landuse_getBuildup(i, p, fArea, fCurb, buildup, - startDrySeconds); - landFactor[i].buildup[p] = buildup; - } - } -} - -//============================================================================= - -double landuse_getBuildup(int i, int p, double area, double curb, double buildup, - double tStep) -// -// Input: i = land use index -// p = pollutant index -// area = land use area (ac or ha) -// curb = land use curb length (users units) -// buildup = current pollutant buildup (lbs or kg) -// tStep = time increment for buildup (sec) -// Output: returns new buildup mass (lbs or kg) -// Purpose: computes new pollutant buildup on a landuse after a time increment. -// -{ - int n; // normalizer code - double days; // accumulated days of buildup - double perUnit; // normalizer value (area or curb length) - - // --- return current buildup if no buildup function or time increment - if ( Landuse[i].buildupFunc[p].funcType == NO_BUILDUP || tStep == 0.0 ) - { - return buildup; - } - - // --- see what buildup is normalized to - n = Landuse[i].buildupFunc[p].normalizer; - perUnit = 1.0; - if ( n == PER_AREA ) perUnit = area; - if ( n == PER_CURB ) perUnit = curb; - if ( perUnit == 0.0 ) return 0.0; - - // --- buildup determined by loading time series - if ( Landuse[i].buildupFunc[p].funcType == EXTERNAL_BUILDUP ) - { - return landuse_getExternalBuildup(i, p, buildup/perUnit, tStep) * - perUnit; - } - - // --- determine equivalent days of current buildup - days = landuse_getBuildupDays(i, p, buildup/perUnit); - - // --- compute buildup after adding on time increment - days += tStep / SECperDAY; - return landuse_getBuildupMass(i, p, days) * perUnit; -} - -//============================================================================= - -double landuse_getBuildupDays(int i, int p, double buildup) -// -// Input: i = land use index -// p = pollutant index -// buildup = amount of pollutant buildup -// Output: returns number of days it takes for buildup to reach a given level -// Purpose: finds the number of days corresponding to a pollutant buildup. -// -{ - double c0 = Landuse[i].buildupFunc[p].coeff[0]; - double c1 = Landuse[i].buildupFunc[p].coeff[1]; - double c2 = Landuse[i].buildupFunc[p].coeff[2]; - - if ( buildup == 0.0 ) return 0.0; - if ( buildup >= c0 ) return Landuse[i].buildupFunc[p].maxDays; - switch (Landuse[i].buildupFunc[p].funcType) - { - case POWER_BUILDUP: - if ( c1*c2 == 0.0 ) return 0.0; - else return pow( (buildup/c1), (1.0/c2) ); - - case EXPON_BUILDUP: - if ( c0*c1 == 0.0 ) return 0.0; - else return -log(1. - buildup/c0) / c1; - - case SATUR_BUILDUP: - if ( c0 == 0.0 ) return 0.0; - else return buildup*c2 / (c0 - buildup); - - default: - return 0.0; - } -} - -//============================================================================= - -double landuse_getBuildupMass(int i, int p, double days) -// -// Input: i = land use index -// p = pollutant index -// days = time over which buildup has occurred (days) -// Output: returns mass of pollutant buildup (lbs or kg per area or curblength) -// Purpose: finds amount of buildup of pollutant on a land use. -// -{ - double b; - double c0 = Landuse[i].buildupFunc[p].coeff[0]; - double c1 = Landuse[i].buildupFunc[p].coeff[1]; - double c2 = Landuse[i].buildupFunc[p].coeff[2]; - - if ( days == 0.0 ) return 0.0; - if ( days >= Landuse[i].buildupFunc[p].maxDays ) return c0; - switch (Landuse[i].buildupFunc[p].funcType) - { - case POWER_BUILDUP: - b = c1 * pow(days, c2); - if ( b > c0 ) b = c0; - break; - - case EXPON_BUILDUP: - b = c0*(1.0 - exp(-days*c1)); - break; - - case SATUR_BUILDUP: - b = days*c0/(c2 + days); - break; - - default: b = 0.0; - } - return b; -} - -//============================================================================= - -double landuse_getAvgBmpEffic(int j, int p) -// -// Input: j = subcatchment index -// p = pollutant index -// Output: returns a BMP removal fraction for pollutant p -// Purpose: finds the overall average BMP removal achieved for pollutant p -// treated in subcatchment j. -// -{ - int i; - double r = 0.0; - for (i = 0; i < Nobjects[LANDUSE]; i++) - { - r += Subcatch[j].landFactor[i].fraction * - Landuse[i].washoffFunc[p].bmpEffic; - } - return r; -} - -//============================================================================= - -double landuse_getWashoffLoad(int i, int p, double area, - TLandFactor landFactor[], double runoff, double vOutflow) -// -// Input: i = land use index -// p = pollut. index -// area = sucatchment area (ft2) -// landFactor[] = array of land use data for subcatchment -// runoff = runoff flow generated by subcatchment (ft/sec) -// vOutflow = runoff volume leaving the subcatchment (ft3) -// Output: returns pollutant runoff load (mass) -// Purpose: computes pollutant load generated by a land use over a time step. -// -{ - double landuseArea; // area of current land use (ft2) - double buildup; // current pollutant buildup (lb or kg) - double washoffQual; // pollutant concentration in washoff (mass/ft3) - double washoffLoad; // pollutant washoff load over time step (lb or kg) - double bmpRemoval; // pollutant load removed by BMP treatment (lb or kg) - - // --- compute concen. of pollutant in washoff (mass/ft3) - buildup = landFactor[i].buildup[p]; - landuseArea = landFactor[i].fraction * area; - washoffQual = landuse_getWashoffQual(i, p, buildup, runoff, landuseArea); - - // --- compute washoff load exported (lbs or kg) from landuse - // (Pollut[].mcf converts from mg (or ug) mass units to lbs (or kg) - washoffLoad = washoffQual * vOutflow * landuseArea / area * Pollut[p].mcf; - - // --- if buildup modelled, reduce it by amount of washoff - if ( Landuse[i].buildupFunc[p].funcType != NO_BUILDUP || - buildup > washoffLoad ) - { - washoffLoad = MIN(washoffLoad, buildup); - buildup -= washoffLoad; - landFactor[i].buildup[p] = buildup; - } - - // --- otherwise add washoff to buildup mass balance totals - // so that things will balance - else - { - massbal_updateLoadingTotals(BUILDUP_LOAD, p, washoffLoad); - landFactor[i].buildup[p] = 0.0; - } - - // --- apply any BMP removal to washoff - bmpRemoval = Landuse[i].washoffFunc[p].bmpEffic * washoffLoad; - if ( bmpRemoval > 0.0 ) - { - massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, bmpRemoval); - washoffLoad -= bmpRemoval; - } - - // --- return washoff load converted back to mass (mg or ug) - return washoffLoad / Pollut[p].mcf; -} - -//============================================================================= - -double landuse_getWashoffQual(int i, int p, double buildup, double runoff, - double area) -// -// Input: i = land use index -// p = pollutant index -// buildup = current buildup over land use (lbs or kg) -// runoff = current runoff on subcatchment (ft/sec) -// area = area devoted to land use (ft2) -// Output: returns pollutant concentration in washoff (mass/ft3) -// Purpose: finds concentration of pollutant washed off a land use. -// -// Notes: "coeff" for each washoff function was previously adjusted to -// result in units of mass/sec -// -{ - double cWashoff = 0.0; - double coeff = Landuse[i].washoffFunc[p].coeff; - double expon = Landuse[i].washoffFunc[p].expon; - int func = Landuse[i].washoffFunc[p].funcType; - - // --- if no washoff function or no runoff, return 0 - if ( func == NO_WASHOFF || runoff == 0.0 ) return 0.0; - - // --- if buildup function exists but no current buildup, return 0 - if ( Landuse[i].buildupFunc[p].funcType != NO_BUILDUP && buildup == 0.0 ) - return 0.0; - - // --- Exponential Washoff function - if ( func == EXPON_WASHOFF ) - { - // --- evaluate washoff eqn. with runoff in in/hr (or mm/hr) - // and buildup converted from lbs (or kg) to concen. mass units - cWashoff = coeff * pow(runoff * UCF(RAINFALL), expon) * - buildup / Pollut[p].mcf; - cWashoff /= runoff * area; - } - - // --- Rating Curve Washoff function - else if ( func == RATING_WASHOFF ) - { - cWashoff = coeff * pow(runoff * area, expon-1.0); - } - - // --- Event Mean Concentration Washoff - else if ( func == EMC_WASHOFF ) - { - cWashoff = coeff; // coeff includes LperFT3 factor - } - return cWashoff; -} - -//============================================================================= - -double landuse_getCoPollutLoad(int p, double washoff[]) -// -// Input: p = pollutant index -// washoff = pollut. washoff rate (mass/sec) -// Output: returns washoff mass added by co-pollutant relation (mass) -// Purpose: finds washoff mass added by a co-pollutant of a given pollutant. -// -{ - int k; - double w; - - // --- check if pollutant p has a co-pollutant k - k = Pollut[p].coPollut; - if ( k >= 0 ) - { - // --- compute addition to washoff from co-pollutant - w = Pollut[p].coFraction * washoff[k]; - - // --- add washoff to buildup mass balance totals - // so that things will balance - massbal_updateLoadingTotals(BUILDUP_LOAD, p, w * Pollut[p].mcf); - return w; - } - return 0.0; -} - -//============================================================================= - -double landuse_getExternalBuildup(int i, int p, double buildup, double tStep) -// -// Input: i = landuse index -// p = pollutant index -// buildup = buildup at start of time step (mass/unit) -// tStep = time step (sec) -// Output: returns pollutant buildup at end of time interval (mass/unit) -// Purpose: finds pollutant buildup contributed by external loading over a -// given time step. -// -{ - double maxBuildup = Landuse[i].buildupFunc[p].coeff[0]; - double sf = Landuse[i].buildupFunc[p].coeff[1]; // scaling factor - int ts = (int)floor(Landuse[i].buildupFunc[p].coeff[2]); // time series index - double rate = 0.0; - - // --- no buildup increment at start of simulation - if (NewRunoffTime == 0.0) return 0.0; - - // --- get buildup rate (mass/unit/day) over the interval - if ( ts >= 0 ) - { - rate = sf * table_tseriesLookup(&Tseries[ts], - getDateTime(NewRunoffTime), FALSE); - } - - // --- compute buildup at end of time interval - buildup = buildup + rate * tStep / SECperDAY; - buildup = MIN(buildup, maxBuildup); - return buildup; -} diff --git a/src/lid.c b/src/lid.c deleted file mode 100644 index 7bb69dcef..000000000 --- a/src/lid.c +++ /dev/null @@ -1,2031 +0,0 @@ -//----------------------------------------------------------------------------- -// lid.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// This module handles all data processing involving LID (Low Impact -// Development) practices used to treat runoff for individual subcatchments -// within a project. The actual computation of LID performance is made by -// functions within the lidproc.c module. See LidTypes below for the types -// of LIDs that can be modeled. -// -// An LID process is described by the TLidProc data structure and consists of -// size-independent design data for the different vertical layers that make -// up a specific type of LID. The collection of these LID process designs is -// stored in the LidProcs array. -// -// When a member of LidProcs is to be deployed in a particular subcatchment, -// its sizing and treatment data are stored in a TLidUnit data structure. -// The collection of all TLidUnits deployed in a subcatchment is held in a -// TLidGroup list data structure. The LidGroups array contains a TLidGroup -// list for each subcatchment in the project. -// -// During a runoff time step, each subcatchment calls the lid_getRunoff() -// function to compute flux rates and a water balance through each layer -// of each LID unit in the subcatchment. The resulting outflows (runoff, -// drain flow, evaporation and infiltration) are added to those computed -// for the non-LID portion of the subcatchment. -// -// An option exists for the detailed time series of flux rates and storage -// levels for a specific LID unit to be written to a text file named by the -// user for viewing outside of the SWMM program. -// -// Update History -// ============== -// Build 5.1.008: -// - More input error reporting added. -// - Rooftop Disconnection added to the types of LIDs. -// - LID drain flows are now tracked separately. -// - LID drain flows can now be routed to separate outlets. -// - Check added to insure LID flows not returned to nonexistent pervious area. -// Build 5.1.009: -// - Fixed bug where LID's could return outflow to non-LID area when LIDs -// make up entire subcatchment. -// Build 5.1.010: -// - Support for new Modified Green Ampt infiltration model added. -// - Imported variable HasWetLids now properly initialized. -// - Initial state of reporting (lidUnit->rptFile->wasDry) changed to -// prevent duplicate printing of first line of detailed report file. -// Build 5.1.011: -// - The top of the storage layer is no longer used as a limit for an -// underdrain offset thus allowing upturned drains to be modeled. -// - Column headings for the detailed LID report file were modified. -// Build 5.1.012: -// - Redefined initialization of wasDry for LID reporting. -// Build 5.1.013: -// - Support added for LID units treating pervious area runoff. -// - Support added for open/closed head levels and multiplier v. head -// control curve for underdrain flow. -// - Support added for unclogging permeable pavement at fixed intervals. -// - Support added for pollutant removal in underdrain flow. -// Build 5.1.014: -// - Fixed bug in creating LidProcs when there are no subcatchments. -// - Fixed bug in adding underdrain pollutant loads to mass balances. -// Build 5.1.015: -// - Support added for mutiple infiltration methods within a project. -// Build 5.2.0: -// - Covered property added to RAIN_BARREL parameters -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include "headers.h" -#include "lid.h" - -#define ERR_PAVE_LAYER " - check pavement layer parameters" -#define ERR_SOIL_LAYER " - check soil layer parameters" -#define ERR_STOR_LAYER " - check storage layer parameters" -#define ERR_SWALE_SURF " - check swale surface parameters" -#define ERR_GREEN_AMPT " - check subcatchment Green-Ampt parameters" -#define ERR_DRAIN_OFFSET " - drain offset exceeds storage height" -#define ERR_DRAIN_HEADS " - invalid drain open/closed heads" -#define ERR_SWALE_WIDTH " - invalid swale width" - -//----------------------------------------------------------------------------- -// Enumerations -//----------------------------------------------------------------------------- -enum LidLayerTypes { - SURF, // surface layer - SOIL, // soil layer - STOR, // storage layer - PAVE, // pavement layer - DRAINMAT, // drainage mat layer - DRAIN, // underdrain system - REMOVALS}; // pollutant removals - -//// Note: DRAINMAT must be placed before DRAIN so the two keywords can -/// be distinguished from one another when parsing a line of input. - -char* LidLayerWords[] = - {"SURFACE", "SOIL", "STORAGE", "PAVEMENT", "DRAINMAT", "DRAIN", - "REMOVALS", NULL}; - -char* LidTypeWords[] = - {"BC", //bio-retention cell - "RG", //rain garden - "GR", //green roof - "IT", //infiltration trench - "PP", //porous pavement - "RB", //rain barrel - "VS", //vegetative swale - "RD", //rooftop disconnection - NULL}; - -//----------------------------------------------------------------------------- -// Data Structures -//----------------------------------------------------------------------------- - -// LID List - list of LID units contained in an LID group -struct LidList -{ - TLidUnit* lidUnit; // ptr. to a LID unit - struct LidList* nextLidUnit; -}; -typedef struct LidList TLidList; - -// LID Group - collection of LID units applied to a specific subcatchment -struct LidGroup -{ - double pervArea; // amount of pervious area in group (ft2) - double flowToPerv; // total flow sent to pervious area (cfs) - double oldDrainFlow; // total drain flow in previous period (cfs) - double newDrainFlow; // total drain flow in current period (cfs) - TLidList* lidList; // list of LID units in the group -}; -typedef struct LidGroup* TLidGroup; - - -//----------------------------------------------------------------------------- -// Shared Variables -//----------------------------------------------------------------------------- -static TLidProc* LidProcs; // array of LID processes -static int LidCount; // number of LID processes -static TLidGroup* LidGroups; // array of LID process groups -static int GroupCount; // number of LID groups (subcatchments) - -static double EvapRate; // evaporation rate (ft/s) -static double NativeInfil; // native soil infil. rate (ft/s) -static double MaxNativeInfil; // native soil infil. rate limit (ft/s) - -//----------------------------------------------------------------------------- -// Imported Variables (from SUBCATCH.C) -//----------------------------------------------------------------------------- -// Volumes (ft3) for a subcatchment over a time step -extern double Vevap; // evaporation -extern double Vpevap; // pervious area evaporation -extern double Vinfil; // non-LID infiltration -extern double VlidInfil; // infiltration from LID units -extern double VlidIn; // impervious area flow to LID units -extern double VlidOut; // surface outflow from LID units -extern double VlidDrain; // drain outflow from LID units -extern double VlidReturn; // LID outflow returned to pervious area -extern char HasWetLids; // TRUE if any LIDs are wet - // (from RUNOFF.C) - -//----------------------------------------------------------------------------- -// External Functions (prototyped in lid.h) -//----------------------------------------------------------------------------- -// lid_create called by createObjects in project.c -// lid_delete called by deleteObjects in project.c -// lid_validate called by project_validate -// lid_initState called by project_init - -// lid_readProcParams called by parseLine in input.c -// lid_readGroupParams called by parseLine in input.c - -// lid_setOldGroupState called by subcatch_setOldState -// lid_setReturnQual called by findLidLoads in surfqual.c -// lid_getReturnQual called by subcatch_getRunon - -// lid_getPervArea called by subcatch_getFracPerv -// lid_getFlowToPerv called by subcatch_getRunon -// lid_getSurfaceDepth called by subcatch_getDepth -// lid_getDepthOnPavement called by sweptSurfacesDry in subcatch.c -// lid_getStoredVolume called by subcatch_getStorage -// lid_getRunon called by subcatch_getRunon -// lid_getRunoff called by subcatch_getRunoff - -// lid_addDrainRunon called by subcatch_getRunon -// lid_addDrainLoads called by surfqual_getWashoff -// lid_addDrainInflow called by addLidDrainInflows in routing.c - -// lid_writeSummary called by inputrpt_writeInput -// lid_writeWaterBalance called by statsrpt_writeReport - - -//----------------------------------------------------------------------------- -// Local Functions -//----------------------------------------------------------------------------- -static void freeLidGroup(int j); -static int readSurfaceData(int j, char* tok[], int ntoks); -static int readPavementData(int j, char* tok[], int ntoks); -static int readSoilData(int j, char* tok[], int ntoks); -static int readStorageData(int j, char* tok[], int ntoks); -static int readDrainData(int j, char* tok[], int ntoks); -static int readDrainMatData(int j, char* toks[], int ntoks); -static int readRemovalsData(int j, char* toks[], int ntoks); - -static int addLidUnit(int j, int k, int n, double x[], char* fname, - int drainSubcatch, int drainNode); -static int createLidRptFile(TLidUnit* lidUnit, char* fname); -static void initLidRptFile(char* title, char* lidID, char* subcatchID, - TLidUnit* lidUnit); -static void validateLidProc(int j); -static void validateLidGroup(int j); - -static int isLidPervious(int k); -static double getImpervAreaRunoff(int j); -static double getPervAreaRunoff(int j); -static double getSurfaceDepth(int subcatch); -static double getRainInflow(int j, TLidUnit* lidUnit); -static void findNativeInfil(int j, double tStep); - - -static void evalLidUnit(int j, TLidUnit* lidUnit, double lidArea, - double lidInflow, double tStep, double *qRunoff, - double *qDrain, double *qReturn); - -//============================================================================= - -void lid_create(int lidCount, int subcatchCount) -// -// Purpose: creates an array of LID objects. -// Input: n = number of LID processes -// Output: none -// -{ - int j; - - //... assign NULL values to LID arrays - LidProcs = NULL; - LidGroups = NULL; - LidCount = lidCount; - - //... create LID groups - GroupCount = subcatchCount; - if ( GroupCount > 0 ) - { - LidGroups = (TLidGroup *) calloc(GroupCount, sizeof(TLidGroup)); - if ( LidGroups == NULL ) - { - ErrorCode = ERR_MEMORY; - return; - } - } - - //... initialize LID groups - for (j = 0; j < GroupCount; j++) LidGroups[j] = NULL; - - //... create LID objects - if ( LidCount == 0 ) return; - LidProcs = (TLidProc *) calloc(LidCount, sizeof(TLidProc)); - if ( LidProcs == NULL ) - { - ErrorCode = ERR_MEMORY; - return; - } - - //... initialize LID objects - for (j = 0; j < LidCount; j++) - { - LidProcs[j].lidType = -1; - LidProcs[j].surface.thickness = 0.0; - LidProcs[j].surface.voidFrac = 1.0; - LidProcs[j].surface.roughness = 0.0; - LidProcs[j].surface.surfSlope = 0.0; - LidProcs[j].pavement.thickness = 0.0; - LidProcs[j].soil.thickness = 0.0; - LidProcs[j].storage.thickness = 0.0; - LidProcs[j].storage.kSat = 0.0; - LidProcs[j].drain.coeff = 0.0; - LidProcs[j].drain.offset = 0.0; - LidProcs[j].drainMat.thickness = 0.0; - LidProcs[j].drainMat.roughness = 0.0; - LidProcs[j].drainRmvl = NULL; - LidProcs[j].drainRmvl = (double *) - calloc(Nobjects[POLLUT], sizeof(double)); - if (LidProcs[j].drainRmvl == NULL) - { - ErrorCode = ERR_MEMORY; - return; - } - } -} - -//============================================================================= - -void lid_delete() -// -// Purpose: deletes all LID objects -// Input: none -// Output: none -// -{ - int j; - for (j = 0; j < GroupCount; j++) freeLidGroup(j); - FREE(LidGroups); - for (j = 0; j < LidCount; j++) FREE(LidProcs[j].drainRmvl); - FREE(LidProcs); - GroupCount = 0; - LidCount = 0; -} - -//============================================================================= - -void freeLidGroup(int j) -// -// Purpose: frees all LID units associated with a subcatchment. -// Input: j = group (or subcatchment) index -// Output: none -// -{ - TLidGroup lidGroup = LidGroups[j]; - TLidList* lidList; - TLidUnit* lidUnit; - TLidList* nextLidUnit; - - if ( lidGroup == NULL ) return; - lidList = lidGroup->lidList; - while (lidList) - { - lidUnit = lidList->lidUnit; - if ( lidUnit->rptFile ) - { - if ( lidUnit->rptFile->file ) fclose(lidUnit->rptFile->file); - free(lidUnit->rptFile); - } - nextLidUnit = lidList->nextLidUnit; - free(lidUnit); - free(lidList); - lidList = nextLidUnit; - } - free(lidGroup); - LidGroups[j] = NULL; -} - -//============================================================================= - -int lid_readProcParams(char* toks[], int ntoks) -// -// Purpose: reads LID process information from line of input data file -// Input: toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format for first line that defines a LID process is: -// LID_ID LID_Type -// -// Followed by some combination of lines below depending on LID_Type: -// LID_ID SURFACE -// LID_ID PAVEMENT -// LID_ID SOIL -// LID_ID STORAGE -// LID_ID DRAIN -// LID_ID DRAINMAT -// LID_ID REMOVALS -// -{ - int j, m; - - // --- check for minimum number of tokens - if ( ntoks < 2 ) return error_setInpError(ERR_ITEMS, ""); - - // --- check that LID exists in database - j = project_findObject(LID, toks[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, toks[0]); - - // --- assign ID if not done yet - if ( LidProcs[j].ID == NULL ) - LidProcs[j].ID = project_findID(LID, toks[0]); - - // --- check if second token is the type of LID - m = findmatch(toks[1], LidTypeWords); - if ( m >= 0 ) - { - LidProcs[j].lidType = m; - return 0; - } - - // --- check if second token is name of LID layer - else m = findmatch(toks[1], LidLayerWords); - - // --- read input parameters for the identified layer - switch (m) - { - case SURF: return readSurfaceData(j, toks, ntoks); - case SOIL: return readSoilData(j, toks, ntoks); - case STOR: return readStorageData(j, toks, ntoks); - case PAVE: return readPavementData(j, toks, ntoks); - case DRAIN: return readDrainData(j, toks, ntoks); - case DRAINMAT: return readDrainMatData(j, toks, ntoks); - case REMOVALS: return readRemovalsData(j, toks, ntoks); - } - return error_setInpError(ERR_KEYWORD, toks[1]); -} - -//============================================================================= - -int lid_readGroupParams(char* toks[], int ntoks) -// -// Purpose: reads input data for a LID unit placed in a subcatchment. -// Input: toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of input data line is: -// Subcatch_ID LID_ID Number Area Width InitSat FromImp ToPerv -// (RptFile DrainTo FromPerv) -// where: -// Subcatch_ID = name of subcatchment -// LID_ID = name of LID process -// Number (n) = number of replicate units -// Area (x[0]) = area of each unit -// Width (x[1]) = outflow width of each unit -// InitSat (x[2]) = % that LID is initially saturated -// FromImp (x[3]) = % of impervious runoff sent to LID -// ToPerv (x[4]) = 1 if outflow goes to pervious sub-area; 0 if not -// RptFile = name of detailed results file (optional) -// DrainTo = name of subcatch/node for drain flow (optional) -// FromPerv (x[5]) = % of pervious runoff sent to LID -// -{ - int i, j, k, n; - double x[6]; - char* fname = NULL; - int drainSubcatch = -1, drainNode = -1; - - //... check for valid number of input tokens - if ( ntoks < 8 ) return error_setInpError(ERR_ITEMS, ""); - - //... find subcatchment - j = project_findObject(SUBCATCH, toks[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, toks[0]); - - //... find LID process in list of LID processes - k = project_findObject(LID, toks[1]); - if ( k < 0 ) return error_setInpError(ERR_NAME, toks[1]); - - //... get number of replicates - n = atoi(toks[2]); - if ( n < 0 ) return error_setInpError(ERR_NUMBER, toks[2]); - if ( n == 0 ) return 0; - - //... convert next 4 tokens to doubles - for (i = 3; i <= 7; i++) - { - if ( ! getDouble(toks[i], &x[i-3]) || x[i-3] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - //... check for valid percentages on tokens 5 & 6 (x[2] & x[3]) - for (i = 2; i <= 3; i++) if ( x[i] > 100.0 ) - return error_setInpError(ERR_NUMBER, toks[i+3]); - - //... read optional report file name - if ( ntoks >= 9 && strcmp(toks[8], "*") != 0 ) fname = toks[8]; - - //... read optional underdrain outlet - if ( ntoks >= 10 && strcmp(toks[9], "*") != 0 ) - { - drainSubcatch = project_findObject(SUBCATCH, toks[9]); - if ( drainSubcatch < 0 ) - { - drainNode = project_findObject(NODE, toks[9]); - if ( drainNode < 0 ) return error_setInpError(ERR_NAME, toks[9]); - } - } - - //... read percent of pervious area treated by LID unit - x[5] = 0.0; - if (ntoks >= 11) - { - if (!getDouble(toks[10], &x[5]) || x[5] < 0.0 || x[5] > 100.0) - return error_setInpError(ERR_NUMBER, toks[10]); - } - - //... create a new LID unit and add it to the subcatchment's LID group - return addLidUnit(j, k, n, x, fname, drainSubcatch, drainNode); -} - -//============================================================================= - -int addLidUnit(int j, int k, int n, double x[], char* fname, - int drainSubcatch, int drainNode) -// -// Purpose: adds an LID unit to a subcatchment's LID group. -// Input: j = subcatchment index -// k = LID control index -// n = number of replicate units -// x = LID unit's parameters -// fname = name of detailed performance report file -// drainSubcatch = index of subcatchment receiving underdrain flow -// drainNode = index of node receiving underdrain flow -// Output: returns an error code -// -{ - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... create a LID group (pointer to an LidGroup struct) - // if one doesn't already exist - lidGroup = LidGroups[j]; - if ( !lidGroup ) - { - lidGroup = (struct LidGroup *) malloc(sizeof(struct LidGroup)); - if ( !lidGroup ) return error_setInpError(ERR_MEMORY, ""); - lidGroup->lidList = NULL; - LidGroups[j] = lidGroup; - } - - //... create a new LID unit to add to the group - lidUnit = (TLidUnit *) malloc(sizeof(TLidUnit)); - if ( !lidUnit ) return error_setInpError(ERR_MEMORY, ""); - lidUnit->rptFile = NULL; - - //... add the LID unit to the group - lidList = (TLidList *) malloc(sizeof(TLidList)); - if ( !lidList ) - { - free(lidUnit); - return error_setInpError(ERR_MEMORY, ""); - } - lidList->lidUnit = lidUnit; - lidList->nextLidUnit = lidGroup->lidList; - lidGroup->lidList = lidList; - - //... assign parameter values to LID unit - lidUnit->lidIndex = k; - lidUnit->number = n; - lidUnit->area = x[0] / SQR(UCF(LENGTH)); - lidUnit->fullWidth = x[1] / UCF(LENGTH); - lidUnit->initSat = x[2] / 100.0; - lidUnit->fromImperv = x[3] / 100.0; - lidUnit->toPerv = (x[4] > 0.0); - lidUnit->fromPerv = x[5] / 100.0; - lidUnit->drainSubcatch = drainSubcatch; - lidUnit->drainNode = drainNode; - - //... open report file if it was supplied - if ( fname != NULL ) - { - if ( !createLidRptFile(lidUnit, fname) ) - return error_setInpError(ERR_RPT_FILE, fname); - } - return 0; -} - -//============================================================================= - -int createLidRptFile(TLidUnit* lidUnit, char* fname) -{ - TLidRptFile* rptFile; - - rptFile = (TLidRptFile *) malloc(sizeof(TLidRptFile)); - if ( rptFile == NULL ) return 0; - lidUnit->rptFile = rptFile; - rptFile->file = fopen(fname, "wt"); - if ( rptFile->file == NULL ) return 0; - return 1; -} - -//============================================================================= - -int readSurfaceData(int j, char* toks[], int ntoks) -// -// Purpose: reads surface layer data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID SURFACE StorageHt VegVolFrac Roughness SurfSlope SideSlope -// -{ - int i; - double x[5]; - - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 2; i < 7; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - if ( x[1] >= 1.0 ) return error_setInpError(ERR_NUMBER, toks[3]); - if ( x[0] == 0.0 ) x[1] = 0.0; - - LidProcs[j].surface.thickness = x[0] / UCF(RAINDEPTH); - LidProcs[j].surface.voidFrac = 1.0 - x[1]; - LidProcs[j].surface.roughness = x[2]; - LidProcs[j].surface.surfSlope = x[3] / 100.0; - LidProcs[j].surface.sideSlope = x[4]; - return 0; -} - -//============================================================================= - -int readPavementData(int j, char* toks[], int ntoks) -// -// Purpose: reads pavement layer data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID PAVEMENT Thickness VoidRatio FracImperv Permeability ClogFactor -// (RegenDays RegenDegree) -// -{ - int i; - double x[7]; - - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 2; i < 7; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - // ... read optional clogging regeneration properties - x[5] = 0.0; - if (ntoks > 7) - { - if (!getDouble(toks[7], &x[5]) || x[5] < 0.0) - return error_setInpError(ERR_NUMBER, toks[7]); - } - x[6] = 0.0; - if (ntoks > 8) - { - if (!getDouble(toks[8], &x[6]) || x[6] < 0.0 || x[6] > 1.0) - return error_setInpError(ERR_NUMBER, toks[8]); - } - - //... convert void ratio to void fraction - x[1] = x[1]/(x[1] + 1.0); - - LidProcs[j].pavement.thickness = x[0] / UCF(RAINDEPTH); - LidProcs[j].pavement.voidFrac = x[1]; - LidProcs[j].pavement.impervFrac = x[2]; - LidProcs[j].pavement.kSat = x[3] / UCF(RAINFALL); - LidProcs[j].pavement.clogFactor = x[4]; - LidProcs[j].pavement.regenDays = x[5]; - LidProcs[j].pavement.regenDegree = x[6]; - return 0; -} - -//============================================================================= - -int readSoilData(int j, char* toks[], int ntoks) -// -// Purpose: reads soil layer data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID SOIL Thickness Porosity FieldCap WiltPt Ksat Kslope Suction -// -{ - int i; - double x[7]; - - if ( ntoks < 9 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 2; i < 9; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - LidProcs[j].soil.thickness = x[0] / UCF(RAINDEPTH); - LidProcs[j].soil.porosity = x[1]; - LidProcs[j].soil.fieldCap = x[2]; - LidProcs[j].soil.wiltPoint = x[3]; - LidProcs[j].soil.kSat = x[4] / UCF(RAINFALL); - LidProcs[j].soil.kSlope = x[5]; - LidProcs[j].soil.suction = x[6] / UCF(RAINDEPTH); - return 0; -} - -//============================================================================= - -int readStorageData(int j, char* toks[], int ntoks) -// -// Purpose: reads drainage layer data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID STORAGE Thickness VoidRatio Ksat ClogFactor (YES/NO) -// -{ - int i; - int covered = FALSE; - double x[6]; - - //... read numerical parameters - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 2; i < 6; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - //... check if rain barrel is covered - if (ntoks > 6) - { - if (match(toks[6], w_YES)) - covered = TRUE; - } - - //... convert void ratio to void fraction - x[1] = x[1]/(x[1] + 1.0); - - //... save parameters to LID storage layer structure - LidProcs[j].storage.thickness = x[0] / UCF(RAINDEPTH); - LidProcs[j].storage.voidFrac = x[1]; - LidProcs[j].storage.kSat = x[2] / UCF(RAINFALL); - LidProcs[j].storage.clogFactor = x[3]; - LidProcs[j].storage.covered = covered; - return 0; -} - -//============================================================================= - -int readDrainData(int j, char* toks[], int ntoks) -// -// Purpose: reads underdrain data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID DRAIN coeff expon offset delay hOpen hClose curve -// -{ - int i; - double x[6]; - - //... read numerical parameters - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - for (i = 0; i < 6; i++) x[i] = 0.0; - for (i = 2; i < 8; i++) - { - if ( (ntoks > i) && (! getDouble(toks[i], &x[i-2]) || x[i-2]) < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - i = -1; - if ( ntoks >= 9 ) - { - i = project_findObject(CURVE, toks[8]); - if (i < 0) return error_setInpError(ERR_NAME, toks[8]); - } - - //... save parameters to LID drain layer structure - LidProcs[j].drain.coeff = x[0]; - LidProcs[j].drain.expon = x[1]; - LidProcs[j].drain.offset = x[2] / UCF(RAINDEPTH); - LidProcs[j].drain.delay = x[3] * 3600.0; - LidProcs[j].drain.hOpen = x[4] / UCF(RAINDEPTH); - LidProcs[j].drain.hClose = x[5] / UCF(RAINDEPTH); - LidProcs[j].drain.qCurve = i; - return 0; -} - -//============================================================================= - -int readDrainMatData(int j, char* toks[], int ntoks) -// -// Purpose: reads drainage mat data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID DRAINMAT thickness voidRatio roughness -// -{ - int i; - double x[3]; - - //... read numerical parameters - if ( ntoks < 5 ) return error_setInpError(ERR_ITEMS, ""); - if ( LidProcs[j].lidType != GREEN_ROOF ) return 0; - for (i = 2; i < 5; i++) - { - if ( ! getDouble(toks[i], &x[i-2]) || x[i-2] < 0.0 ) - return error_setInpError(ERR_NUMBER, toks[i]); - } - - //... save parameters to LID drain layer structure - LidProcs[j].drainMat.thickness = x[0] / UCF(RAINDEPTH);; - LidProcs[j].drainMat.voidFrac = x[1]; - LidProcs[j].drainMat.roughness = x[2]; - return 0; -} - -//============================================================================= - -int readRemovalsData(int j, char* toks[], int ntoks) -// -// Purpose: reads pollutant removal data for a LID process from line of input -// data file -// Input: j = LID process index -// toks = array of string tokens -// ntoks = number of tokens -// Output: returns error code -// -// Format of data is: -// LID_ID REMOVALS pollut1 %removal1 pollut2 %removal2 ... -// -{ - int i = 2; - int p; - double rmvl; - - //... start with 3rd token - if (ntoks < 4) return error_setInpError(ERR_ITEMS, ""); - while (ntoks > i) - { - //... find pollutant index from its name - p = project_findObject(POLLUT, toks[i]); - if (p < 0) return error_setInpError(ERR_NAME, toks[i]); - - //... check that a next token exists - i++; - if (ntoks == i) return error_setInpError(ERR_ITEMS, ""); - - //... get the % removal value from the next token - if (!getDouble(toks[i], &rmvl) || rmvl < 0.0 || rmvl > 100.0) - return error_setInpError(ERR_NUMBER, toks[i]); - - //... save the pollutant removal for the LID process as a fraction - LidProcs[j].drainRmvl[p] = rmvl / 100.0; - i++; - } - return 0; -} -//============================================================================= - -void lid_writeSummary() -// -// Purpose: writes summary of LID processes used to report file. -// Input: none -// Output: none -// -{ - int j, k; - double pctArea; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - fprintf(Frpt.file, "\n"); - fprintf(Frpt.file, "\n"); - fprintf(Frpt.file, "\n *******************"); - fprintf(Frpt.file, "\n LID Control Summary"); - fprintf(Frpt.file, "\n *******************"); - - - fprintf(Frpt.file, -"\n No. of Unit Unit %% Area %% Imperv %% Perv"); //(5.1.013) - fprintf(Frpt.file, // -"\n Subcatchment LID Control Units Area Width Covered Treated Treated"); // - fprintf(Frpt.file, // -"\n ---------------------------------------------------------------------------------------------------"); // - - for (j = 0; j < GroupCount; j++) - { - lidGroup = LidGroups[j]; - if ( lidGroup == NULL ) continue; - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - pctArea = lidUnit->area * lidUnit->number / Subcatch[j].area * 100.0; - fprintf(Frpt.file, "\n %-16s %-16s", Subcatch[j].ID, LidProcs[k].ID); - fprintf(Frpt.file, "%6d %10.2f %10.2f %10.2f %10.2f %10.2f", - lidUnit->number, lidUnit->area * SQR(UCF(LENGTH)), - lidUnit->fullWidth * UCF(LENGTH), pctArea, - lidUnit->fromImperv*100.0, lidUnit->fromPerv*100.0); - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_validate() -// -// Purpose: validates LID process and group parameters. -// Input: none -// Output: none -// -{ - int j; - for (j = 0; j < LidCount; j++) validateLidProc(j); - for (j = 0; j < GroupCount; j++) validateLidGroup(j); -} - -//============================================================================= - -void validateLidProc(int j) -// -// Purpose: validates LID process parameters. -// Input: j = LID process index -// Output: none -// -{ - int layerMissing = FALSE; - - //... check that LID type was supplied - if ( LidProcs[j].lidType < 0 ) - { - report_writeErrorMsg(ERR_LID_TYPE, LidProcs[j].ID); - return; - } - - //... check that required layers were defined - switch (LidProcs[j].lidType) - { - case BIO_CELL: - case RAIN_GARDEN: - if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; - break; - case GREEN_ROOF: - if ( LidProcs[j].soil.thickness <= 0.0 ) layerMissing = TRUE; - if ( LidProcs[j].drainMat.thickness <= 0.0) layerMissing = TRUE; - break; - case POROUS_PAVEMENT: - if ( LidProcs[j].pavement.thickness <= 0.0 ) layerMissing = TRUE; - break; - case INFIL_TRENCH: - if ( LidProcs[j].storage.thickness <= 0.0 ) layerMissing = TRUE; - break; - } - if ( layerMissing ) - { - report_writeErrorMsg(ERR_LID_LAYER, LidProcs[j].ID); - return; - } - - //... check pavement layer parameters - if ( LidProcs[j].lidType == POROUS_PAVEMENT ) - { - if ( LidProcs[j].pavement.thickness <= 0.0 - || LidProcs[j].pavement.kSat <= 0.0 - || LidProcs[j].pavement.voidFrac <= 0.0 - || LidProcs[j].pavement.voidFrac > 1.0 - || LidProcs[j].pavement.impervFrac > 1.0 ) - - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_PAVE_LAYER, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... check soil layer parameters - if ( LidProcs[j].soil.thickness > 0.0 ) - { - if ( LidProcs[j].soil.porosity <= 0.0 - || LidProcs[j].soil.fieldCap >= LidProcs[j].soil.porosity - || LidProcs[j].soil.wiltPoint >= LidProcs[j].soil.fieldCap - || LidProcs[j].soil.kSat <= 0.0 - || LidProcs[j].soil.kSlope < 0.0 ) - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_SOIL_LAYER, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... check storage layer parameters - if ( LidProcs[j].storage.thickness > 0.0 ) - { - if ( LidProcs[j].storage.voidFrac <= 0.0 || - LidProcs[j].storage.voidFrac > 1.0 ) - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_STOR_LAYER, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... if no storage layer adjust void fraction and drain offset - else - { - LidProcs[j].storage.voidFrac = 1.0; - LidProcs[j].drain.offset = 0.0; - } - - //... check for invalid drain open/closed heads - if (LidProcs[j].drain.hOpen > 0.0 && - LidProcs[j].drain.hOpen <= LidProcs[j].drain.hClose) - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_DRAIN_HEADS, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - - //... compute the surface layer's overland flow constant (alpha) - if ( LidProcs[j].lidType == VEG_SWALE ) - { - if ( LidProcs[j].surface.roughness * - LidProcs[j].surface.surfSlope <= 0.0 || - LidProcs[j].surface.thickness == 0.0 - ) - { - sstrncpy(Msg, LidProcs[j].ID, MAXMSG); - sstrcat(Msg, ERR_SWALE_SURF, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - else LidProcs[j].surface.alpha = - 1.49 * sqrt(LidProcs[j].surface.surfSlope) / - LidProcs[j].surface.roughness; - } - else - { - //... compute surface overland flow coeff. - if ( LidProcs[j].surface.roughness > 0.0 ) - LidProcs[j].surface.alpha = 1.49 / LidProcs[j].surface.roughness * - sqrt(LidProcs[j].surface.surfSlope); - else LidProcs[j].surface.alpha = 0.0; - } - - //... compute drainage mat layer's flow coeff. - if ( LidProcs[j].drainMat.roughness > 0.0 ) - { - LidProcs[j].drainMat.alpha = 1.49 / LidProcs[j].drainMat.roughness * - sqrt(LidProcs[j].surface.surfSlope); - } - else LidProcs[j].drainMat.alpha = 0.0; - - - //... convert clogging factors to void volume basis - if ( LidProcs[j].pavement.thickness > 0.0 ) - { - LidProcs[j].pavement.clogFactor *= - LidProcs[j].pavement.thickness * LidProcs[j].pavement.voidFrac * - (1.0 - LidProcs[j].pavement.impervFrac); - } - if ( LidProcs[j].storage.thickness > 0.0 ) - { - LidProcs[j].storage.clogFactor *= - LidProcs[j].storage.thickness * LidProcs[j].storage.voidFrac; - } - else LidProcs[j].storage.clogFactor = 0.0; - - //... for certain LID types, immediate overflow of excess surface water - // occurs if either the surface roughness or slope is zero - LidProcs[j].surface.canOverflow = TRUE; - switch (LidProcs[j].lidType) - { - case ROOF_DISCON: LidProcs[j].surface.canOverflow = FALSE; break; - case INFIL_TRENCH: - case POROUS_PAVEMENT: - case BIO_CELL: - case RAIN_GARDEN: - case GREEN_ROOF: - if ( LidProcs[j].surface.alpha > 0.0 ) - LidProcs[j].surface.canOverflow = FALSE; - } - - //... rain barrels have 100% void space and impermeable bottom - if ( LidProcs[j].lidType == RAIN_BARREL ) - { - LidProcs[j].storage.voidFrac = 1.0; - LidProcs[j].storage.kSat = 0.0; - } - - //... set storage layer parameters of a green roof - if ( LidProcs[j].lidType == GREEN_ROOF ) - { - LidProcs[j].storage.thickness = LidProcs[j].drainMat.thickness; - LidProcs[j].storage.voidFrac = LidProcs[j].drainMat.voidFrac; - LidProcs[j].storage.clogFactor = 0.0; - LidProcs[j].storage.kSat = 0.0; - } -} - -//============================================================================= - -void validateLidGroup(int j) -// -// Purpose: validates properties of LID units grouped in a subcatchment. -// Input: j = subcatchment index -// Output: returns 1 if data are valid, 0 if not -// -{ - int k; - double p[3]; - double totalArea = Subcatch[j].area; - double totalLidArea = 0.0; - double fromImperv = 0.0; - double fromPerv = 0.0; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - lidGroup = LidGroups[j]; - if ( lidGroup == NULL ) return; - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - - //... update contributing fractions - totalLidArea += (lidUnit->area * lidUnit->number); - fromImperv += lidUnit->fromImperv; - fromPerv += lidUnit->fromPerv; - - //... assign biocell soil layer infiltration parameters - lidUnit->soilInfil.Ks = 0.0; - if ( LidProcs[k].soil.thickness > 0.0 ) - { - p[0] = LidProcs[k].soil.suction * UCF(RAINDEPTH); - p[1] = LidProcs[k].soil.kSat * UCF(RAINFALL); - p[2] = (LidProcs[k].soil.porosity - LidProcs[k].soil.wiltPoint) * - (1.0 - lidUnit->initSat); - if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) - { - sstrncpy(Msg, LidProcs[k].ID, MAXMSG); - sstrcat(Msg, ERR_SOIL_LAYER, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... assign vegetative swale infiltration parameters - if ( LidProcs[k].lidType == VEG_SWALE ) - { - if ( Subcatch[j].infilModel == GREEN_AMPT || - Subcatch[j].infilModel == MOD_GREEN_AMPT ) - { - grnampt_getParams(j, p); - if ( grnampt_setParams(&(lidUnit->soilInfil), p) == FALSE ) - { - sstrncpy(Msg, LidProcs[k].ID, MAXMSG); - sstrcat(Msg, ERR_GREEN_AMPT, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - if ( lidUnit->fullWidth <= 0.0 ) - { - sstrncpy(Msg, LidProcs[k].ID, MAXMSG); - sstrcat(Msg, ERR_SWALE_WIDTH, MAXMSG); - report_writeErrorMsg(ERR_LID_PARAMS, Msg); - } - } - - //... LID unit cannot send outflow back to subcatchment's - // pervious area if none exists - if ( Subcatch[j].fracImperv >= 0.999 ) lidUnit->toPerv = 0; - - //... assign drain outlet if not set by user - if ( lidUnit->drainNode == -1 && lidUnit->drainSubcatch == -1 ) - { - lidUnit->drainNode = Subcatch[j].outNode; - lidUnit->drainSubcatch = Subcatch[j].outSubcatch; - } - lidList = lidList->nextLidUnit; - } - - //... check contributing area fractions - if ( totalLidArea > 1.001 * totalArea ) - { - report_writeErrorMsg(ERR_LID_AREAS, Subcatch[j].ID); - } - if ( fromImperv > 1.001 || fromPerv > 1.001 ) - { - report_writeErrorMsg(ERR_LID_CAPTURE_AREA, Subcatch[j].ID); - } - - //... Make subcatchment LID area equal total area if the two are close - if ( totalLidArea > 0.999 * totalArea ) totalLidArea = totalArea; - Subcatch[j].lidArea = totalLidArea; -} - -//============================================================================= - -void lid_initState() -// -// Purpose: initializes the internal state of each LID in a subcatchment. -// Input: none -// Output: none -// -{ - int i, j, k; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - double initVol; - double initDryTime = StartDryDays * SECperDAY; - - HasWetLids = FALSE; - for (j = 0; j < GroupCount; j++) - { - //... check if group exists - lidGroup = LidGroups[j]; - if ( lidGroup == NULL ) continue; - - //... initialize group variables - lidGroup->pervArea = 0.0; - lidGroup->flowToPerv = 0.0; - lidGroup->oldDrainFlow = 0.0; - lidGroup->newDrainFlow = 0.0; - - //... examine each LID in the group - lidList = lidGroup->lidList; - while ( lidList ) - { - //... initialize depth & moisture content - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - lidUnit->surfaceDepth = 0.0; - lidUnit->storageDepth = 0.0; - lidUnit->soilMoisture = 0.0; - lidUnit->paveDepth = 0.0; - lidUnit->dryTime = initDryTime; - lidUnit->volTreated = 0.0; - lidUnit->nextRegenDay = LidProcs[k].pavement.regenDays; - initVol = 0.0; - if ( LidProcs[k].soil.thickness > 0.0 ) - { - lidUnit->soilMoisture = LidProcs[k].soil.wiltPoint + - lidUnit->initSat * (LidProcs[k].soil.porosity - - LidProcs[k].soil.wiltPoint); - initVol += lidUnit->soilMoisture * LidProcs[k].soil.thickness; - } - if ( LidProcs[k].storage.thickness > 0.0 ) - { - lidUnit->storageDepth = lidUnit->initSat * - LidProcs[k].storage.thickness; - initVol += lidUnit->storageDepth * LidProcs[k].storage.voidFrac; - } - if ( LidProcs[k].drainMat.thickness > 0.0 ) - { - lidUnit->storageDepth = lidUnit->initSat * - LidProcs[k].drainMat.thickness; - initVol += lidUnit->storageDepth * LidProcs[k].drainMat.voidFrac; - } - if ( lidUnit->initSat > 0.0 ) HasWetLids = TRUE; - - //... initialize water balance totals - lidproc_initWaterBalance(lidUnit, initVol); - lidUnit->volTreated = 0.0; - - //... initialize report file for the LID - if ( lidUnit->rptFile ) - { - initLidRptFile(Title[0], LidProcs[k].ID, Subcatch[j].ID, lidUnit); - } - - //... initialize drain flows - lidUnit->oldDrainFlow = 0.0; - lidUnit->newDrainFlow = 0.0; - - //... set previous flux rates to 0 - for (i = 0; i < MAX_LAYERS; i++) - { - lidUnit->oldFluxRates[i] = 0.0; - } - - //... initialize infiltration state variables - if ( lidUnit->soilInfil.Ks > 0.0 ) - grnampt_initState(&(lidUnit->soilInfil)); - - //... add contribution to pervious LID area - if ( isLidPervious(lidUnit->lidIndex) ) - lidGroup->pervArea += (lidUnit->area * lidUnit->number); - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_setOldGroupState(int j) -// -// Purpose: saves the current drain flow rate for the LIDs in a subcatchment. -// Input: j = subcatchment index -// Output: none -// -{ - TLidList* lidList; - if ( LidGroups[j] != NULL ) - { - LidGroups[j]->oldDrainFlow = LidGroups[j]->newDrainFlow; - LidGroups[j]->newDrainFlow = 0.0; - lidList = LidGroups[j]->lidList; - while (lidList) - { - lidList->lidUnit->oldDrainFlow = lidList->lidUnit->newDrainFlow; - lidList->lidUnit->newDrainFlow = 0.0; - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -int isLidPervious(int k) -// -// Purpose: determines if a LID process allows infiltration or not. -// Input: k = LID process index -// Output: returns 1 if process is pervious or 0 if not -// -{ - return ( LidProcs[k].storage.thickness == 0.0 || - LidProcs[k].storage.kSat > 0.0 ); -} - -//============================================================================= - -double getSurfaceDepth(int j) -// -// Purpose: computes the depth (volume per unit area) of ponded water on the -// surface of all LIDs within a subcatchment. -// Input: j = subcatchment index -// Output: returns volumetric depth of ponded water (ft) -// -{ - int k; - double depth = 0.0; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - lidGroup = LidGroups[j]; - if ( lidGroup == NULL ) return 0.0; - if ( Subcatch[j].lidArea == 0.0 ) return 0.0; - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - depth += lidUnit->surfaceDepth * LidProcs[k].surface.voidFrac * - lidUnit->area * lidUnit->number; - lidList = lidList->nextLidUnit; - } - return depth / Subcatch[j].lidArea; -} - -//============================================================================= - -double lid_getPervArea(int j) -// -// Purpose: retrieves amount of pervious LID area in a subcatchment. -// Input: j = subcatchment index -// Output: returns amount of pervious LID area (ft2) -// -{ - if ( LidGroups[j] ) return LidGroups[j]->pervArea; - else return 0.0; -} - -//============================================================================= - -double lid_getFlowToPerv(int j) -// -// Purpose: retrieves flow returned from LID treatment to pervious area of -// a subcatchment. -// Input: j = subcatchment index -// Output: returns flow returned to pervious area (cfs) -// -{ - if ( LidGroups[j] != NULL ) return LidGroups[j]->flowToPerv; - return 0.0; -} - -//============================================================================= - -double lid_getStoredVolume(int j) -// -// Purpose: computes stored volume of water for all LIDs -// grouped within a subcatchment. -// Input: j = subcatchment index -// Output: returns stored volume of water (ft3) -// -{ - double total = 0.0; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - lidGroup = LidGroups[j]; - if ( lidGroup == NULL || Subcatch[j].lidArea == 0.0 ) return 0.0; - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - total += lidUnit->waterBalance.finalVol * lidUnit->area * lidUnit->number; - lidList = lidList->nextLidUnit; - } - return total; -} - -//============================================================================= - -double lid_getDrainFlow(int j, int timePeriod) -// -// Purpose: returns flow from all of a subcatchment's LID drains for -// a designated time period -// Input: j = subcatchment index -// timePeriod = either PREVIOUS or CURRENT -// Output: total drain flow (cfs) from the subcatchment. -{ - if ( LidGroups[j] != NULL ) - { - if ( timePeriod == PREVIOUS ) return LidGroups[j]->oldDrainFlow; - else return LidGroups[j]->newDrainFlow; - } - return 0.0; -} - -//============================================================================= - -void lid_addDrainLoads(int j, double c[], double tStep) -// -// Purpose: adds pollutant loads routed from drains to system -// mass balance totals. -// Input: j = subcatchment index -// c = array of pollutant washoff concentrations (mass/L) -// tStep = time step (sec) -// Output: none. -// -{ - int isRunoffLoad; // true if drain becomes external runoff load - int p; // pollutant index - double r; // pollutant fractional removal - double w; // pollutant mass load (lb or kg) - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... check if LID group exists - lidGroup = LidGroups[j]; - if ( lidGroup != NULL ) - { - //... examine each LID unit in the group - lidList = lidGroup->lidList; - while ( lidList ) - { - lidUnit = lidList->lidUnit; - - //... see if unit's drain flow becomes external runoff - isRunoffLoad = (lidUnit->drainNode >= 0 || - lidUnit->drainSubcatch == j); - - //... for each pollutant not routed back on to subcatchment surface - if (!lidUnit->toPerv) for (p = 0; p < Nobjects[POLLUT]; p++) - { - //... get mass load flowing through the drain - w = lidUnit->newDrainFlow * c[p] * tStep * LperFT3 * Pollut[p].mcf; - - //... get fractional removal for this load - r = LidProcs[lidUnit->lidIndex].drainRmvl[p]; - - //... update system mass balance totals - massbal_updateLoadingTotals(BMP_REMOVAL_LOAD, p, r*w); - if (isRunoffLoad) - massbal_updateLoadingTotals(RUNOFF_LOAD, p, w*(1.0 - r)); - } - - // process next LID unit in the group - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_addDrainRunon(int j) -// -// Purpose: adds drain flows from LIDs in a given subcatchment to the -// subcatchments that were designated to receive them -// Input: j = index of subcatchment contributing underdrain flows -// Output: none. -// -{ - int i; // index of an LID unit's LID process - int k; // index of subcatchment receiving LID drain flow - int p; // pollutant index - double q; // drain flow rate (cfs) - double w; // mass of polllutant from drain flow - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... check if LID group exists - lidGroup = LidGroups[j]; - if ( lidGroup != NULL ) - { - //... examine each LID in the group - lidList = lidGroup->lidList; - while ( lidList ) - { - //... see if LID's drain discharges to another subcatchment - lidUnit = lidList->lidUnit; - i = lidUnit->lidIndex; - k = lidUnit->drainSubcatch; - if ( k >= 0 && k != j ) - { - //... distribute drain flow across subcatchment's areas - q = lidUnit->oldDrainFlow; - subcatch_addRunonFlow(k, q); - - //... add pollutant loads from drain to subcatchment - // (newQual[] contains loading rate (mass/sec) at this - // point which is converted later on to a concentration) - for (p = 0; p < Nobjects[POLLUT]; p++) - { - w = q * Subcatch[j].oldQual[p] * LperFT3; - w = w * (1.0 - LidProcs[i].drainRmvl[p]); - Subcatch[k].newQual[p] += w; - } - } - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_addDrainInflow(int j, double f) -// -// Purpose: adds LID drain flow to conveyance system nodes -// Input: j = subcatchment index -// f = time interval weighting factor -// Output: none. -// -// Note: this function updates the total lateral flow (Node[].newLatFlow) -// and pollutant mass (Node[].newQual[]) inflow seen by nodes that -// receive drain flow from the LID units in subcatchment j. -{ - int i, // LID process index - k, // node index - p; // pollutant index - double q, // drain flow (cfs) - w, w1, w2; // pollutant mass loads (mass/sec) - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... check if LID group exists - lidGroup = LidGroups[j]; - if ( lidGroup != NULL ) - { - //... examine each LID in the group - lidList = lidGroup->lidList; - while ( lidList ) - { - //... see if LID's drain discharges to conveyance system node - lidUnit = lidList->lidUnit; - i = lidUnit->lidIndex; - k = lidUnit->drainNode; - if ( k >= 0 ) - { - //... add drain flow to node's wet weather inflow - q = (1.0 - f) * lidUnit->oldDrainFlow + f * lidUnit->newDrainFlow; - Node[k].newLatFlow += q; - massbal_addInflowFlow(WET_WEATHER_INFLOW, q); - - //... add pollutant load, based on parent subcatchment quality - for (p = 0; p < Nobjects[POLLUT]; p++) - { - //... get previous & current drain loads - w1 = lidUnit->oldDrainFlow * Subcatch[j].oldQual[p]; - w2 = lidUnit->newDrainFlow * Subcatch[j].newQual[p]; - - //... add interpolated load to node's wet weather loading - w = (1.0 - f) * w1 + f * w2; - w = w * (1.0 - LidProcs[i].drainRmvl[p]); - Node[k].newQual[p] += w; - massbal_addInflowQual(WET_WEATHER_INFLOW, p, w); - } - } - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void lid_getRunoff(int j, double tStep) -// -// Purpose: computes runoff and drain flows from the LIDs in a subcatchment. -// Input: j = subcatchment index -// tStep = time step (sec) -// Output: updates following global quantities after LID treatment applied: -// Vevap, Vpevap, VlidInfil, VlidIn, VlidOut, VlidDrain. -// -{ - TLidGroup theLidGroup; // group of LIDs placed in the subcatchment - TLidList* lidList; // list of LID units in the group - TLidUnit* lidUnit; // a member of the list of LID units - double lidArea; // area of an LID unit - double qImperv = 0.0; // runoff from impervious areas (cfs) - double qPerv = 0.0; // runoff from pervious areas (cfs) - double lidInflow = 0.0; // inflow to an LID unit (ft/s) - double qRunoff = 0.0; // surface runoff from all LID units (cfs) - double qDrain = 0.0; // drain flow from all LID units (cfs) - double qReturn = 0.0; // LID outflow returned to pervious area (cfs) - - //... return if there are no LID's - theLidGroup = LidGroups[j]; - if ( !theLidGroup ) return; - lidList = theLidGroup->lidList; - if ( !lidList ) return; - - //... determine if evaporation can occur - EvapRate = Evap.rate; - if ( Evap.dryOnly && Subcatch[j].rainfall > 0.0 ) EvapRate = 0.0; - - //... find subcatchment's infiltration rate into native soil - findNativeInfil(j, tStep); - - //... get impervious and pervious area runoff from non-LID - // portion of subcatchment (cfs) - if ( Subcatch[j].area > Subcatch[j].lidArea ) - { - qImperv = getImpervAreaRunoff(j); - qPerv = getPervAreaRunoff(j); - } - - //... evaluate performance of each LID unit placed in the subcatchment - while ( lidList ) - { - //... find area of the LID unit - lidUnit = lidList->lidUnit; - lidArea = lidUnit->area * lidUnit->number; - - //... if LID unit has area, evaluate its performance - if ( lidArea > 0.0 ) - { - //... find runoff from non-LID area treated by LID area (ft/sec) - lidInflow = (qImperv * lidUnit->fromImperv + - qPerv * lidUnit->fromPerv) / lidArea; - - //... update total runoff volume treated - VlidIn += lidInflow * lidArea * tStep; - - //... add rainfall onto LID inflow (ft/s) - lidInflow = lidInflow + getRainInflow(j, lidUnit); - - // ... add upstream runon only if LID occupies full subcatchment - if ( Subcatch[j].area == Subcatch[j].lidArea ) - { - lidInflow += Subcatch[j].runon; - } - - //... evaluate the LID unit's performance, updating the LID group's - // total surface runoff, drain flow, and flow returned to - // pervious area - evalLidUnit(j, lidUnit, lidArea, lidInflow, tStep, - &qRunoff, &qDrain, &qReturn); - } - lidList = lidList->nextLidUnit; - } - - //... save the LID group's total drain & return flows - theLidGroup->newDrainFlow = qDrain; - theLidGroup->flowToPerv = qReturn; - - //... save the LID group's total surface, drain and return flow volumes - VlidOut = qRunoff * tStep; - VlidDrain = qDrain * tStep; - VlidReturn = qReturn * tStep; -} - -//============================================================================= - -void findNativeInfil(int j, double tStep) -// -// Purpose: determines a subcatchment's current infiltration rate into -// its native soil. -// Input: j = subcatchment index -// tStep = time step (sec) -// Output: sets values for module-level variables NativeInfil -// -{ - double nonLidArea; - - //... subcatchment has non-LID pervious area - nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; - if ( nonLidArea > 0.0 && Subcatch[j].fracImperv < 1.0 ) - { - NativeInfil = Vinfil / nonLidArea / tStep; - } - - //... otherwise find infil. rate for the subcatchment's rainfall + runon - else - { - NativeInfil = infil_getInfil(j, tStep, - Subcatch[j].rainfall, - Subcatch[j].runon, - getSurfaceDepth(j)); - } - - //... see if there is any groundwater-imposed limit on infil. - if ( !IgnoreGwater && Subcatch[j].groundwater ) - { - MaxNativeInfil = Subcatch[j].groundwater->maxInfilVol / tStep; - } - else MaxNativeInfil = BIG; -} - -//============================================================================= - -double getRainInflow(int j, TLidUnit* lidUnit) -// -// Purpose: gets rainfall inflow to an LID unit. -// Input: j = subcatchment index -// lidUnit = ptr. to an LID unit -// Output: returns rainfall rate over the LID unit (ft/sec) -// -{ - TLidProc* lidProc = &LidProcs[lidUnit->lidIndex]; - - if (lidProc->lidType == RAIN_BARREL && - lidProc->storage.covered == TRUE) return 0.0; - return Subcatch[j].rainfall; -} - -//============================================================================= - -double getImpervAreaRunoff(int j) -// -// Purpose: computes runoff from impervious area of a subcatchment that -// is available for LID treatment. -// Input: j = subcatchment index -// Output: returns runoff flow rate (cfs) -// -{ - int i; - double q = 0.0, // runoff rate (ft/sec) - nonLidArea; // non-LID area (ft2) - - // --- runoff from impervious area w/ & w/o depression storage - for (i = IMPERV0; i <= IMPERV1; i++) - { - q += Subcatch[j].subArea[i].runoff * Subcatch[j].subArea[i].fArea; - } - - // --- adjust for any fraction of runoff sent to pervious area - if ( Subcatch[j].subArea[IMPERV0].routeTo == TO_PERV && - Subcatch[j].fracImperv < 1.0 ) - { - q *= Subcatch[j].subArea[IMPERV0].fOutlet; - } - nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; - return q * nonLidArea; -} - -//============================================================================= - -double getPervAreaRunoff(int j) -// -// Purpose: computes runoff from pervious area of a subcatchment that -// is available for LID treatment. -// Input: j = subcatchment index -// Output: returns runoff flow rate (cfs) -// -{ - double q = 0.0, // runoff rate (ft/sec) - nonLidArea; // non-LID area (ft2) - - // --- runoff from pervious area - q = Subcatch[j].subArea[PERV].runoff * Subcatch[j].subArea[PERV].fArea; - - // --- adjust for any fraction of runoff sent to impervious area - if (Subcatch[j].subArea[PERV].routeTo == TO_IMPERV && - Subcatch[j].fracImperv > 0.0) - { - q *= Subcatch[j].subArea[PERV].fOutlet; - } - nonLidArea = Subcatch[j].area - Subcatch[j].lidArea; - return q * nonLidArea; -} - -//============================================================================= - -void evalLidUnit(int j, TLidUnit* lidUnit, double lidArea, double lidInflow, - double tStep, double *qRunoff, double *qDrain, double *qReturn) -// -// Purpose: evaluates performance of a specific LID unit over current time step. -// Input: j = subcatchment index -// lidUnit = ptr. to LID unit being evaluated -// lidArea = area of LID unit -// lidInflow = inflow to LID unit (ft/s) -// tStep = time step (sec) -// Output: qRunoff = sum of surface runoff from all LIDs (cfs) -// qDrain = sum of drain flows from all LIDs (cfs) -// qReturn = sum of LID flows returned to pervious area (cfs) -// -{ - TLidProc* lidProc; // LID process associated with lidUnit - double lidRunoff, // surface runoff from LID unit (cfs) - lidEvap, // evaporation rate from LID unit (ft/s) - lidInfil, // infiltration rate from LID unit (ft/s) - lidDrain; // drain flow rate from LID unit (ft/s & cfs) - - //... identify the LID process of the LID unit being analyzed - lidProc = &LidProcs[lidUnit->lidIndex]; - - //... initialize evap and infil losses - lidEvap = 0.0; - lidInfil = 0.0; - - //... find surface runoff from the LID unit (in cfs) - lidRunoff = lidproc_getOutflow(lidUnit, lidProc, lidInflow, EvapRate, - NativeInfil, MaxNativeInfil, tStep, - &lidEvap, &lidInfil, &lidDrain) * lidArea; - - //... convert drain flow to CFS - lidDrain *= lidArea; - - //... revise flows if LID outflow returned to pervious area - if ( lidUnit->toPerv && Subcatch[j].area > Subcatch[j].lidArea ) - { - //... surface runoff is always returned - *qReturn += lidRunoff; - lidRunoff = 0.0; - - //... drain flow returned if it has same outlet as subcatchment - if ( lidUnit->drainNode == Subcatch[j].outNode && - lidUnit->drainSubcatch == Subcatch[j].outSubcatch ) - { - *qReturn += lidDrain; - lidDrain = 0.0; - } - } - - //... update system flow balance if drain flow goes to a - // conveyance system node - if ( lidUnit->drainNode >= 0 ) - { - massbal_updateRunoffTotals(RUNOFF_DRAINS, lidDrain * tStep); - } - - //... save new drain outflow - lidUnit->newDrainFlow = lidDrain; - - //... update moisture losses (ft3) - Vevap += lidEvap * tStep * lidArea; - VlidInfil += lidInfil * tStep * lidArea; - if ( isLidPervious(lidUnit->lidIndex) ) - { - Vpevap += lidEvap * tStep * lidArea; - } - - //... update time since last rainfall (for Rain Barrel emptying) - if ( Subcatch[j].rainfall > MIN_RUNOFF ) lidUnit->dryTime = 0.0; - else lidUnit->dryTime += tStep; - - //... update LID water balance and save results - lidproc_saveResults(lidUnit, UCF(RAINFALL), UCF(RAINDEPTH)); - - //... update LID group totals - *qRunoff += lidRunoff; - *qDrain += lidDrain; -} - -//============================================================================= - -void lid_writeWaterBalance() -// -// Purpose: writes a LID performance summary table to the project's report file. -// Input: none -// Output: none -// -{ - int j; - int k = 0; - double ucf = UCF(RAINDEPTH); - double inflow; - double outflow; - double err; - TLidUnit* lidUnit; - TLidList* lidList; - TLidGroup lidGroup; - - //... check that project has LIDs - for ( j = 0; j < GroupCount; j++ ) - { - if ( LidGroups[j] ) k++; - } - if ( k == 0 ) return; - - //... write table header - fprintf(Frpt.file, - "\n" - "\n ***********************" - "\n LID Performance Summary" - "\n ***********************\n"); - - fprintf(Frpt.file, -"\n --------------------------------------------------------------------------------------------------------------------" -"\n Total Evap Infil Surface Drain Initial Final Continuity" -"\n Inflow Loss Loss Outflow Outflow Storage Storage Error"); - if ( UnitSystem == US ) fprintf(Frpt.file, -"\n Subcatchment LID Control in in in in in in in %%"); - else fprintf(Frpt.file, -"\n Subcatchment LID Control mm mm mm mm mm mm mm %%"); - fprintf(Frpt.file, -"\n --------------------------------------------------------------------------------------------------------------------"); - - //... examine each LID unit in each subcatchment - for ( j = 0; j < GroupCount; j++ ) - { - lidGroup = LidGroups[j]; - if ( !lidGroup || Subcatch[j].lidArea == 0.0 ) continue; - lidList = lidGroup->lidList; - while ( lidList ) - { - //... write water balance components to report file - lidUnit = lidList->lidUnit; - k = lidUnit->lidIndex; - fprintf(Frpt.file, "\n %-16s %-16s", Subcatch[j].ID, - LidProcs[k].ID); - fprintf(Frpt.file, "%10.2f%10.2f%10.2f%10.2f%10.2f%10.2f%10.2f", - lidUnit->waterBalance.inflow*ucf, - lidUnit->waterBalance.evap*ucf, - lidUnit->waterBalance.infil*ucf, - lidUnit->waterBalance.surfFlow*ucf, - lidUnit->waterBalance.drainFlow*ucf, - lidUnit->waterBalance.initVol*ucf, - lidUnit->waterBalance.finalVol*ucf); - - //... compute flow balance error - inflow = lidUnit->waterBalance.initVol + - lidUnit->waterBalance.inflow; - outflow = lidUnit->waterBalance.finalVol + - lidUnit->waterBalance.evap + - lidUnit->waterBalance.infil + - lidUnit->waterBalance.surfFlow + - lidUnit->waterBalance.drainFlow; - if ( inflow > 0.0 ) err = (inflow - outflow) / inflow; - else err = 1.0; - fprintf(Frpt.file, " %10.2f", err*100.0); - lidList = lidList->nextLidUnit; - } - } -} - -//============================================================================= - -void initLidRptFile(char* title, char* lidID, char* subcatchID, TLidUnit* lidUnit) -// -// Purpose: initializes the report file used for a specific LID unit -// Input: title = project's title -// lidID = LID process name -// subcatchID = subcatchment ID name -// lidUnit = ptr. to LID unit -// Output: none -// -{ - static int colCount = 14; - static char* head1[] = { - "\n \t", " Elapsed\t", - " Total\t", " Total\t", " Surface\t", " Pavement\t", " Soil\t", - " Storage\t", " Surface\t", " Drain\t", " Surface\t", " Pavement\t", - " Soil\t", " Storage"}; - static char* head2[] = { - "\n \t", " Time\t", - " Inflow\t", " Evap\t", " Infil\t", " Perc\t", " Perc\t", - " Exfil\t", " Runoff\t", " OutFlow\t", " Level\t", " Level\t", - " Moisture\t", " Level"}; - static char* units1[] = { - "\nDate Time \t", " Hours\t", - " in/hr\t", " in/hr\t", " in/hr\t", " in/hr\t", " in/hr\t", - " in/hr\t", " in/hr\t", " in/hr\t", " inches\t", " inches\t", - " Content\t", " inches"}; - static char* units2[] = { - "\nDate Time \t", " Hours\t", - " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm/hr\t", - " mm/hr\t", " mm/hr\t", " mm/hr\t", " mm\t", " mm\t", - " Content\t", " mm"}; - static char line9[] = " ---------"; - int i; - FILE* f = lidUnit->rptFile->file; - - //... check that file was opened - if ( f == NULL ) return; - - //... write title lines - fprintf(f, "SWMM5 LID Report File\n"); - fprintf(f, "\nProject: %s", title); - fprintf(f, "\nLID Unit: %s in Subcatchment %s\n", lidID, subcatchID); - - //... write column headings - for ( i = 0; i < colCount; i++) fprintf(f, "%s", head1[i]); - for ( i = 0; i < colCount; i++) fprintf(f, "%s", head2[i]); - if ( UnitSystem == US ) - { - for ( i = 0; i < colCount; i++) fprintf(f, "%s", units1[i]); - } - else for ( i = 0; i < colCount; i++) fprintf(f, "%s", units2[i]); - fprintf(f, "\n----------- --------"); - for ( i = 1; i < colCount; i++) fprintf(f, "\t%s", line9); - - //... initialize LID dryness state - lidUnit->rptFile->wasDry = 1; - sstrncpy(lidUnit->rptFile->results, "", 0); -} diff --git a/src/lid.h b/src/lid.h deleted file mode 100644 index b250fcf3a..000000000 --- a/src/lid.h +++ /dev/null @@ -1,236 +0,0 @@ -//----------------------------------------------------------------------------- -// lid.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// Public interface for LID functions. -// -// Update History -// ============== -// Build 5.1.008: -// - Support added for Roof Disconnection LID. -// - Support added for separate routing of LID drain flows. -// - Detailed LID reporting modified. -// Build 5.1.011: -// - Water depth replaces moisture content for LID's pavement layer. -// - Arguments for lidproc_saveResults() modified. -// Build 5.1.012: -// - Redefined meaning of wasDry in TLidRptFile structure. -// Build 5.1.013: -// - New member fromPerv added to TLidUnit structure to allow LID -// units to also treat pervious area runoff. -// - New members hOpen and hClose addded to TDrainLayer to open/close -// drain when certain heads are reached. -// - New member qCurve added to TDrainLayer to allow underdrain flow to -// be adjusted by a curve of multiplier v. head. -// - New array drainRmvl added to TLidProc to allow for underdrain -// pollutant removal values. -// - New members added to TPavementLayer and TLidUnit to support -// unclogging permeable pavement at fixed intervals. -// Build 5.2.0: -// - Covered property added to RAIN_BARREL parameters -//----------------------------------------------------------------------------- - -#ifndef LID_H -#define LID_H - -#include -#include -#include -#include "infil.h" - -//----------------------------------------------------------------------------- -// Enumerations -//----------------------------------------------------------------------------- -enum LidTypes { - BIO_CELL, // bio-retention cell - RAIN_GARDEN, // rain garden - GREEN_ROOF, // green roof - INFIL_TRENCH, // infiltration trench - POROUS_PAVEMENT, // porous pavement - RAIN_BARREL, // rain barrel - VEG_SWALE, // vegetative swale - ROOF_DISCON}; // roof disconnection - -enum TimePeriod { - PREVIOUS, // previous time period - CURRENT}; // current time period - -//----------------------------------------------------------------------------- -// Data Structures -//----------------------------------------------------------------------------- -#define MAX_LAYERS 4 - -// LID Surface Layer -typedef struct -{ - double thickness; // depression storage or berm ht. (ft) - double voidFrac; // available fraction of storage volume - double roughness; // surface Mannings n - double surfSlope; // land surface slope (fraction) - double sideSlope; // swale side slope (run/rise) - double alpha; // slope/roughness term in Manning eqn. - char canOverflow; // 1 if immediate outflow of excess water -} TSurfaceLayer; - -// LID Pavement Layer -typedef struct -{ - double thickness; // layer thickness (ft) - double voidFrac; // void volume / total volume - double impervFrac; // impervious area fraction - double kSat; // permeability (ft/sec) - double clogFactor; // clogging factor - double regenDays; // clogging regeneration interval (days) - double regenDegree; // degree of clogging regeneration -} TPavementLayer; - -// LID Soil Layer -typedef struct -{ - double thickness; // layer thickness (ft) - double porosity; // void volume / total volume - double fieldCap; // field capacity - double wiltPoint; // wilting point - double suction; // suction head at wetting front (ft) - double kSat; // saturated hydraulic conductivity (ft/sec) - double kSlope; // slope of log(K) v. moisture content curve -} TSoilLayer; - -// LID Storage Layer -typedef struct -{ - double thickness; // layer thickness (ft) - double voidFrac; // void volume / total volume - double kSat; // saturated hydraulic conductivity (ft/sec) - double clogFactor; // clogging factor - int covered; // TRUE if rain barrel is covered -} TStorageLayer; - -// Underdrain System (part of Storage Layer) -typedef struct -{ - double coeff; // underdrain flow coeff. (in/hr or mm/hr) - double expon; // underdrain head exponent (for in or mm) - double offset; // offset height of underdrain (ft) - double delay; // rain barrel drain delay time (sec) - double hOpen; // head when drain opens (ft) - double hClose; // head when drain closes (ft) - int qCurve; // curve controlling flow rate (optional) -} TDrainLayer; - -// Drainage Mat Layer (for green roofs) -typedef struct -{ - double thickness; // layer thickness (ft) - double voidFrac; // void volume / total volume - double roughness; // Mannings n for green roof drainage mats - double alpha; // slope/roughness term in Manning equation -} TDrainMatLayer; - -// LID Process - generic LID design per unit of area -typedef struct -{ - char* ID; // identifying name - int lidType; // type of LID - TSurfaceLayer surface; // surface layer parameters - TPavementLayer pavement; // pavement layer parameters - TSoilLayer soil; // soil layer parameters - TStorageLayer storage; // storage layer parameters - TDrainLayer drain; // underdrain system parameters - TDrainMatLayer drainMat; // drainage mat layer - double* drainRmvl; // underdrain pollutant removals -} TLidProc; - -// Water Balance Statistics -typedef struct -{ - double inflow; // total inflow (ft) - double evap; // total evaporation (ft) - double infil; // total infiltration (ft) - double surfFlow; // total surface runoff (ft) - double drainFlow; // total underdrain flow (ft) - double initVol; // initial stored volume (ft) - double finalVol; // final stored volume (ft) -} TWaterBalance; - -// LID Report File -typedef struct -{ - FILE* file; // file pointer - int wasDry; // number of successive dry periods - char results[256]; // results for current time period -} TLidRptFile; - -// LID Unit - specific LID process applied over a given area -typedef struct -{ - int lidIndex; // index of LID process - int number; // number of replicate units - double area; // area of single replicate unit (ft2) - double fullWidth; // full top width of single unit (ft) - double botWidth; // bottom width of single unit (ft) - double initSat; // initial saturation of soil & storage layers - double fromImperv; // fraction of impervious area runoff treated - double fromPerv; // fraction of pervious area runoff treated - int toPerv; // 1 if outflow sent to pervious area; 0 if not - int drainSubcatch; // subcatchment receiving drain flow - int drainNode; // node receiving drain flow - TLidRptFile* rptFile; // pointer to detailed report file - - TGrnAmpt soilInfil; // infil. object for biocell soil layer - double surfaceDepth; // depth of ponded water on surface layer (ft) - double paveDepth; // depth of water in porous pavement layer - double soilMoisture; // moisture content of biocell soil layer - double storageDepth; // depth of water in storage layer (ft) - - // net inflow - outflow from previous time step for each LID layer (ft/s) - double oldFluxRates[MAX_LAYERS]; - - double dryTime; // time since last rainfall (sec) - double oldDrainFlow; // previous drain flow (cfs) - double newDrainFlow; // current drain flow (cfs) - double volTreated; // total volume treated (ft) - double nextRegenDay; // next day when unit regenerated - TWaterBalance waterBalance; // water balance quantites -} TLidUnit; - -//----------------------------------------------------------------------------- -// LID Methods -//----------------------------------------------------------------------------- -void lid_create(int lidCount, int subcatchCount); -void lid_delete(void); - -int lid_readProcParams(char* tok[], int ntoks); -int lid_readGroupParams(char* tok[], int ntoks); - -void lid_validate(void); -void lid_initState(void); -void lid_setOldGroupState(int subcatch); - -double lid_getPervArea(int subcatch); -double lid_getFlowToPerv(int subcatch); -double lid_getDrainFlow(int subcatch, int timePeriod); -double lid_getStoredVolume(int subcatch); -void lid_addDrainLoads(int subcatch, double c[], double tStep); -void lid_addDrainRunon(int subcatch); -void lid_addDrainInflow(int subcatch, double f); -void lid_getRunoff(int subcatch, double tStep); -void lid_writeSummary(void); -void lid_writeWaterBalance(void); - -//----------------------------------------------------------------------------- - -void lidproc_initWaterBalance(TLidUnit *lidUnit, double initVol); - -double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, - double inflow, double evap, double infil, double maxInfil, - double tStep, double* lidEvap, double* lidInfil, double* lidDrain); - -void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, - double ucfRainDepth); - -#endif diff --git a/src/lidproc.c b/src/lidproc.c deleted file mode 100644 index aae0ac9bd..000000000 --- a/src/lidproc.c +++ /dev/null @@ -1,1592 +0,0 @@ -//----------------------------------------------------------------------------- -// lidproc.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -// -// This module computes the hydrologic performance of an LID (Low Impact -// Development) unit at a given point in time. -// -// Update History -// ============== -// Build 5.1.007: -// - Euler integration now applied to all LID types except Vegetative -// Swale which continues to use successive approximation. -// - LID layer flux routines were re-written to more accurately model -// flooded conditions. -// Build 5.1.008: -// - MAX_STATE_VARS replaced with MAX_LAYERS. -// - Optional soil layer added to Porous Pavement LID. -// - Rooftop Disconnection added to types of LIDs. -// - Separate accounting of drain flows added. -// - Indicator for currently wet LIDs added. -// - Detailed reporting procedure fixed. -// - Possibile negative head on Bioretention Cell drain avoided. -// - Bug in computing flow through Green Roof drainage mat fixed. -// Build 5.1.009: -// - Fixed typo in net flux rate for vegetative swale LID. -// Build 5.1.010: -// - New modified version of Green-Ampt used for surface layer infiltration. -// Build 5.1.011: -// - Re-named STOR_INFIL to STOR_EXFIL and StorageInfil to StorageExfil to -// better reflect their meaning. -// - Evaporation rates from sub-surface layers reduced by fraction of -// surface that is pervious (applies to block paver systems) -// - Flux rate routines for LIDs with underdrains modified to produce more -// physically meaningful results. -// - Reporting of detailed results re-written. -// Build 5.1.012: -// - Modified upper limit for soil layer percolation. -// - Modified upper limit on surface infiltration into rain gardens. -// - Modified upper limit on drain flow for LIDs with storage layers. -// - Used re-defined wasDry variable for LID reports to fix duplicate lines. -// Build 5.1.013: -// - Support added for open/closed head levels and multiplier v. head curve -// to control underdrain flow. -// - Support added for regenerating pavement permeability at fixed intervals. -// Build 5.1.014: -// - Fixed failure to initialize all LID layer moisture volumes to 0 before -// computing LID unit performance in lidproc_getOutflow. -// Build 5.2.0: -// - Fixed failure to account for effect of Impervious Surface Fraction on -// pavement permeability for Permeable Pavement LID -// - Fixed units conversion for pavement depth in detailed report file. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "lid.h" -#include "headers.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -#define STOPTOL 0.00328 // integration error tolerance in ft (= 1 mm) -#define MINFLOW 2.3e-8 // flow cutoff for dry conditions (= 0.001 in/hr) - -//----------------------------------------------------------------------------- -// Enumerations -//----------------------------------------------------------------------------- -enum LidLayerTypes { - SURF, // surface layer - SOIL, // soil layer - STOR, // storage layer - PAVE, // pavement layer - DRAIN}; // underdrain system - -enum LidRptVars { - SURF_INFLOW, // inflow to surface layer - TOTAL_EVAP, // evaporation rate from all layers - SURF_INFIL, // infiltration into surface layer - PAVE_PERC, // percolation through pavement layer - SOIL_PERC, // percolation through soil layer - STOR_EXFIL, // exfiltration out of storage layer - SURF_OUTFLOW, // outflow from surface layer - STOR_DRAIN, // outflow from storage layer - SURF_DEPTH, // ponded depth on surface layer - PAVE_DEPTH, // water level in pavement layer - SOIL_MOIST, // moisture content of soil layer - STOR_DEPTH, // water level in storage layer - MAX_RPT_VARS}; - -//----------------------------------------------------------------------------- -// Imported variables -//----------------------------------------------------------------------------- -extern char HasWetLids; // TRUE if any LIDs are wet (declared in runoff.c) - -//----------------------------------------------------------------------------- -// Local Variables -//----------------------------------------------------------------------------- -static TLidUnit* theLidUnit; // ptr. to a subcatchment's LID unit -static TLidProc* theLidProc; // ptr. to a LID process - -static double Tstep; // current time step (sec) -static double EvapRate; // evaporation rate (ft/s) -static double MaxNativeInfil; // native soil infil. rate limit (ft/s) - -static double SurfaceInflow; // precip. + runon to LID unit (ft/s) -static double SurfaceInfil; // infil. rate from surface layer (ft/s) -static double SurfaceEvap; // evap. rate from surface layer (ft/s) -static double SurfaceOutflow; // outflow from surface layer (ft/s) -static double SurfaceVolume; // volume in surface storage (ft) - -static double PaveEvap; // evap. from pavement layer (ft/s) -static double PavePerc; // percolation from pavement layer (ft/s) -static double PaveVolume; // volume stored in pavement layer (ft) - -static double SoilEvap; // evap. from soil layer (ft/s) -static double SoilPerc; // percolation from soil layer (ft/s) -static double SoilVolume; // volume in soil/pavement storage (ft) - -static double StorageInflow; // inflow rate to storage layer (ft/s) -static double StorageExfil; // exfil. rate from storage layer (ft/s) -static double StorageEvap; // evap.rate from storage layer (ft/s) -static double StorageDrain; // underdrain flow rate layer (ft/s) -static double StorageVolume; // volume in storage layer (ft) - -static double Xold[MAX_LAYERS]; // previous moisture level in LID layers - -//----------------------------------------------------------------------------- -// External Functions (declared in lid.h) -//----------------------------------------------------------------------------- -// lidproc_initWaterBalance (called by lid_initState) -// lidproc_getOutflow (called by evalLidUnit in lid.c) -// lidproc_saveResults (called by evalLidUnit in lid.c) - -//----------------------------------------------------------------------------- -// Local Functions -//----------------------------------------------------------------------------- -static void barrelFluxRates(double x[], double f[]); -static void biocellFluxRates(double x[], double f[]); -static void greenRoofFluxRates(double x[], double f[]); -static void pavementFluxRates(double x[], double f[]); -static void trenchFluxRates(double x[], double f[]); -static void swaleFluxRates(double x[], double f[]); -static void roofFluxRates(double x[], double f[]); - -static double getSurfaceOutflowRate(double depth); -static double getSurfaceOverflowRate(double* surfaceDepth); -static double getPavementPermRate(void); -static double getSoilPercRate(double theta); -static double getStorageExfilRate(void); -static double getStorageDrainRate(double storageDepth, double soilTheta, - double paveDepth, double surfaceDepth); -static double getDrainMatOutflow(double depth); -static void getEvapRates(double surfaceVol, double paveVol, - double soilVol, double storageVol, double pervFrac); - -static void updateWaterBalance(TLidUnit *lidUnit, double inflow, - double evap, double infil, double surfFlow, - double drainFlow, double storage); - -static int modpuls_solve(int n, double* x, double* xOld, double* xPrev, - double* xMin, double* xMax, double* xTol, - double* qOld, double* q, double dt, double omega, - void (*derivs)(double*, double*)); - -//============================================================================= - -void lidproc_initWaterBalance(TLidUnit *lidUnit, double initVol) -// -// Purpose: initializes the water balance components of a LID unit. -// Input: lidUnit = a particular LID unit -// initVol = initial water volume stored in the unit (ft) -// Output: none -// -{ - lidUnit->waterBalance.inflow = 0.0; - lidUnit->waterBalance.evap = 0.0; - lidUnit->waterBalance.infil = 0.0; - lidUnit->waterBalance.surfFlow = 0.0; - lidUnit->waterBalance.drainFlow = 0.0; - lidUnit->waterBalance.initVol = initVol; - lidUnit->waterBalance.finalVol = initVol; -} - -//============================================================================= - -double lidproc_getOutflow(TLidUnit* lidUnit, TLidProc* lidProc, double inflow, - double evap, double infil, double maxInfil, - double tStep, double* lidEvap, - double* lidInfil, double* lidDrain) -// -// Purpose: computes runoff outflow from a single LID unit. -// Input: lidUnit = ptr. to specific LID unit being analyzed -// lidProc = ptr. to generic LID process of the LID unit -// inflow = runoff rate captured by LID unit (ft/s) -// evap = potential evaporation rate (ft/s) -// infil = infiltration rate to native soil (ft/s) -// maxInfil = max. infiltration rate to native soil (ft/s) -// tStep = time step (sec) -// Output: lidEvap = evaporation rate for LID unit (ft/s) -// lidInfil = infiltration rate for LID unit (ft/s) -// lidDrain = drain flow for LID unit (ft/s) -// returns surface runoff rate from the LID unit (ft/s) -// -{ - int i; - double x[MAX_LAYERS]; // layer moisture levels - double xOld[MAX_LAYERS]; // work vector - double xPrev[MAX_LAYERS]; // work vector - double xMin[MAX_LAYERS]; // lower limit on moisture levels - double xMax[MAX_LAYERS]; // upper limit on moisture levels - double fOld[MAX_LAYERS]; // previously computed flux rates - double f[MAX_LAYERS]; // newly computed flux rates - - // convergence tolerance on moisture levels (ft, moisture fraction , ft) - double xTol[MAX_LAYERS] = {STOPTOL, STOPTOL, STOPTOL, STOPTOL}; - - double omega = 0.0; // integration time weighting - - //... define a pointer to function that computes flux rates through the LID - void (*fluxRates) (double *, double *) = NULL; - - //... save references to the LID process and LID unit - theLidProc = lidProc; - theLidUnit = lidUnit; - - //... save evap, max. infil. & time step to shared variables - EvapRate = evap; - MaxNativeInfil = maxInfil; - Tstep = tStep; - - //... store current moisture levels in vector x - x[SURF] = theLidUnit->surfaceDepth; - x[SOIL] = theLidUnit->soilMoisture; - x[STOR] = theLidUnit->storageDepth; - x[PAVE] = theLidUnit->paveDepth; - - //... initialize layer moisture volumes, flux rates and moisture limits - SurfaceVolume = 0.0; - PaveVolume = 0.0; - SoilVolume = 0.0; - StorageVolume = 0.0; - SurfaceInflow = inflow; - SurfaceInfil = 0.0; - SurfaceEvap = 0.0; - SurfaceOutflow = 0.0; - PaveEvap = 0.0; - PavePerc = 0.0; - SoilEvap = 0.0; - SoilPerc = 0.0; - StorageInflow = 0.0; - StorageExfil = 0.0; - StorageEvap = 0.0; - StorageDrain = 0.0; - for (i = 0; i < MAX_LAYERS; i++) - { - f[i] = 0.0; - fOld[i] = theLidUnit->oldFluxRates[i]; - xMin[i] = 0.0; - xMax[i] = BIG; - Xold[i] = x[i]; - } - - //... find Green-Ampt infiltration from surface layer - if ( theLidProc->lidType == POROUS_PAVEMENT ) SurfaceInfil = 0.0; - else if ( theLidUnit->soilInfil.Ks > 0.0 ) - { - SurfaceInfil = - grnampt_getInfil(&theLidUnit->soilInfil, Tstep, - SurfaceInflow, theLidUnit->surfaceDepth, - MOD_GREEN_AMPT); - } - else SurfaceInfil = infil; - - //... set moisture limits for soil & storage layers - if ( theLidProc->soil.thickness > 0.0 ) - { - xMin[SOIL] = theLidProc->soil.wiltPoint; - xMax[SOIL] = theLidProc->soil.porosity; - } - if ( theLidProc->pavement.thickness > 0.0 ) - { - xMax[PAVE] = theLidProc->pavement.thickness; - } - if ( theLidProc->storage.thickness > 0.0 ) - { - xMax[STOR] = theLidProc->storage.thickness; - } - if ( theLidProc->lidType == GREEN_ROOF ) - { - xMax[STOR] = theLidProc->drainMat.thickness; - } - - //... determine which flux rate function to use - switch (theLidProc->lidType) - { - case BIO_CELL: - case RAIN_GARDEN: fluxRates = &biocellFluxRates; break; - case GREEN_ROOF: fluxRates = &greenRoofFluxRates; break; - case INFIL_TRENCH: fluxRates = &trenchFluxRates; break; - case POROUS_PAVEMENT: fluxRates = &pavementFluxRates; break; - case RAIN_BARREL: fluxRates = &barrelFluxRates; break; - case ROOF_DISCON: fluxRates = &roofFluxRates; break; - case VEG_SWALE: fluxRates = &swaleFluxRates; - omega = 0.5; - break; - default: return 0.0; - } - - //... update moisture levels and flux rates over the time step - i = modpuls_solve(MAX_LAYERS, x, xOld, xPrev, xMin, xMax, xTol, - fOld, f, tStep, omega, fluxRates); - -/** For debugging only ******************************************** - if (i == 0) - { - fprintf(Frpt.file, - "\n WARNING 09: integration failed to converge at %s %s", - theDate, theTime); - fprintf(Frpt.file, - "\n for LID %s placed in subcatchment %s.", - theLidProc->ID, theSubcatch->ID); - } -*******************************************************************/ - - //... add any surface overflow to surface outflow - if ( theLidProc->surface.canOverflow || theLidUnit->fullWidth == 0.0 ) - { - SurfaceOutflow += getSurfaceOverflowRate(&x[SURF]); - } - - //... save updated results - theLidUnit->surfaceDepth = x[SURF]; - theLidUnit->paveDepth = x[PAVE]; - theLidUnit->soilMoisture = x[SOIL]; - theLidUnit->storageDepth = x[STOR]; - for (i = 0; i < MAX_LAYERS; i++) theLidUnit->oldFluxRates[i] = f[i]; - - //... assign values to LID unit evaporation, infiltration & drain flow - *lidEvap = SurfaceEvap + PaveEvap + SoilEvap + StorageEvap; - *lidInfil = StorageExfil; - *lidDrain = StorageDrain; - - //... return surface outflow (per unit area) from unit - return SurfaceOutflow; -} - -//============================================================================= - -void lidproc_saveResults(TLidUnit* lidUnit, double ucfRainfall, double ucfRainDepth) -// -// Purpose: updates the mass balance for an LID unit and saves -// current flux rates to the LID report file. -// Input: lidUnit = ptr. to LID unit -// ucfRainfall = units conversion factor for rainfall rate -// ucfDepth = units conversion factor for rainfall depth -// Output: none -// -{ - double ucf; // units conversion factor - double totalEvap; // total evaporation rate (ft/s) - double totalVolume; // total volume stored in LID (ft) - double rptVars[MAX_RPT_VARS]; // array of reporting variables - int isDry = FALSE; // true if current state of LID is dry - char timeStamp[TIME_STAMP_SIZE + 1]; // date/time stamp - double elapsedHrs; // elapsed hours - - //... find total evap. rate and stored volume - totalEvap = SurfaceEvap + PaveEvap + SoilEvap + StorageEvap; - totalVolume = SurfaceVolume + PaveVolume + SoilVolume + StorageVolume; - - //... update mass balance totals - updateWaterBalance(theLidUnit, SurfaceInflow, totalEvap, StorageExfil, - SurfaceOutflow, StorageDrain, totalVolume); - - //... check if dry-weather conditions hold - if ( SurfaceInflow < MINFLOW && - SurfaceOutflow < MINFLOW && - StorageDrain < MINFLOW && - StorageExfil < MINFLOW && - totalEvap < MINFLOW - ) isDry = TRUE; - - //... update status of HasWetLids - if ( !isDry ) HasWetLids = TRUE; - - //... write results to LID report file - if ( lidUnit->rptFile ) - { - //... convert rate results to original units (in/hr or mm/hr) - ucf = ucfRainfall; - rptVars[SURF_INFLOW] = SurfaceInflow*ucf; - rptVars[TOTAL_EVAP] = totalEvap*ucf; - rptVars[SURF_INFIL] = SurfaceInfil*ucf; - rptVars[PAVE_PERC] = PavePerc*ucf; - rptVars[SOIL_PERC] = SoilPerc*ucf; - rptVars[STOR_EXFIL] = StorageExfil*ucf; - rptVars[SURF_OUTFLOW] = SurfaceOutflow*ucf; - rptVars[STOR_DRAIN] = StorageDrain*ucf; - - //... convert storage results to original units (in or mm) - ucf = ucfRainDepth; - rptVars[SURF_DEPTH] = theLidUnit->surfaceDepth*ucf; - rptVars[PAVE_DEPTH] = theLidUnit->paveDepth*ucf; - rptVars[SOIL_MOIST] = theLidUnit->soilMoisture; - rptVars[STOR_DEPTH] = theLidUnit->storageDepth*ucf; - - //... if the current LID state is wet but the previous state was dry - // for more than one period then write the saved previous results - // to the report file thus marking the end of a dry period - if ( !isDry && theLidUnit->rptFile->wasDry > 1) - { - fprintf(theLidUnit->rptFile->file, "%s", - theLidUnit->rptFile->results); - } - - //... write the current results to a string which is saved between - // reporting periods - elapsedHrs = NewRunoffTime / 1000.0 / 3600.0; - datetime_getTimeStamp( - M_D_Y, getDateTime(NewRunoffTime), TIME_STAMP_SIZE, timeStamp); - snprintf(theLidUnit->rptFile->results, sizeof(theLidUnit->rptFile->results), - "\n%20s\t %8.3f\t %8.3f\t %8.4f\t %8.3f\t %8.3f\t %8.3f\t %8.3f\t" - "%8.3f\t %8.3f\t %8.3f\t %8.3f\t %8.3f\t %8.3f", - timeStamp, elapsedHrs, rptVars[0], rptVars[1], rptVars[2], - rptVars[3], rptVars[4], rptVars[5], rptVars[6], rptVars[7], - rptVars[8], rptVars[9], rptVars[10], rptVars[11]); - - //... if the current LID state is dry - if ( isDry ) - { - //... if the previous state was wet then write the current - // results to file marking the start of a dry period - if ( theLidUnit->rptFile->wasDry == 0 ) - { - fprintf(theLidUnit->rptFile->file, "%s", - theLidUnit->rptFile->results); - } - - //... increment the number of successive dry periods - theLidUnit->rptFile->wasDry++; - } - - //... if the current LID state is wet - else - { - //... write the current results to the report file - fprintf(theLidUnit->rptFile->file, "%s", - theLidUnit->rptFile->results); - - //... re-set the number of successive dry periods to 0 - theLidUnit->rptFile->wasDry = 0; - } - } -} - -//============================================================================= - -void roofFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates for roof disconnection. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - double surfaceDepth = x[SURF]; - - getEvapRates(surfaceDepth, 0.0, 0.0, 0.0, 1.0); - SurfaceVolume = surfaceDepth; - SurfaceInfil = 0.0; - if ( theLidProc->surface.alpha > 0.0 ) - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - else getSurfaceOverflowRate(&surfaceDepth); - StorageDrain = MIN(theLidProc->drain.coeff/UCF(RAINFALL), SurfaceOutflow); - SurfaceOutflow -= StorageDrain; - f[SURF] = (SurfaceInflow - SurfaceEvap - StorageDrain - SurfaceOutflow); -} - -//============================================================================= - -void greenRoofFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates from the layers of a green roof. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - // Moisture level variables - double surfaceDepth; - double soilTheta; - double storageDepth; - - // Intermediate variables - double availVolume; - double maxRate; - - // Green roof properties - double soilThickness = theLidProc->soil.thickness; - double storageThickness = theLidProc->storage.thickness; - double soilPorosity = theLidProc->soil.porosity; - double storageVoidFrac = theLidProc->storage.voidFrac; - double soilFieldCap = theLidProc->soil.fieldCap; - double soilWiltPoint = theLidProc->soil.wiltPoint; - - //... retrieve moisture levels from input vector - surfaceDepth = x[SURF]; - soilTheta = x[SOIL]; - storageDepth = x[STOR]; - - //... convert moisture levels to volumes - SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; - SoilVolume = soilTheta * soilThickness; - StorageVolume = storageDepth * storageVoidFrac; - - //... get ET rates - availVolume = SoilVolume - soilWiltPoint * soilThickness; - getEvapRates(SurfaceVolume, 0.0, availVolume, StorageVolume, 1.0); - if ( soilTheta >= soilPorosity ) StorageEvap = 0.0; - - //... soil layer perc rate - SoilPerc = getSoilPercRate(soilTheta); - - //... limit perc rate by available water - availVolume = (soilTheta - soilFieldCap) * soilThickness; - maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; - SoilPerc = MIN(SoilPerc, maxRate); - SoilPerc = MAX(SoilPerc, 0.0); - - //... storage (drain mat) outflow rate - StorageExfil = 0.0; - StorageDrain = getDrainMatOutflow(storageDepth); - - //... unit is full - if ( soilTheta >= soilPorosity && storageDepth >= storageThickness ) - { - //... outflow from both layers equals limiting rate - maxRate = MIN(SoilPerc, StorageDrain); - SoilPerc = maxRate; - StorageDrain = maxRate; - - //... adjust inflow rate to soil layer - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... unit not full - else - { - //... limit drainmat outflow by available storage volume - maxRate = storageDepth * storageVoidFrac / Tstep - StorageEvap; - if ( storageDepth >= storageThickness ) maxRate += SoilPerc; - maxRate = MAX(maxRate, 0.0); - StorageDrain = MIN(StorageDrain, maxRate); - - //... limit soil perc inflow by unused storage volume - maxRate = (storageThickness - storageDepth) * storageVoidFrac / Tstep + - StorageDrain + StorageEvap; - SoilPerc = MIN(SoilPerc, maxRate); - - //... adjust surface infil. so soil porosity not exceeded - maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + - SoilPerc + SoilEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - // ... find surface outflow rate - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - - // ... compute overall layer flux rates - f[SURF] = (SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow) / - theLidProc->surface.voidFrac; - f[SOIL] = (SurfaceInfil - SoilEvap - SoilPerc) / - theLidProc->soil.thickness; - f[STOR] = (SoilPerc - StorageEvap - StorageDrain) / - theLidProc->storage.voidFrac; -} - -//============================================================================= - -void biocellFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates from the layers of a bio-retention cell LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - // Moisture level variables - double surfaceDepth; - double soilTheta; - double storageDepth; - - // Intermediate variables - double availVolume; - double maxRate; - - // LID layer properties - double soilThickness = theLidProc->soil.thickness; - double soilPorosity = theLidProc->soil.porosity; - double soilFieldCap = theLidProc->soil.fieldCap; - double soilWiltPoint = theLidProc->soil.wiltPoint; - double storageThickness = theLidProc->storage.thickness; - double storageVoidFrac = theLidProc->storage.voidFrac; - - //... retrieve moisture levels from input vector - surfaceDepth = x[SURF]; - soilTheta = x[SOIL]; - storageDepth = x[STOR]; - - //... convert moisture levels to volumes - SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; - SoilVolume = soilTheta * soilThickness; - StorageVolume = storageDepth * storageVoidFrac; - - //... get ET rates - availVolume = SoilVolume - soilWiltPoint * soilThickness; - getEvapRates(SurfaceVolume, 0.0, availVolume, StorageVolume, 1.0); - if ( soilTheta >= soilPorosity ) StorageEvap = 0.0; - - //... soil layer perc rate - SoilPerc = getSoilPercRate(soilTheta); - - //... limit perc rate by available water - availVolume = (soilTheta - soilFieldCap) * soilThickness; - maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; - SoilPerc = MIN(SoilPerc, maxRate); - SoilPerc = MAX(SoilPerc, 0.0); - - //... exfiltration rate out of storage layer - StorageExfil = getStorageExfilRate(); - - //... underdrain flow rate - StorageDrain = 0.0; - if ( theLidProc->drain.coeff > 0.0 ) - { - StorageDrain = getStorageDrainRate(storageDepth, soilTheta, 0.0, - surfaceDepth); - } - - //... special case of no storage layer present - if ( storageThickness == 0.0 ) - { - StorageEvap = 0.0; - maxRate = MIN(SoilPerc, StorageExfil); - SoilPerc = maxRate; - StorageExfil = maxRate; - - //... limit surface infil. by unused soil volume - maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + - SoilPerc + SoilEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... storage & soil layers are full - else if ( soilTheta >= soilPorosity && storageDepth >= storageThickness ) - { - //... limiting rate is smaller of soil perc and storage outflow - maxRate = StorageExfil + StorageDrain; - if ( SoilPerc < maxRate ) - { - maxRate = SoilPerc; - if ( maxRate > StorageExfil ) StorageDrain = maxRate - StorageExfil; - else - { - StorageExfil = maxRate; - StorageDrain = 0.0; - } - } - else SoilPerc = maxRate; - - //... apply limiting rate to surface infil. - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... either layer not full - else if ( storageThickness > 0.0 ) - { - //... limit storage exfiltration by available storage volume - maxRate = SoilPerc - StorageEvap + storageDepth*storageVoidFrac/Tstep; - StorageExfil = MIN(StorageExfil, maxRate); - StorageExfil = MAX(StorageExfil, 0.0); - - //... limit underdrain flow by volume above drain offset - if ( StorageDrain > 0.0 ) - { - maxRate = -StorageExfil - StorageEvap; - if ( storageDepth >= storageThickness) maxRate += SoilPerc; - if ( theLidProc->drain.offset <= storageDepth ) - { - maxRate += (storageDepth - theLidProc->drain.offset) * - storageVoidFrac/Tstep; - } - maxRate = MAX(maxRate, 0.0); - StorageDrain = MIN(StorageDrain, maxRate); - } - - //... limit soil perc by unused storage volume - maxRate = StorageExfil + StorageDrain + StorageEvap + - (storageThickness - storageDepth) * - storageVoidFrac/Tstep; - SoilPerc = MIN(SoilPerc, maxRate); - - //... limit surface infil. by unused soil volume - maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + - SoilPerc + SoilEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... find surface layer outflow rate - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - - //... compute overall layer flux rates - f[SURF] = (SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow) / - theLidProc->surface.voidFrac; - f[SOIL] = (SurfaceInfil - SoilEvap - SoilPerc) / - theLidProc->soil.thickness; - if ( storageThickness == 0.0 ) f[STOR] = 0.0; - else f[STOR] = (SoilPerc - StorageEvap - StorageExfil - StorageDrain) / - theLidProc->storage.voidFrac; -} - -//============================================================================= - -void trenchFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates from the layers of an infiltration trench LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - // Moisture level variables - double surfaceDepth; - double storageDepth; - - // Intermediate variables - double availVolume; - double maxRate; - - // Storage layer properties - double storageThickness = theLidProc->storage.thickness; - double storageVoidFrac = theLidProc->storage.voidFrac; - - //... retrieve moisture levels from input vector - surfaceDepth = x[SURF]; - storageDepth = x[STOR]; - - //... convert moisture levels to volumes - SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; - SoilVolume = 0.0; - StorageVolume = storageDepth * storageVoidFrac; - - //... get ET rates - availVolume = (storageThickness - storageDepth) * storageVoidFrac; - getEvapRates(SurfaceVolume, 0.0, 0.0, StorageVolume, 1.0); - - //... no storage evap if surface ponded - if ( surfaceDepth > 0.0 ) StorageEvap = 0.0; - - //... nominal storage inflow - StorageInflow = SurfaceInflow + SurfaceVolume / Tstep; - - //... exfiltration rate out of storage layer - StorageExfil = getStorageExfilRate(); - - //... underdrain flow rate - StorageDrain = 0.0; - if ( theLidProc->drain.coeff > 0.0 ) - { - StorageDrain = getStorageDrainRate(storageDepth, 0.0, 0.0, surfaceDepth); - } - - //... limit storage exfiltration by available storage volume - maxRate = StorageInflow - StorageEvap + storageDepth*storageVoidFrac/Tstep; - StorageExfil = MIN(StorageExfil, maxRate); - StorageExfil = MAX(StorageExfil, 0.0); - - //... limit underdrain flow by volume above drain offset - if ( StorageDrain > 0.0 ) - { - maxRate = -StorageExfil - StorageEvap; - if (storageDepth >= storageThickness ) maxRate += StorageInflow; - if ( theLidProc->drain.offset <= storageDepth ) - { - maxRate += (storageDepth - theLidProc->drain.offset) * - storageVoidFrac/Tstep; - } - maxRate = MAX(maxRate, 0.0); - StorageDrain = MIN(StorageDrain, maxRate); - } - - //... limit storage inflow to not exceed storage layer capacity - maxRate = (storageThickness - storageDepth)*storageVoidFrac/Tstep + - StorageExfil + StorageEvap + StorageDrain; - StorageInflow = MIN(StorageInflow, maxRate); - - //... equate surface infil to storage inflow - SurfaceInfil = StorageInflow; - - //... find surface outflow rate - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - - // ... find net fluxes for each layer - f[SURF] = SurfaceInflow - SurfaceEvap - StorageInflow - SurfaceOutflow / - theLidProc->surface.voidFrac;; - f[STOR] = (StorageInflow - StorageEvap - StorageExfil - StorageDrain) / - theLidProc->storage.voidFrac; - f[SOIL] = 0.0; -} - -//============================================================================= - -void pavementFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates for the layers of a porous pavement LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - //... Moisture level variables - double surfaceDepth; - double paveDepth; - double soilTheta; - double storageDepth; - - //... Intermediate variables - double pervFrac = (1.0 - theLidProc->pavement.impervFrac); - double storageInflow; // inflow rate to storage layer (ft/s) - double availVolume; - double maxRate; - - //... LID layer properties - double paveVoidFrac = theLidProc->pavement.voidFrac * pervFrac; - double paveThickness = theLidProc->pavement.thickness; - double soilThickness = theLidProc->soil.thickness; - double soilPorosity = theLidProc->soil.porosity; - double soilFieldCap = theLidProc->soil.fieldCap; - double soilWiltPoint = theLidProc->soil.wiltPoint; - double storageThickness = theLidProc->storage.thickness; - double storageVoidFrac = theLidProc->storage.voidFrac; - - //... retrieve moisture levels from input vector - surfaceDepth = x[SURF]; - paveDepth = x[PAVE]; - soilTheta = x[SOIL]; - storageDepth = x[STOR]; - - //... convert moisture levels to volumes - SurfaceVolume = surfaceDepth * theLidProc->surface.voidFrac; - PaveVolume = paveDepth * paveVoidFrac; - SoilVolume = soilTheta * soilThickness; - StorageVolume = storageDepth * storageVoidFrac; - - //... get ET rates - availVolume = SoilVolume - soilWiltPoint * soilThickness; - getEvapRates(SurfaceVolume, PaveVolume, availVolume, StorageVolume, - pervFrac); - - //... no storage evap if soil or pavement layer saturated - if ( paveDepth >= paveThickness || - ( soilThickness > 0.0 && soilTheta >= soilPorosity ) - ) StorageEvap = 0.0; - - //... find nominal rate of surface infiltration into pavement layer - SurfaceInfil = SurfaceInflow + (SurfaceVolume / Tstep); - - //... find perc rate out of pavement layer - PavePerc = getPavementPermRate() * pervFrac; - - //... surface infiltration can't exceed pavement permeability - SurfaceInfil = MIN(SurfaceInfil, PavePerc); - - //... limit pavement perc by available water - maxRate = PaveVolume/Tstep + SurfaceInfil - PaveEvap; - maxRate = MAX(maxRate, 0.0); - PavePerc = MIN(PavePerc, maxRate); - - //... find soil layer perc rate - if ( soilThickness > 0.0 ) - { - SoilPerc = getSoilPercRate(soilTheta); - availVolume = (soilTheta - soilFieldCap) * soilThickness; - maxRate = MAX(availVolume, 0.0) / Tstep - SoilEvap; - SoilPerc = MIN(SoilPerc, maxRate); - SoilPerc = MAX(SoilPerc, 0.0); - } - else SoilPerc = PavePerc; - - //... exfiltration rate out of storage layer - StorageExfil = getStorageExfilRate(); - - //... underdrain flow rate - StorageDrain = 0.0; - if ( theLidProc->drain.coeff > 0.0 ) - { - StorageDrain = getStorageDrainRate(storageDepth, soilTheta, paveDepth, - surfaceDepth); - } - - //... check for adjacent saturated layers - - //... no soil layer, pavement & storage layers are full - if ( soilThickness == 0.0 && - storageDepth >= storageThickness && - paveDepth >= paveThickness ) - { - //... pavement outflow can't exceed storage outflow - maxRate = StorageEvap + StorageDrain + StorageExfil; - if ( PavePerc > maxRate ) PavePerc = maxRate; - - //... storage outflow can't exceed pavement outflow - else - { - //... use up available exfiltration capacity first - StorageExfil = MIN(StorageExfil, PavePerc); - StorageDrain = PavePerc - StorageExfil; - } - - //... set soil perc to pavement perc - SoilPerc = PavePerc; - - //... limit surface infil. by pavement perc - SurfaceInfil = MIN(SurfaceInfil, PavePerc); - } - - //... pavement, soil & storage layers are full - else if ( soilThickness > 0 && - storageDepth >= storageThickness && - soilTheta >= soilPorosity && - paveDepth >= paveThickness ) - { - //... find which layer has limiting flux rate - maxRate = StorageExfil + StorageDrain; - if ( SoilPerc < maxRate) maxRate = SoilPerc; - else maxRate = MIN(maxRate, PavePerc); - - //... use up available storage exfiltration capacity first - if ( maxRate > StorageExfil ) StorageDrain = maxRate - StorageExfil; - else - { - StorageExfil = maxRate; - StorageDrain = 0.0; - } - SoilPerc = maxRate; - PavePerc = maxRate; - - //... limit surface infil. by pavement perc - SurfaceInfil = MIN(SurfaceInfil, PavePerc); - } - - //... storage & soil layers are full - else if ( soilThickness > 0.0 && - storageDepth >= storageThickness && - soilTheta >= soilPorosity ) - { - //... soil perc can't exceed storage outflow - maxRate = StorageDrain + StorageExfil; - if ( SoilPerc > maxRate ) SoilPerc = maxRate; - - //... storage outflow can't exceed soil perc - else - { - //... use up available exfiltration capacity first - StorageExfil = MIN(StorageExfil, SoilPerc); - StorageDrain = SoilPerc - StorageExfil; - } - - //... limit surface infil. by available pavement volume - availVolume = (paveThickness - paveDepth) * paveVoidFrac; - maxRate = availVolume / Tstep + PavePerc + PaveEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... soil and pavement layers are full - else if ( soilThickness > 0.0 && - paveDepth >= paveThickness && - soilTheta >= soilPorosity ) - { - PavePerc = MIN(PavePerc, SoilPerc); - SoilPerc = PavePerc; - SurfaceInfil = MIN(SurfaceInfil,PavePerc); - } - - //... no adjoining layers are full - else - { - //... limit storage exfiltration by available storage volume - // (if no soil layer, SoilPerc is same as PavePerc) - maxRate = SoilPerc - StorageEvap + StorageVolume / Tstep; - maxRate = MAX(0.0, maxRate); - StorageExfil = MIN(StorageExfil, maxRate); - - //... limit underdrain flow by volume above drain offset - if ( StorageDrain > 0.0 ) - { - maxRate = -StorageExfil - StorageEvap; - if (storageDepth >= storageThickness ) maxRate += SoilPerc; - if ( theLidProc->drain.offset <= storageDepth ) - { - maxRate += (storageDepth - theLidProc->drain.offset) * - storageVoidFrac/Tstep; - } - maxRate = MAX(maxRate, 0.0); - StorageDrain = MIN(StorageDrain, maxRate); - } - - //... limit soil & pavement outflow by unused storage volume - availVolume = (storageThickness - storageDepth) * storageVoidFrac; - maxRate = availVolume/Tstep + StorageEvap + StorageDrain + StorageExfil; - maxRate = MAX(maxRate, 0.0); - if ( soilThickness > 0.0 ) - { - SoilPerc = MIN(SoilPerc, maxRate); - maxRate = (soilPorosity - soilTheta) * soilThickness / Tstep + - SoilPerc; - } - PavePerc = MIN(PavePerc, maxRate); - - //... limit surface infil. by available pavement volume - availVolume = (paveThickness - paveDepth) * paveVoidFrac; - maxRate = availVolume / Tstep + PavePerc + PaveEvap; - SurfaceInfil = MIN(SurfaceInfil, maxRate); - } - - //... surface outflow - SurfaceOutflow = getSurfaceOutflowRate(surfaceDepth); - - //... compute overall layer flux rates - f[SURF] = SurfaceInflow - SurfaceEvap - SurfaceInfil - SurfaceOutflow; - f[PAVE] = (SurfaceInfil - PaveEvap - PavePerc) / paveVoidFrac; - if ( theLidProc->soil.thickness > 0.0) - { - f[SOIL] = (PavePerc - SoilEvap - SoilPerc) / soilThickness; - storageInflow = SoilPerc; - } - else - { - f[SOIL] = 0.0; - storageInflow = PavePerc; - SoilPerc = 0.0; - } - f[STOR] = (storageInflow - StorageEvap - StorageExfil - StorageDrain) / - storageVoidFrac; -} - -//============================================================================= - -void swaleFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates from a vegetative swale LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - double depth; // depth of surface water in swale (ft) - double topWidth; // top width of full swale (ft) - double botWidth; // bottom width of swale (ft) - double length; // length of swale (ft) - double surfInflow; // inflow rate to swale (cfs) - double surfWidth; // top width at current water depth (ft) - double surfArea; // surface area of current water depth (ft2) - double flowArea; // x-section flow area (ft2) - double lidArea; // surface area of full swale (ft2) - double hydRadius; // hydraulic radius for current depth (ft) - double slope; // slope of swale side wall (run/rise) - double volume; // swale volume at current water depth (ft3) - double dVdT; // change in volume w.r.t. time (cfs) - double dStore; // depression storage depth (ft) - double xDepth; // depth above depression storage (ft) - - //... retrieve state variable from work vector - depth = x[SURF]; - depth = MIN(depth, theLidProc->surface.thickness); - - //... depression storage depth - dStore = 0.0; - - //... get swale's bottom width - // (0.5 ft minimum to avoid numerical problems) - slope = theLidProc->surface.sideSlope; - topWidth = theLidUnit->fullWidth; - topWidth = MAX(topWidth, 0.5); - botWidth = topWidth - 2.0 * slope * theLidProc->surface.thickness; - if ( botWidth < 0.5 ) - { - botWidth = 0.5; - slope = 0.5 * (topWidth - 0.5) / theLidProc->surface.thickness; - } - - //... swale's length - lidArea = theLidUnit->area; - length = lidArea / topWidth; - - //... top width, surface area and flow area of current ponded depth - surfWidth = botWidth + 2.0 * slope * depth; - surfArea = length * surfWidth; - flowArea = (depth * (botWidth + slope * depth)) * - theLidProc->surface.voidFrac; - - //... wet volume and effective depth - volume = length * flowArea; - - //... surface inflow into swale (cfs) - surfInflow = SurfaceInflow * lidArea; - - //... ET rate in cfs - SurfaceEvap = EvapRate * surfArea; - SurfaceEvap = MIN(SurfaceEvap, volume/Tstep); - - //... infiltration rate to native soil in cfs - StorageExfil = SurfaceInfil * surfArea; - - //... no surface outflow if depth below depression storage - xDepth = depth - dStore; - if ( xDepth <= ZERO ) SurfaceOutflow = 0.0; - - //... otherwise compute a surface outflow - else - { - //... modify flow area to remove depression storage, - flowArea -= (dStore * (botWidth + slope * dStore)) * - theLidProc->surface.voidFrac; - if ( flowArea < ZERO ) SurfaceOutflow = 0.0; - else - { - //... compute hydraulic radius - botWidth = botWidth + 2.0 * dStore * slope; - hydRadius = botWidth + 2.0 * xDepth * sqrt(1.0 + slope*slope); - hydRadius = flowArea / hydRadius; - - //... use Manning Eqn. to find outflow rate in cfs - SurfaceOutflow = theLidProc->surface.alpha * flowArea * - pow(hydRadius, 2./3.); - } - } - - //... net flux rate (dV/dt) in cfs - dVdT = surfInflow - SurfaceEvap - StorageExfil - SurfaceOutflow; - - //... when full, any net positive inflow becomes spillage - if ( depth == theLidProc->surface.thickness && dVdT > 0.0 ) - { - SurfaceOutflow += dVdT; - dVdT = 0.0; - } - - //... convert flux rates to ft/s - SurfaceEvap /= lidArea; - StorageExfil /= lidArea; - SurfaceOutflow /= lidArea; - f[SURF] = dVdT / surfArea; - f[SOIL] = 0.0; - f[STOR] = 0.0; - - //... assign values to layer volumes - SurfaceVolume = volume / lidArea; - SoilVolume = 0.0; - StorageVolume = 0.0; -} - -//============================================================================= - -void barrelFluxRates(double x[], double f[]) -// -// Purpose: computes flux rates for a rain barrel LID. -// Input: x = vector of storage levels -// Output: f = vector of flux rates -// -{ - double storageDepth = x[STOR]; - double head; - double maxValue; - - //... assign values to layer volumes - SurfaceVolume = 0.0; - SoilVolume = 0.0; - StorageVolume = storageDepth; - - //... initialize flows - SurfaceInfil = 0.0; - SurfaceOutflow = 0.0; - StorageDrain = 0.0; - - //... compute outflow if time since last rain exceeds drain delay - // (dryTime is updated in lid.evalLidUnit at each time step) - if ( theLidProc->drain.delay == 0.0 || - theLidUnit->dryTime >= theLidProc->drain.delay ) - { - head = storageDepth - theLidProc->drain.offset; - if ( head > 0.0 ) - { - StorageDrain = getStorageDrainRate(storageDepth, 0.0, 0.0, 0.0); - maxValue = (head/Tstep); - StorageDrain = MIN(StorageDrain, maxValue); - } - } - - //... limit inflow to available storage - StorageInflow = SurfaceInflow; - maxValue = (theLidProc->storage.thickness - storageDepth) / Tstep + - StorageDrain; - StorageInflow = MIN(StorageInflow, maxValue); - SurfaceInfil = StorageInflow; - - //... assign values to layer flux rates - f[SURF] = SurfaceInflow - StorageInflow; - f[STOR] = StorageInflow - StorageDrain; - f[SOIL] = 0.0; -} - -//============================================================================= - -double getSurfaceOutflowRate(double depth) -// -// Purpose: computes outflow rate from a LID's surface layer. -// Input: depth = depth of ponded water on surface layer (ft) -// Output: returns outflow from surface layer (ft/s) -// -// Note: this function should not be applied to swales or rain barrels. -// -{ - double delta; - double outflow; - - //... no outflow if ponded depth below storage depth - delta = depth - theLidProc->surface.thickness; - if ( delta < 0.0 ) return 0.0; - - //... compute outflow from overland flow Manning equation - outflow = theLidProc->surface.alpha * pow(delta, 5.0/3.0) * - theLidUnit->fullWidth / theLidUnit->area; - outflow = MIN(outflow, delta / Tstep); - return outflow; -} - -//============================================================================= - -double getPavementPermRate() -// -// Purpose: computes reduced permeability of a pavement layer due to -// clogging. -// Input: none -// Output: returns the reduced permeability of the pavement layer (ft/s). -// -{ - double permReduction = 0.0; - double clogFactor= theLidProc->pavement.clogFactor; - double regenDays = theLidProc->pavement.regenDays; - - // ... find permeability reduction due to clogging - if ( clogFactor > 0.0 ) - { - // ... see if permeability regeneration has occurred - // (regeneration is assumed to reduce the total - // volumetric loading that the pavement has received) - if ( regenDays > 0.0 ) - { - if ( OldRunoffTime / 1000.0 / SECperDAY >= theLidUnit->nextRegenDay ) - { - // ... reduce total volume treated by degree of regeneration - theLidUnit->volTreated *= - (1.0 - theLidProc->pavement.regenDegree); - - // ... update next day that regenration occurs - theLidUnit->nextRegenDay += regenDays; - } - } - - // ... find permeabiity reduction factor - permReduction = theLidUnit->volTreated / clogFactor; - permReduction = MIN(permReduction, 1.0); - } - - // ... return the effective pavement permeability - return theLidProc->pavement.kSat * (1.0 - permReduction); -} - -//============================================================================= - -double getSoilPercRate(double theta) -// -// Purpose: computes percolation rate of water through a LID's soil layer. -// Input: theta = moisture content (fraction) -// Output: returns percolation rate within soil layer (ft/s) -// -{ - double delta; // moisture deficit - - // ... no percolation if soil moisture <= field capacity - if ( theta <= theLidProc->soil.fieldCap ) return 0.0; - - // ... perc rate = unsaturated hydraulic conductivity - delta = theLidProc->soil.porosity - theta; - return theLidProc->soil.kSat * exp(-delta * theLidProc->soil.kSlope); - -} - -//============================================================================= - -double getStorageExfilRate() -// -// Purpose: computes exfiltration rate from storage zone into -// native soil beneath a LID. -// Input: depth = depth of water storage zone (ft) -// Output: returns infiltration rate (ft/s) -// -{ - double infil = 0.0; - double clogFactor = 0.0; - - if ( theLidProc->storage.kSat == 0.0 ) return 0.0; - if ( MaxNativeInfil == 0.0 ) return 0.0; - - //... reduction due to clogging - clogFactor = theLidProc->storage.clogFactor; - if ( clogFactor > 0.0 ) - { - clogFactor = theLidUnit->waterBalance.inflow / clogFactor; - clogFactor = MIN(clogFactor, 1.0); - } - - //... infiltration rate = storage Ksat reduced by any clogging - infil = theLidProc->storage.kSat * (1.0 - clogFactor); - - //... limit infiltration rate by any groundwater-imposed limit - return MIN(infil, MaxNativeInfil); -} - -//============================================================================= - -double getStorageDrainRate(double storageDepth, double soilTheta, - double paveDepth, double surfaceDepth) -// -// Purpose: computes underdrain flow rate in a LID's storage layer. -// Input: storageDepth = depth of water in storage layer (ft) -// soilTheta = moisture content of soil layer -// paveDepth = effective depth of water in pavement layer (ft) -// surfaceDepth = depth of ponded water on surface layer (ft) -// Output: returns flow in underdrain (ft/s) -// -// Note: drain eqn. is evaluated in user's units. -// Note: head on drain is water depth in storage layer plus the -// layers above it (soil, pavement, and surface in that order) -// minus the drain outlet offset. -{ - int curve = theLidProc->drain.qCurve; - double head = storageDepth; - double outflow = 0.0; - double paveThickness = theLidProc->pavement.thickness; - double soilThickness = theLidProc->soil.thickness; - double soilPorosity = theLidProc->soil.porosity; - double soilFieldCap = theLidProc->soil.fieldCap; - double storageThickness = theLidProc->storage.thickness; - - // --- storage layer is full - if ( storageDepth >= storageThickness ) - { - // --- a soil layer exists - if ( soilThickness > 0.0 ) - { - // --- increase head by fraction of soil layer saturated - if ( soilTheta > soilFieldCap ) - { - head += (soilTheta - soilFieldCap) / - (soilPorosity - soilFieldCap) * soilThickness; - - // --- soil layer is saturated, increase head by water - // depth in layer above it - if ( soilTheta >= soilPorosity ) - { - if ( paveThickness > 0.0 ) head += paveDepth; - else head += surfaceDepth; - } - } - } - - // --- no soil layer so increase head by water level in pavement - // layer and possibly surface layer - if ( paveThickness > 0.0 ) - { - head += paveDepth; - if ( paveDepth >= paveThickness ) head += surfaceDepth; - } - } - - // --- no outflow if: - // a) no prior outflow and head below open threshold - // b) prior outflow and head below closed threshold - if ( theLidUnit->oldDrainFlow == 0.0 && - head <= theLidProc->drain.hOpen ) return 0.0; - if ( theLidUnit->oldDrainFlow > 0.0 && - head <= theLidProc->drain.hClose ) return 0.0; - - // --- make head relative to drain offset - head -= theLidProc->drain.offset; - - // --- compute drain outflow from underdrain flow equation in user units - // (head in inches or mm, flow rate in in/hr or mm/hr) - if ( head > ZERO ) - { - // --- convert head to user units - head *= UCF(RAINDEPTH); - - // --- compute drain outflow in user units - outflow = theLidProc->drain.coeff * - pow(head, theLidProc->drain.expon); - - // --- apply user-supplied control curve to outflow - if (curve >= 0) outflow *= table_lookup(&Curve[curve], head); - - // --- convert outflow to ft/s - outflow /= UCF(RAINFALL); - } - return outflow; -} - -//============================================================================= - -double getDrainMatOutflow(double depth) -// -// Purpose: computes flow rate through a green roof's drainage mat. -// Input: depth = depth of water in drainage mat (ft) -// Output: returns flow in drainage mat (ft/s) -// -{ - //... default is to pass all inflow - double result = SoilPerc; - - //... otherwise use Manning eqn. if its parameters were supplied - if ( theLidProc->drainMat.alpha > 0.0 ) - { - result = theLidProc->drainMat.alpha * pow(depth, 5.0/3.0) * - theLidUnit->fullWidth / theLidUnit->area * - theLidProc->drainMat.voidFrac; - } - return result; -} - -//============================================================================= - -void getEvapRates(double surfaceVol, double paveVol, double soilVol, - double storageVol, double pervFrac) -// -// Purpose: computes surface, pavement, soil, and storage evaporation rates. -// Input: surfaceVol = volume/area of ponded water on surface layer (ft) -// paveVol = volume/area of water in pavement pores (ft) -// soilVol = volume/area of water in soil (or pavement) pores (ft) -// storageVol = volume/area of water in storage layer (ft) -// pervFrac = fraction of surface layer that is pervious -// Output: none -// -{ - double availEvap; - - //... surface evaporation flux - availEvap = EvapRate; - SurfaceEvap = MIN(availEvap, surfaceVol/Tstep); - SurfaceEvap = MAX(0.0, SurfaceEvap); - availEvap = MAX(0.0, (availEvap - SurfaceEvap)); - availEvap *= pervFrac; - - //... no subsurface evap if water is infiltrating - if ( SurfaceInfil > 0.0 ) - { - PaveEvap = 0.0; - SoilEvap = 0.0; - StorageEvap = 0.0; - } - else - { - //... pavement evaporation flux - PaveEvap = MIN(availEvap, paveVol / Tstep); - availEvap = MAX(0.0, (availEvap - PaveEvap)); - - //... soil evaporation flux - SoilEvap = MIN(availEvap, soilVol / Tstep); - availEvap = MAX(0.0, (availEvap - SoilEvap)); - - //... storage evaporation flux - StorageEvap = MIN(availEvap, storageVol / Tstep); - } -} - -//============================================================================= - -double getSurfaceOverflowRate(double* surfaceDepth) -// -// Purpose: finds surface overflow rate from a LID unit. -// Input: surfaceDepth = depth of water stored in surface layer (ft) -// Output: returns the overflow rate (ft/s) -// -{ - double delta = *surfaceDepth - theLidProc->surface.thickness; - if ( delta <= 0.0 ) return 0.0; - *surfaceDepth = theLidProc->surface.thickness; - return delta * theLidProc->surface.voidFrac / Tstep; -} - -//============================================================================= - -void updateWaterBalance(TLidUnit *lidUnit, double inflow, double evap, - double infil, double surfFlow, double drainFlow, double storage) -// -// Purpose: updates components of the water mass balance for a LID unit -// over the current time step. -// Input: lidUnit = a particular LID unit -// inflow = runon + rainfall to the LID unit (ft/s) -// evap = evaporation rate from the unit (ft/s) -// infil = infiltration out the bottom of the unit (ft/s) -// surfFlow = surface runoff from the unit (ft/s) -// drainFlow = underdrain flow from the unit -// storage = volume of water stored in the unit (ft) -// Output: none -// -{ - lidUnit->volTreated += inflow * Tstep; - lidUnit->waterBalance.inflow += inflow * Tstep; - lidUnit->waterBalance.evap += evap * Tstep; - lidUnit->waterBalance.infil += infil * Tstep; - lidUnit->waterBalance.surfFlow += surfFlow * Tstep; - lidUnit->waterBalance.drainFlow += drainFlow * Tstep; - lidUnit->waterBalance.finalVol = storage; -} - -//============================================================================= - -int modpuls_solve(int n, double* x, double* xOld, double* xPrev, - double* xMin, double* xMax, double* xTol, - double* qOld, double* q, double dt, double omega, - void (*derivs)(double*, double*)) -// -// Purpose: solves system of equations dx/dt = q(x) for x at end of time step -// dt using a modified Puls method. -// Input: n = number of state variables -// x = vector of state variables -// xOld = state variable values at start of time step -// xPrev = state variable values from previous iteration -// xMin = lower limits on state variables -// xMax = upper limits on state variables -// xTol = convergence tolerances on state variables -// qOld = flux rates at start of time step -// q = flux rates at end of time step -// dt = time step (sec) -// omega = time weighting parameter (use 0 for Euler method -// or 0.5 for modified Puls method) -// derivs = pointer to function that computes flux rates q as a -// function of state variables x -// Output: returns number of steps required for convergence (or 0 if -// process doesn't converge) -// -{ - int i; - int canStop; - int steps = 1; - int maxSteps = 20; - - //... initialize state variable values - for (i=0; i 0.0 && - fabs(x[i] - xPrev[i]) > xTol[i] ) canStop = 0; - xPrev[i] = x[i]; - } - - //... return if process converges - if (canStop) return steps; - steps++; - } - - //... no convergence so return 0 - return 0; -} diff --git a/src/link.c b/src/link.c deleted file mode 100644 index 052a030b9..000000000 --- a/src/link.c +++ /dev/null @@ -1,2679 +0,0 @@ -//----------------------------------------------------------------------------- -// link.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 10/29/22 (Build 5.2.2) -// Author: L. Rossman -// M. Tryby (EPA) -// -// Conveyance system link functions -// -// Update History -// ============== -// Build 5.1.007: -// - Optional surcharging of weirs introduced. -// Build 5.1.008: -// - Bug in finding flow through surcharged weir fixed. -// - Bug in finding if conduit is upstrm/dnstrm full fixed. -// - Monthly conductivity adjustment applied to conduit seepage. -// - Conduit seepage limited by conduit's flow rate. -// Build 5.1.010: -// - Support added for new ROADWAY_WEIR object. -// - Time of last setting change initialized for links. -// Build 5.1.011: -// - Crest elevation of regulator links raised to downstream invert. -// - Fixed converting roadWidth weir parameter to internal units. -// - Weir shape parameter deprecated. -// - Extra geometric parameters ignored for non-conduit open rectangular -// cross sections. -// Build 5.1.012: -// - Conduit seepage rate now based on flow width, not wetted perimeter. -// - Formula for side flow weir corrected. -// - Crest length contraction adjustments corrected. -// Build 5.1.013: -// - Maximum depth adjustments made for storage units that can surcharge. -// - Support added for head-dependent weir coefficient curves. -// - Adjustment of regulator link crest offset to match downstream node invert -// now only done for Dynamic Wave flow routing. -// Build 5.1.014: -// - Conduit evap. and seepage losses initialized to 0 in conduit_initState() -// and not allowed to exceed current flow rate in conduit_getLossRate(). -// Build 5.2.0: -// - Support added for Streets and Inlets. -// - Support added for variable speed pumps. -// Build 5.2.1 -// - Warning no longer issued when conduit elevation drop < MIN_DELTA_Z. -// Build 5.2.2: -// - Warning for conduit elevation drop < MIN_DELTA_Z restored. -//----------------------------------------------------------------------------- -#define _CRT_SECURE_NO_DEPRECATE - -#include -#include -#include -#include "headers.h" -#include "inlet.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -static const double MIN_DELTA_Z = 0.001; // minimum elevation change for conduit - // slopes (ft) - -//----------------------------------------------------------------------------- -// External functions (declared in funcs.h) -//----------------------------------------------------------------------------- -// link_readParams (called by parseLine in input.c) -// link_readXsectParams (called by parseLine in input.c) -// link_readLossParams (called by parseLine in input.c) -// link_validate (called by project_validate in project.c) -// link_initState (called by initObjects in swmm5.c) -// link_setOldHydState (called by routing_execute in routing.c) -// link_setOldQualState (called by routing_execute in routing.c) -// link_setTargetSetting (called by routing_execute in routing.c) -// link_setSetting (called by routing_execute in routing.c) -// link_getResults (called by output_saveLinkResults) -// link_getLength (called in dwflow.c, kinwave.c & flowrout.c) -// link_getFroude (called in dwflow.c) -// link_getInflow (called in flowrout.c & dynwave.c) -// link_setOutfallDepth (called in flowrout.c & dynwave.c) -// link_getYcrit (called by link_setOutfallDepth & in dwflow.c) -// link_getYnorm (called by conduit_initState, link_setOutfallDepth & in dwflow.c) -// link_getVelocity (called by link_getResults & stats_updateLinkStats) -// link_getPower (called by stats_updateLinkStats in stats.c) -// link_getLossRate (called in dwflow.c, kinwave.c & flowrout.c) - -//----------------------------------------------------------------------------- -// Local functions -//----------------------------------------------------------------------------- -static void link_setParams(int j, int type, int n1, int n2, int k, double x[]); -static void link_convertOffsets(int j); -static double link_getOffsetHeight(int j, double offset, double elev); - -static int conduit_readParams(int j, int k, char* tok[], int ntoks); -static void conduit_validate(int j, int k); -static void conduit_initState(int j, int k); -static void conduit_reverse(int j, int k); -static double conduit_getLength(int j); -static double conduit_getLengthFactor(int j, int k, double roughness); -static double conduit_getSlope(int j); -static double conduit_getInflow(int j); -static double conduit_getLossRate(int j, double q); - -static int pump_readParams(int j, int k, char* tok[], int ntoks); -static void pump_validate(int j, int k); -static void pump_initState(int j, int k); -static double pump_getInflow(int j); - -static int orifice_readParams(int j, int k, char* tok[], int ntoks); -static void orifice_validate(int j, int k); -static void orifice_setSetting(int j, double tstep); -static double orifice_getWeirCoeff(int j, int k, double h); -static double orifice_getInflow(int j); -static double orifice_getFlow(int j, int k, double head, double f, - int hasFlapGate); - -static int weir_readParams(int j, int k, char* tok[], int ntoks); -static void weir_validate(int j, int k); -static void weir_setSetting(int j); -static double weir_getInflow(int j); -static double weir_getOpenArea(int j, double y); -static void weir_getFlow(int j, int k, double head, double dir, - int hasFlapGate, double* q1, double* q2); -static double weir_getOrificeFlow(int j, double head, double y, double cOrif); -static double weir_getdqdh(int k, double dir, double h, double q1, double q2); - -static int outlet_readParams(int j, int k, char* tok[], int ntoks); -static double outlet_getFlow(int k, double head); -static double outlet_getInflow(int j); - - -//============================================================================= - -int link_readParams(int j, int type, int k, char* tok[], int ntoks) -// -// Input: j = link index -// type = link type code -// k = link type index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads parameters for a specific type of link from a -// tokenized line of input data. -// -{ - switch ( type ) - { - case CONDUIT: return conduit_readParams(j, k, tok, ntoks); - case PUMP: return pump_readParams(j, k, tok, ntoks); - case ORIFICE: return orifice_readParams(j, k, tok, ntoks); - case WEIR: return weir_readParams(j, k, tok, ntoks); - case OUTLET: return outlet_readParams(j, k, tok, ntoks); - default: return 0; - } -} - -//============================================================================= - -int link_readXsectParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads a link's cross section parameters from a tokenized -// line of input data. -// Formats: -// Link Shape Geom1 Geom2 Geom3 Geom4 (Barrels Culvert) -// Link IRREGULAR TransectID -// Link STREET StreetID -// -{ - int i, j, k; - double x[4]; - - // --- check for minimum number of tokens - if (ntoks < 3) return error_setInpError(ERR_ITEMS, ""); - - // --- get index of link - j = project_findObject(LINK, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - - // --- get code of xsection shape - k = findmatch(tok[1], XsectTypeWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[1]); - - // --- assign default number of barrels to conduit - if ( Link[j].type == CONDUIT ) Conduit[Link[j].subIndex].barrels = 1; - - // --- assume link is not a culvert - Link[j].xsect.culvertCode = 0; - - // --- for irregular shape, find index of transect object - if ( k == IRREGULAR ) - { - i = project_findObject(TRANSECT, tok[2]); - if ( i < 0 ) return error_setInpError(ERR_NAME, tok[2]); - Link[j].xsect.type = k; - Link[j].xsect.transect = i; - return 0; - } - - // --- for street cross section, find index of Street object - else if (k == STREET_XSECT) - { - i = project_findObject(STREET, tok[2]); - if (i < 0) return error_setInpError(ERR_NAME, tok[2]); - Link[j].xsect.type = k; - Link[j].xsect.transect = i; - return 0; - } - - else - { - // --- check that geometric parameters are present - if (ntoks < 6) return error_setInpError(ERR_ITEMS, ""); - - // --- parse max. depth & shape curve for a custom shape - if ( k == CUSTOM ) - { - if ( !getDouble(tok[2], &x[0]) || x[0] <= 0.0 ) - return error_setInpError(ERR_NUMBER, tok[2]); - i = project_findObject(CURVE, tok[3]); - if ( i < 0 ) return error_setInpError(ERR_NAME, tok[3]); - Link[j].xsect.type = k; - Link[j].xsect.transect = i; - Link[j].xsect.yFull = x[0] / UCF(LENGTH); - } - - // --- parse and save geometric parameters - else for (i = 2; i <= 5; i++) - { - if ( !getDouble(tok[i], &x[i-2]) ) - return error_setInpError(ERR_NUMBER, tok[i]); - } - - // --- ignore extra parameters for non-conduit open rectangular shapes - if ( Link[j].type != CONDUIT && k == RECT_OPEN ) - { - x[2] = 0.0; - x[3] = 0.0; - } - if ( !xsect_setParams(&Link[j].xsect, k, x, UCF(LENGTH)) ) - { - return error_setInpError(ERR_NUMBER, ""); - } - - // --- parse number of barrels if present - if ( Link[j].type == CONDUIT && ntoks >= 7 ) - { - i = atoi(tok[6]); - if ( i <= 0 ) return error_setInpError(ERR_NUMBER, tok[6]); - else Conduit[Link[j].subIndex].barrels = (char)i; - } - - // --- parse culvert code if present - if ( Link[j].type == CONDUIT && ntoks >= 8 ) - { - i = atoi(tok[7]); - if ( i < 0 ) return error_setInpError(ERR_NUMBER, tok[7]); - else Link[j].xsect.culvertCode = i; - } - } - return 0; -} - -//============================================================================= - -int link_readLossParams(char* tok[], int ntoks) -// -// Input: tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads local loss parameters for a link from a tokenized -// line of input data. -// -// Format: LinkID cInlet cOutlet cAvg FlapGate(YES/NO) SeepRate -// -{ - int i, j, k; - double x[3]; - double seepRate = 0.0; - - if ( ntoks < 4 ) return error_setInpError(ERR_ITEMS, ""); - j = project_findObject(LINK, tok[0]); - if ( j < 0 ) return error_setInpError(ERR_NAME, tok[0]); - for (i=1; i<=3; i++) - { - if ( ! getDouble(tok[i], &x[i-1]) || x[i-1] < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[i]); - } - k = 0; - if ( ntoks >= 5 ) - { - k = findmatch(tok[4], NoYesWords); - if ( k < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); - } - if ( ntoks >= 6 ) - { - if ( ! getDouble(tok[5], &seepRate) ) - return error_setInpError(ERR_NUMBER, tok[5]); - } - Link[j].cLossInlet = x[0]; - Link[j].cLossOutlet = x[1]; - Link[j].cLossAvg = x[2]; - Link[j].hasFlapGate = k; - Link[j].seepRate = seepRate / UCF(RAINFALL); - return 0; -} - -//============================================================================= - -void link_setParams(int j, int type, int n1, int n2, int k, double x[]) -// -// Input: j = link index -// type = link type code -// n1 = index of upstream node -// n2 = index of downstream node -// k = index of link's sub-type -// x = array of parameter values -// Output: none -// Purpose: sets parameters for a link. -// -{ - Link[j].node1 = n1; - Link[j].node2 = n2; - Link[j].type = type; - Link[j].subIndex = k; - Link[j].offset1 = 0.0; - Link[j].offset2 = 0.0; - Link[j].q0 = 0.0; - Link[j].qFull = 0.0; - Link[j].setting = 1.0; - Link[j].targetSetting = 1.0; - Link[j].hasFlapGate = 0; - Link[j].qLimit = 0.0; // 0 means that no limit is defined - Link[j].direction = 1; - - switch (type) - { - case CONDUIT: - Conduit[k].length = x[0] / UCF(LENGTH); - Conduit[k].modLength = Conduit[k].length; - Conduit[k].roughness = x[1]; - Link[j].offset1 = x[2] / UCF(LENGTH); - Link[j].offset2 = x[3] / UCF(LENGTH); - Link[j].q0 = x[4] / UCF(FLOW); - Link[j].qLimit = x[5] / UCF(FLOW); - break; - - case PUMP: - Pump[k].pumpCurve = (int)x[0]; - Link[j].hasFlapGate = FALSE; - Pump[k].initSetting = x[1]; - Pump[k].yOn = x[2] / UCF(LENGTH); - Pump[k].yOff = x[3] / UCF(LENGTH); - Pump[k].xMin = 0.0; - Pump[k].xMax = 0.0; - break; - - case ORIFICE: - Orifice[k].type = (int)x[0]; - Link[j].offset1 = x[1] / UCF(LENGTH); - Link[j].offset2 = Link[j].offset1; - Orifice[k].cDisch = x[2]; - Link[j].hasFlapGate = (x[3] > 0.0) ? 1 : 0; - Orifice[k].orate = x[4] * 3600.0; - break; - - case WEIR: - Weir[k].type = (int)x[0]; - Link[j].offset1 = x[1] / UCF(LENGTH); - Link[j].offset2 = Link[j].offset1; - Weir[k].cDisch1 = x[2]; - Link[j].hasFlapGate = (x[3] > 0.0) ? 1 : 0; - Weir[k].endCon = x[4]; - Weir[k].cDisch2 = x[5]; - Weir[k].canSurcharge = (int)x[6]; - Weir[k].roadWidth = x[7] / UCF(LENGTH); - Weir[k].roadSurface = (int)x[8]; - Weir[k].cdCurve = (int)x[9]; - break; - - case OUTLET: - Link[j].offset1 = x[0] / UCF(LENGTH); - Link[j].offset2 = Link[j].offset1; - Outlet[k].qCoeff = x[1]; - Outlet[k].qExpon = x[2]; - Outlet[k].qCurve = (int)x[3]; - Link[j].hasFlapGate = (x[4] > 0.0) ? 1 : 0; - Outlet[k].curveType = (int)x[5]; - - xsect_setParams(&Link[j].xsect, DUMMY, NULL, 0.0); - break; - - } -} - -//============================================================================= - -void link_validate(int j) -// -// Input: j = link index -// Output: none -// Purpose: validates a link's properties. -// -{ - int n; - - if ( LinkOffsets == ELEV_OFFSET ) link_convertOffsets(j); - switch ( Link[j].type ) - { - case CONDUIT: conduit_validate(j, Link[j].subIndex); break; - case PUMP: pump_validate(j, Link[j].subIndex); break; - case ORIFICE: orifice_validate(j, Link[j].subIndex); break; - case WEIR: weir_validate(j, Link[j].subIndex); break; - } - - // --- check if crest of regulator opening < invert of downstream node - switch ( Link[j].type ) - { - case ORIFICE: - case WEIR: - case OUTLET: - if ( Node[Link[j].node1].invertElev + Link[j].offset1 < - Node[Link[j].node2].invertElev ) - { - if (RouteModel == DW) - { - Link[j].offset1 = Node[Link[j].node2].invertElev - - Node[Link[j].node1].invertElev; - report_writeWarningMsg(WARN10b, Link[j].ID); - } - else report_writeWarningMsg(WARN10a, Link[j].ID); - } - } - - // --- force max. depth of end nodes to be >= link crown height - // at non-storage nodes - - // --- skip pumps and bottom orifices - if ( Link[j].type == PUMP || - (Link[j].type == ORIFICE && - Orifice[Link[j].subIndex].type == BOTTOM_ORIFICE) ) return; - - // --- extend upstream node's full depth to link's crown elevation - n = Link[j].node1; - if ( Node[n].type != STORAGE || Node[n].surDepth > 0.0 ) - { - Node[n].fullDepth = MAX(Node[n].fullDepth, - Link[j].offset1 + Link[j].xsect.yFull); - } - - // --- do same for downstream node only for conduit links - n = Link[j].node2; - if ( (Node[n].type != STORAGE || Node[n].surDepth > 0.0) && - Link[j].type == CONDUIT ) - { - Node[n].fullDepth = MAX(Node[n].fullDepth, - Link[j].offset2 + Link[j].xsect.yFull); - } -} - -//============================================================================= - -void link_convertOffsets(int j) -// -// Input: j = link index -// Output: none -// Purpose: converts offset elevations to offset heights for a link. -// -{ - double elev; - - elev = Node[Link[j].node1].invertElev; - Link[j].offset1 = link_getOffsetHeight(j, Link[j].offset1, elev); - if ( Link[j].type == CONDUIT ) - { - elev = Node[Link[j].node2].invertElev; - Link[j].offset2 = link_getOffsetHeight(j, Link[j].offset2, elev); - } - else Link[j].offset2 = Link[j].offset1; -} - -//============================================================================= - -double link_getOffsetHeight(int j, double offset, double elev) -// -// Input: j = link index -// offset = link elevation offset (ft) -// elev = node invert elevation (ft) -// Output: returns offset distance above node invert (ft) -// Purpose: finds offset height for one end of a link. -// -{ - if ( offset <= MISSING || Link[j].type == PUMP) return 0.0; - offset -= elev; - if ( offset >= 0.0 ) return offset; - if ( offset >= -MIN_DELTA_Z ) return 0.0; - report_writeWarningMsg(WARN03, Link[j].ID); - return 0.0; -} - -//============================================================================= - -void link_initState(int j) -// -// Input: j = link index -// Output: none -// Purpose: initializes a link's state variables at start of simulation. -// -{ - int p; - - // --- initialize hydraulic state - Link[j].oldFlow = Link[j].q0; - Link[j].newFlow = Link[j].q0; - Link[j].oldDepth = 0.0; - Link[j].newDepth = 0.0; - Link[j].oldVolume = 0.0; - Link[j].newVolume = 0.0; - Link[j].setting = 1.0; - Link[j].targetSetting = 1.0; - Link[j].timeLastSet = StartDate; - Link[j].inletControl = FALSE; - Link[j].normalFlow = FALSE; - if ( Link[j].type == CONDUIT ) conduit_initState(j, Link[j].subIndex); - if ( Link[j].type == PUMP ) pump_initState(j, Link[j].subIndex); - - // --- initialize water quality state - for (p = 0; p < Nobjects[POLLUT]; p++) - { - Link[j].oldQual[p] = 0.0; - Link[j].newQual[p] = 0.0; - Link[j].totalLoad[p] = 0.0; - } -} - -//============================================================================= - -double link_getInflow(int j) -// -// Input: j = link index -// Output: returns link flow rate (cfs) -// Purpose: finds total flow entering a link during current time step. -// -{ - if ( Link[j].setting == 0 ) return 0.0; - switch ( Link[j].type ) - { - case CONDUIT: return conduit_getInflow(j); - case PUMP: return pump_getInflow(j); - case ORIFICE: return orifice_getInflow(j); - case WEIR: return weir_getInflow(j); - case OUTLET: return outlet_getInflow(j); - default: return node_getOutflow(Link[j].node1, j); - } -} - -//============================================================================= - -void link_setOldHydState(int j) -// -// Input: j = link index -// Output: none -// Purpose: replaces link's old hydraulic state values with current ones. -// -{ - int k; - - Link[j].oldDepth = Link[j].newDepth; - Link[j].oldFlow = Link[j].newFlow; - Link[j].oldVolume = Link[j].newVolume; - - if ( Link[j].type == CONDUIT ) - { - k = Link[j].subIndex; - Conduit[k].q1Old = Conduit[k].q1; - Conduit[k].q2Old = Conduit[k].q2; - } -} - -//============================================================================= - -void link_setOldQualState(int j) -// -// Input: j = link index -// Output: none -// Purpose: replaces link's old water quality state values with current ones. -// -{ - int p; - for (p = 0; p < Nobjects[POLLUT]; p++) - { - Link[j].oldQual[p] = Link[j].newQual[p]; - Link[j].newQual[p] = 0.0; - } -} - -//============================================================================= - -void link_setTargetSetting(int j) -// -// Input: j = link index -// Output: none -// Purpose: updates a link's target setting. -// -{ - int k, n1; - if ( Link[j].type == PUMP ) - { - k = Link[j].subIndex; - n1 = Link[j].node1; - Link[j].targetSetting = Link[j].setting; - if ( Pump[k].yOff > 0.0 && - Link[j].setting > 0.0 && - Node[n1].newDepth < Pump[k].yOff ) Link[j].targetSetting = 0.0; - if ( Pump[k].yOn > 0.0 && - Link[j].setting == 0.0 && - Node[n1].newDepth > Pump[k].yOn ) Link[j].targetSetting = 1.0; - } -} - -//============================================================================= - -void link_setSetting(int j, double tstep) -// -// Input: j = link index -// tstep = time step over which setting is adjusted -// Output: none -// Purpose: updates a link's setting as a result of a control action. -// -{ - if ( Link[j].type == ORIFICE ) orifice_setSetting(j, tstep); - else if ( Link[j].type == WEIR ) weir_setSetting(j); - else Link[j].setting = Link[j].targetSetting; -} - -//============================================================================= - -int link_setFlapGate(int j, int n1, int n2, double q) -// -// Input: j = link index -// n1 = index of node on upstream end of link -// n2 = index of node on downstream end of link -// q = signed flow value (value and units don't matter) -// Output: returns TRUE if there is reverse flow through a flap gate -// associated with the link. -// Purpose: based on the sign of the flow, determines if a flap gate -// associated with the link should close or not. -// -{ - int n = -1; - - // --- check for reverse flow through link's flap gate - if ( Link[j].hasFlapGate ) - { - if ( q * (double)Link[j].direction < 0.0 ) return TRUE; - } - - // --- check for Outfall with flap gate node on inflow end of link - if ( q < 0.0 ) n = n2; - if ( q > 0.0 ) n = n1; - if ( n >= 0 && - Node[n].type == OUTFALL && - Outfall[Node[n].subIndex].hasFlapGate ) return TRUE; - return FALSE; -} - -//============================================================================= - -void link_getResults(int j, double f, float x[]) -// -// Input: j = link index -// f = time weighting factor -// Output: x = array of weighted results -// Purpose: retrieves time-weighted average of old and new results for a link. -// -{ - int p; // pollutant index - double y, // depth - q, // flow - u, // velocity - v, // volume - c; // capacity, setting or concentration - double f1 = 1.0 - f; - - y = f1*Link[j].oldDepth + f*Link[j].newDepth; - q = f1*Link[j].oldFlow + f*Link[j].newFlow; - v = f1*Link[j].oldVolume + f*Link[j].newVolume; - u = link_getVelocity(j, q, y); - c = 0.0; - if (Link[j].type == CONDUIT) - { - if (Link[j].xsect.type != DUMMY) - c = xsect_getAofY(&Link[j].xsect, y) / Link[j].xsect.aFull; - } - else c = Link[j].setting; - - // --- override time weighting for pump flow between on/off states - if (Link[j].type == PUMP && Link[j].oldFlow*Link[j].newFlow == 0.0) - { - if ( f >= f1 ) q = Link[j].newFlow; - else q = Link[j].oldFlow; - } - - y *= UCF(LENGTH); - v *= UCF(VOLUME); - q *= UCF(FLOW) * (double)Link[j].direction; - u *= UCF(LENGTH) * (double)Link[j].direction; - x[LINK_DEPTH] = (float)y; - x[LINK_FLOW] = (float)q; - x[LINK_VELOCITY] = (float)u; - x[LINK_VOLUME] = (float)v; - x[LINK_CAPACITY] = (float)c; - - if ( !IgnoreQuality ) for (p = 0; p < Nobjects[POLLUT]; p++) - { - c = f1*Link[j].oldQual[p] + f*Link[j].newQual[p]; - x[LINK_QUAL+p] = (float)c; - } -} - -//============================================================================= - -void link_setOutfallDepth(int j) -// -// Input: j = link index -// Output: none -// Purpose: sets depth at outfall node connected to link j. -// -{ - int k; // conduit index - int n; // outfall node index - double z; // invert offset height (ft) - double q; // flow rate (cfs) - double yCrit = 0.0; // critical flow depth (ft) - double yNorm = 0.0; // normal flow depth (ft) - - // --- find which end node of link is an outfall - if ( Node[Link[j].node2].type == OUTFALL ) - { - n = Link[j].node2; - z = Link[j].offset2; - } - else if ( Node[Link[j].node1].type == OUTFALL ) - { - n = Link[j].node1; - z = Link[j].offset1; - } - else return; - - // --- find both normal & critical depth for current flow - if ( Link[j].type == CONDUIT ) - { - k = Link[j].subIndex; - q = fabs(Link[j].newFlow / Conduit[k].barrels); - yNorm = link_getYnorm(j, q); - yCrit = link_getYcrit(j, q); - } - - // --- set new depth at node - node_setOutletDepth(n, yNorm, yCrit, z); -} - -//============================================================================= - -double link_getYcrit(int j, double q) -// -// Input: j = link index -// q = link flow rate (cfs) -// Output: returns critical depth (ft) -// Purpose: computes critical depth for given flow rate. -// -{ - return xsect_getYcrit(&Link[j].xsect, q); -} - -//============================================================================= - -double link_getYnorm(int j, double q) -// -// Input: j = link index -// q = link flow rate (cfs) -// Output: returns normal depth (ft) -// Purpose: computes normal depth for given flow rate. -// -{ - int k; - double s, a, y; - - if ( Link[j].type != CONDUIT ) return 0.0; - if ( Link[j].xsect.type == DUMMY ) return 0.0; - q = fabs(q); - k = Link[j].subIndex; - if ( q > Conduit[k].qMax ) q = Conduit[k].qMax; - if ( q <= 0.0 ) return 0.0; - s = q / Conduit[k].beta; - a = xsect_getAofS(&Link[j].xsect, s); - y = xsect_getYofA(&Link[j].xsect, a); - return y; -} - -//============================================================================= - -double link_getLength(int j) -// -// Input: j = link index -// Output: returns length (ft) -// Purpose: finds true length of a link. -// -{ - if ( Link[j].type == CONDUIT ) return conduit_getLength(j); - return 0.0; -} - -//============================================================================= - -double link_getVelocity(int j, double flow, double depth) -// -// Input: j = link index -// flow = link flow rate (cfs) -// depth = link flow depth (ft) -// Output: returns flow velocity (fps) -// Purpose: finds flow velocity given flow and depth. -// -{ - double area; - double veloc = 0.0; - int k; - - if ( depth <= 0.01 ) return 0.0; - if ( Link[j].type == CONDUIT ) - { - k = Link[j].subIndex; - flow /= Conduit[k].barrels; - area = xsect_getAofY(&Link[j].xsect, depth); - if (area > FUDGE ) veloc = flow / area; - } - return veloc; -} - -//============================================================================= - -double link_getFroude(int j, double v, double y) -// -// Input: j = link index -// v = flow velocity (fps) -// y = flow depth (ft) -// Output: returns Froude Number -// Purpose: computes Froude Number for given velocity and flow depth -// -{ - TXsect* xsect = &Link[j].xsect; - - // --- return 0 if link is not a conduit - if ( Link[j].type != CONDUIT ) return 0.0; - - // --- return 0 if link empty or closed conduit is full - if ( y <= FUDGE ) return 0.0; - if ( !xsect_isOpen(xsect->type) && - xsect->yFull - y <= FUDGE ) return 0.0; - - // --- compute hydraulic depth - y = xsect_getAofY(xsect, y) / xsect_getWofY(xsect, y); - - // --- compute Froude No. - return fabs(v) / sqrt(GRAVITY * y); -} - -//============================================================================= - -double link_getPower(int j) -// -// Input: j = link index -// Output: returns power consumed by link in kwatts -// Purpose: computes power consumed by head loss (or head gain) of -// water flowing through a link -// -{ - int n1 = Link[j].node1; - int n2 = Link[j].node2; - double dh = (Node[n1].invertElev + Node[n1].newDepth) - - (Node[n2].invertElev + Node[n2].newDepth); - double q = fabs(Link[j].newFlow); - return fabs(dh) * q / 8.814 * KWperHP; -} - -//============================================================================= - -double link_getLossRate(int j, double q) -// -// Input: j = link index -// q = flow rate (ft3/sec) -// tstep = time step (sec) -// Output: returns uniform loss rate in link (ft3/sec) -// Purpose: computes rate at which flow volume is lost in a link due to -// evaporation and seepage. -// -{ - if ( Link[j].type == CONDUIT ) return conduit_getLossRate(j, q); - else return 0.0; -} - -//============================================================================= - -char link_getFullState(double a1, double a2, double aFull) -// -// Input: a1 = upstream link area (ft2) -// a2 = downstream link area (ft2) -// aFull = area of full conduit -// Output: returns fullness state of a link -// Purpose: determines if a link is upstream, downstream or completely full. -// -{ - if ( a1 >= aFull ) - { - if ( a2 >= aFull ) return ALL_FULL; - else return UP_FULL; - } - if ( a2 >= aFull ) return DN_FULL; - return 0; -} - -//============================================================================= -// C O N D U I T M E T H O D S -//============================================================================= - -int conduit_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = conduit index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads conduit parameters from a tokenzed line of input. -// -{ - int n1, n2; - double x[6]; - char* id; - - // --- check for valid ID and end node IDs - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); // link ID - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); // upstrm. node - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); // dwnstrm. node - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- parse length & Mannings N - if ( !getDouble(tok[3], &x[0]) ) - return error_setInpError(ERR_NUMBER, tok[3]); - if ( !getDouble(tok[4], &x[1]) ) - return error_setInpError(ERR_NUMBER, tok[4]); - - // --- parse offsets - if ( LinkOffsets == ELEV_OFFSET && *tok[5] == '*' ) x[2] = MISSING; - else if ( !getDouble(tok[5], &x[2]) ) - return error_setInpError(ERR_NUMBER, tok[5]); - if ( LinkOffsets == ELEV_OFFSET && *tok[6] == '*' ) x[3] = MISSING; - else if ( !getDouble(tok[6], &x[3]) ) - return error_setInpError(ERR_NUMBER, tok[6]); - - // --- parse optional parameters - x[4] = 0.0; // init. flow - if ( ntoks >= 8 ) - { - if ( !getDouble(tok[7], &x[4]) ) - return error_setInpError(ERR_NUMBER, tok[7]); - } - x[5] = 0.0; - if ( ntoks >= 9 ) - { - if ( !getDouble(tok[8], &x[5]) ) - return error_setInpError(ERR_NUMBER, tok[8]); - } - - // --- add parameters to data base - Link[j].ID = id; - link_setParams(j, CONDUIT, n1, n2, k, x); - return 0; -} - -//============================================================================= - -void conduit_validate(int j, int k) -// -// Input: j = link index -// k = conduit index -// Output: none -// Purpose: validates a conduit's properties. -// -{ - double aa; - double lengthFactor, roughness, slope; - - // --- a storage node cannot have a dummy outflow link - if ( Link[j].xsect.type == DUMMY && RouteModel == DW ) - { - if ( Node[Link[j].node1].type == STORAGE ) - { - report_writeErrorMsg(ERR_DUMMY_LINK, Node[Link[j].node1].ID); - return; - } - } - - // --- if custom xsection, then set its parameters - if ( Link[j].xsect.type == CUSTOM ) - xsect_setCustomXsectParams(&Link[j].xsect); - - // --- if irreg. xsection, assign transect roughness to conduit - if ( Link[j].xsect.type == IRREGULAR ) - { - xsect_setIrregXsectParams(&Link[j].xsect); - Conduit[k].roughness = Transect[Link[j].xsect.transect].roughness; - } - - // --- if street xsection, then set its parameters - if (Link[j].xsect.type == STREET_XSECT) - { - xsect_setStreetXsectParams(&Link[j].xsect); - Conduit[k].roughness = Street[Link[j].xsect.transect].roughness; - } - - // --- if force main xsection, adjust units on D-W roughness height - if ( Link[j].xsect.type == FORCE_MAIN ) - { - if ( ForceMainEqn == D_W ) Link[j].xsect.rBot /= UCF(RAINDEPTH); - if ( Link[j].xsect.rBot <= 0.0 ) - report_writeErrorMsg(ERR_XSECT, Link[j].ID); - } - - // --- check for valid length & roughness - if ( Conduit[k].length <= 0.0 ) - report_writeErrorMsg(ERR_LENGTH, Link[j].ID); - if ( Conduit[k].roughness <= 0.0 ) - report_writeErrorMsg(ERR_ROUGHNESS, Link[j].ID); - if ( Conduit[k].barrels <= 0 ) - report_writeErrorMsg(ERR_BARRELS, Link[j].ID); - - // --- check for valid xsection - if ( Link[j].xsect.type != DUMMY ) - { - if ( Link[j].xsect.type < 0 ) - report_writeErrorMsg(ERR_NO_XSECT, Link[j].ID); - else if ( Link[j].xsect.aFull <= 0.0 ) - report_writeErrorMsg(ERR_XSECT, Link[j].ID); - } - if ( ErrorCode ) return; - - // --- check for negative offsets - if ( Link[j].offset1 < 0.0 ) - { - report_writeWarningMsg(WARN03, Link[j].ID); - Link[j].offset1 = 0.0; - } - if ( Link[j].offset2 < 0.0 ) - { - report_writeWarningMsg(WARN03, Link[j].ID); - Link[j].offset2 = 0.0; - } - - // --- adjust conduit offsets for partly filled circular xsection - if ( Link[j].xsect.type == FILLED_CIRCULAR ) - { - Link[j].offset1 += Link[j].xsect.yBot; - Link[j].offset2 += Link[j].xsect.yBot; - } - - // --- compute conduit slope - slope = conduit_getSlope(j); - Conduit[k].slope = slope; - - // --- reverse orientation of conduit if using dynamic wave routing - // and slope is negative - if ( RouteModel == DW && - slope < 0.0 && - Link[j].xsect.type != DUMMY ) - { - conduit_reverse(j, k); - } - - // --- get equivalent Manning roughness for Force Mains - // for use when pipe is partly full - roughness = Conduit[k].roughness; - if ( RouteModel == DW && Link[j].xsect.type == FORCE_MAIN ) - { - roughness = forcemain_getEquivN(j, k); - } - - // --- adjust roughness for meandering natural channels - if ( Link[j].xsect.type == IRREGULAR ) - { - lengthFactor = Transect[Link[j].xsect.transect].lengthFactor; - roughness *= sqrt(lengthFactor); - } - - // --- lengthen conduit if lengthening option is in effect - lengthFactor = 1.0; - if ( RouteModel == DW && - LengtheningStep > 0.0 && - Link[j].xsect.type != DUMMY ) - { - lengthFactor = conduit_getLengthFactor(j, k, roughness); - } - - if ( lengthFactor != 1.0 ) - { - Conduit[k].modLength = lengthFactor * conduit_getLength(j); - slope /= lengthFactor; - roughness = roughness / sqrt(lengthFactor); - } - - // --- compute roughness factor used when computing friction - // slope term in Dynamic Wave flow routing - - // --- special case for non-Manning Force Mains - // (roughness factor for full flow is saved in xsect.sBot) - if ( RouteModel == DW && Link[j].xsect.type == FORCE_MAIN ) - { - Link[j].xsect.sBot = - forcemain_getRoughFactor(j, lengthFactor); - } - Conduit[k].roughFactor = GRAVITY * SQR(roughness/PHI); - - // --- compute full flow through cross section - if ( Link[j].xsect.type == DUMMY ) Conduit[k].beta = 0.0; - else Conduit[k].beta = PHI * sqrt(fabs(slope)) / roughness; - Link[j].qFull = Link[j].xsect.sFull * Conduit[k].beta; - Conduit[k].qMax = Link[j].xsect.sMax * Conduit[k].beta; - - // --- see if flow is supercritical most of time - // by comparing normal & critical velocities. - // (factor of 0.3 is for circular pipe 95% full) - // NOTE: this factor was used in the past for a modified version of - // Kinematic Wave routing but is now deprecated. - aa = Conduit[k].beta / sqrt(32.2) * - pow(Link[j].xsect.yFull, 0.1666667) * 0.3; - if ( aa >= 1.0 ) Conduit[k].superCritical = TRUE; - else Conduit[k].superCritical = FALSE; - - // --- set value of hasLosses flag - if ( Link[j].cLossInlet == 0.0 && - Link[j].cLossOutlet == 0.0 && - Link[j].cLossAvg == 0.0 - ) Conduit[k].hasLosses = FALSE; - else Conduit[k].hasLosses = TRUE; -} - -//============================================================================= - -void conduit_reverse(int j, int k) -// -// Input: j = link index -// k = conduit index -// Output: none -// Purpose: reverses direction of a conduit -// -{ - int i; - double z; - double cLoss; - - // --- reverse end nodes - i = Link[j].node1; - Link[j].node1 = Link[j].node2; - Link[j].node2 = i; - - // --- reverse node offsets - z = Link[j].offset1; - Link[j].offset1 = Link[j].offset2; - Link[j].offset2 = z; - - // --- reverse loss coeffs. - cLoss = Link[j].cLossInlet; - Link[j].cLossInlet = Link[j].cLossOutlet; - Link[j].cLossOutlet = cLoss; - - // --- reverse direction & slope - Conduit[k].slope = -Conduit[k].slope; - Link[j].direction *= (signed char)-1; - - // --- reverse initial flow value - Link[j].q0 = -Link[j].q0; -} - -//============================================================================= - -double conduit_getLength(int j) -// -// Input: j = link index -// Output: returns conduit's length (ft) -// Purpose: finds true length of a conduit. -// -// Note: for irregular natural channels, user inputs length of main -// channel (for FEMA purposes) but program should use length -// associated with entire flood plain. Transect.lengthFactor -// is the ratio of these two lengths. -// -{ - int k = Link[j].subIndex; - int t; - if ( Link[j].xsect.type != IRREGULAR ) return Conduit[k].length; - t = Link[j].xsect.transect; - if ( t < 0 || t >= Nobjects[TRANSECT] ) return Conduit[k].length; - return Conduit[k].length / Transect[t].lengthFactor; -} - -//============================================================================= - -double conduit_getLengthFactor(int j, int k, double roughness) -// -// Input: j = link index -// k = conduit index -// roughness = conduit Manning's n -// Output: returns factor by which a conduit should be lengthened -// Purpose: computes amount of conduit lengthing to improve numerical stability. -// -// The following form of the Courant criterion is used: -// L = t * v * (1 + Fr) / Fr -// where L = conduit length, t = time step, v = velocity, & Fr = Froude No. -// After substituting Fr = v / sqrt(gy), where y = flow depth, we get: -// L = t * ( sqrt(gy) + v ) -// -{ - double ratio; - double yFull; - double vFull; - double tStep; - - // --- evaluate flow depth and velocity at full normal flow condition - yFull = Link[j].xsect.yFull; - if ( xsect_isOpen(Link[j].xsect.type) ) - { - yFull = Link[j].xsect.aFull / xsect_getWofY(&Link[j].xsect, yFull); - } - vFull = PHI / roughness * Link[j].xsect.sFull * - sqrt(fabs(Conduit[k].slope)) / Link[j].xsect.aFull; - - // --- determine ratio of Courant length to actual length - if ( LengtheningStep == 0.0 ) tStep = RouteStep; - else tStep = MIN(RouteStep, LengtheningStep); - ratio = (sqrt(GRAVITY*yFull) + vFull) * tStep / conduit_getLength(j); - - // --- return max. of 1.0 and ratio - if ( ratio > 1.0 ) return ratio; - else return 1.0; -} - -//============================================================================= - -double conduit_getSlope(int j) -// -// Input: j = link index -// Output: returns conduit slope -// Purpose: computes conduit slope. -// -{ - double elev1, elev2, delta, slope; - double length = conduit_getLength(j); - - // --- check that elevation drop > minimum allowable drop - elev1 = Link[j].offset1 + Node[Link[j].node1].invertElev; - elev2 = Link[j].offset2 + Node[Link[j].node2].invertElev; - delta = fabs(elev1 - elev2); - if ( delta < MIN_DELTA_Z ) - { - report_writeWarningMsg(WARN04, Link[j].ID); - delta = MIN_DELTA_Z; - } - - // --- elevation drop cannot exceed conduit length - if ( delta >= length ) - { - report_writeWarningMsg(WARN08, Link[j].ID); - slope = delta / length; - } - - // --- slope = elev. drop / horizontal distance - else slope = delta / sqrt(SQR(length) - SQR(delta)); - - // -- check that slope exceeds minimum allowable slope - if ( MinSlope > 0.0 && slope < MinSlope ) - { - report_writeWarningMsg(WARN05, Link[j].ID); - slope = MinSlope; - // keep min. slope positive for SF or KW routing - if (RouteModel == SF || RouteModel == KW) return slope; - } - - // --- change sign for adverse slope - if ( elev1 < elev2 ) slope = -slope; - return slope; -} - -//============================================================================= - -void conduit_initState(int j, int k) -// -// Input: j = link index -// k = conduit index -// Output: none -// Purpose: sets initial conduit depth to normal depth of initial flow -// -{ - Link[j].newDepth = link_getYnorm(j, Link[j].q0 / Conduit[k].barrels); - Link[j].oldDepth = Link[j].newDepth; - Conduit[k].evapLossRate = 0.0; - Conduit[k].seepLossRate = 0.0; -} - -//============================================================================= - -double conduit_getInflow(int j) -// -// Input: j = link index -// Output: returns flow in link (cfs) -// Purpose: finds inflow to conduit from upstream node. -// -{ - double qIn = node_getOutflow(Link[j].node1, j); - if ( Link[j].qLimit > 0.0 ) qIn = MIN(qIn, Link[j].qLimit); - return qIn; -} - -//============================================================================= - -double conduit_getLossRate(int j, double q) -// -// Input: j = link index -// q = current link flow rate (cfs) -// Output: returns rate of evaporation & seepage losses (ft3/sec) -// Purpose: computes volumetric rate of water evaporation & seepage -// from a conduit (per barrel). -// -{ - TXsect *xsect; - double depth = 0.5 * (Link[j].oldDepth + Link[j].newDepth); - double length; - double topWidth; - double evapLossRate = 0.0, - seepLossRate = 0.0, - totalLossRate = 0.0; - - if ( depth > FUDGE ) - { - xsect = &Link[j].xsect; - length = conduit_getLength(j); - - // --- find evaporation rate for open conduits - if ( xsect_isOpen(xsect->type) && Evap.rate > 0.0 ) - { - topWidth = xsect_getWofY(xsect, depth); - evapLossRate = topWidth * length * Evap.rate; - } - - // --- compute seepage loss rate - if ( Link[j].seepRate > 0.0 ) - { - // limit depth to depth at max width - if ( depth >= xsect->ywMax ) depth = xsect->ywMax; - - // compute seepage loss rate across length of conduit - seepLossRate = Link[j].seepRate * xsect_getWofY(xsect, depth) * - length; - seepLossRate *= Adjust.hydconFactor; - } - - // --- compute total loss rate - totalLossRate = evapLossRate + seepLossRate; - - // --- total loss rate cannot exceed flow rate - q = ABS(q); - if (totalLossRate > q) - { - evapLossRate = evapLossRate * q / totalLossRate; - seepLossRate = seepLossRate * q / totalLossRate; - totalLossRate = q; - } - } - - Conduit[Link[j].subIndex].evapLossRate = evapLossRate; - Conduit[Link[j].subIndex].seepLossRate = seepLossRate; - return totalLossRate; -} - - -//============================================================================= -// P U M P M E T H O D S -//============================================================================= - -int pump_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = pump index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads pump parameters from a tokenized line of input. -// -{ - int m; - int n1, n2; - double x[4]; - char* id; - - // --- check for valid ID and end node IDs - if ( ntoks < 3 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- parse curve name - x[0] = -1.; - if ( ntoks >= 4 ) - { - if ( !strcomp(tok[3],"*") ) - { - m = project_findObject(CURVE, tok[3]); - if ( m < 0 ) return error_setInpError(ERR_NAME, tok[3]); - x[0] = m; - } - } - - // --- parse init. status if present - x[1] = 1.0; - if ( ntoks >= 5 ) - { - m = findmatch(tok[4], OffOnWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); - x[1] = m; - } - - // --- parse startup/shutoff depths if present - x[2] = 0.0; - if ( ntoks >= 6 ) - { - if ( !getDouble(tok[5], &x[2]) || x[2] < 0.0) - return error_setInpError(ERR_NUMBER, tok[5]); - } - x[3] = 0.0; - if ( ntoks >= 7 ) - { - if ( !getDouble(tok[6], &x[3]) || x[3] < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[6]); - } - - // --- add parameters to pump object - Link[j].ID = id; - link_setParams(j, PUMP, n1, n2, k, x); - return 0; -} - -//============================================================================= - -void pump_validate(int j, int k) -// -// Input: j = link index -// k = pump index -// Output: none -// Purpose: validates a pump's properties -// -{ - int m, n1; - double x, y; - - Link[j].xsect.yFull = 0.0; - - // --- check for valid curve type - m = Pump[k].pumpCurve; - if ( m < 0 ) - { - Pump[k].type = IDEAL_PUMP; - } - else - { - if ( Curve[m].curveType < PUMP1_CURVE || - Curve[m].curveType > PUMP5_CURVE ) - report_writeErrorMsg(ERR_NO_CURVE, Link[j].ID); - - // --- store pump curve type with pump's parameters - else - { - Pump[k].type = Curve[m].curveType - PUMP1_CURVE; - if ( table_getFirstEntry(&Curve[m], &x, &y) ) - { - Link[j].qFull = y; - Pump[k].xMin = x; - Pump[k].xMax = x; - while ( table_getNextEntry(&Curve[m], &x, &y) ) - { - Link[j].qFull = MAX(y, Link[j].qFull); - Pump[k].xMax = x; - } - } - Link[j].qFull /= UCF(FLOW); - } - } - - // --- check that shutoff depth < startup depth - if ( Pump[k].yOn > 0.0 && Pump[k].yOn <= Pump[k].yOff ) - report_writeErrorMsg(ERR_PUMP_LIMITS, Link[j].ID); - - // --- assign wet well volume to inlet node of Type 1 pump - if ( Pump[k].type == TYPE1_PUMP ) - { - n1 = Link[j].node1; - if ( Node[n1].type != STORAGE ) - Node[n1].fullVolume = MAX(Node[n1].fullVolume, - Pump[k].xMax / UCF(VOLUME)); - } - -} - -//============================================================================= - -void pump_initState(int j, int k) -// -// Input: j = link index -// k = pump index -// Output: none -// Purpose: initializes pump conditions at start of a simulation -// -{ - Link[j].setting = Pump[k].initSetting; - Link[j].targetSetting = Pump[k].initSetting; -} - -//============================================================================= - -double pump_getInflow(int j) -// -// Input: j = link index -// Output: returns pump flow (cfs) -// Purpose: finds flow produced by a pump. -// -{ - int k, m; - int n1, n2; - double vol, depth, head; - double qIn, qIn1, dh = 0.001; - double s = 1.0; // speed setting - - k = Link[j].subIndex; - m = Pump[k].pumpCurve; - n1 = Link[j].node1; - n2 = Link[j].node2; - - // --- no flow if setting is closed - Link[j].flowClass = NO; - Link[j].setting = Link[j].targetSetting; - if ( Link[j].setting == 0.0 ) return 0.0; - - // --- pump flow = node inflow for IDEAL_PUMP - if ( Pump[k].type == IDEAL_PUMP ) - qIn = Node[n1].inflow + Node[n1].overflow; - - // --- pumping rate depends on pump curve type - else switch(Curve[m].curveType) - { - case PUMP1_CURVE: - vol = Node[n1].newVolume * UCF(VOLUME); - qIn = table_intervalLookup(&Curve[m], vol) / UCF(FLOW); - - // --- check if off of pump curve - if ( vol < Pump[k].xMin || vol > Pump[k].xMax ) - Link[j].flowClass = YES; - break; - - case PUMP2_CURVE: - depth = Node[n1].newDepth * UCF(LENGTH); - qIn = table_intervalLookup(&Curve[m], depth) / UCF(FLOW); - - // --- check if off of pump curve - if ( depth < Pump[k].xMin || depth > Pump[k].xMax ) - Link[j].flowClass = YES; - break; - - case PUMP3_CURVE: - case PUMP5_CURVE: - if (Curve[m].curveType == PUMP5_CURVE) s = Link[j].setting; - head = ((Node[n2].newDepth + Node[n2].invertElev) - - (Node[n1].newDepth + Node[n1].invertElev)) / s / s; - head = MAX(head, 0.0) * UCF(LENGTH); - qIn = table_lookup(&Curve[m], head) / UCF(FLOW); - - // --- compute dQ/dh (slope of pump curve) and - // reverse sign since flow decreases with increasing head - Link[j].dqdh = -table_getSlope(&Curve[m], head) * - UCF(LENGTH) / UCF(FLOW) / s; - - // --- check if off of pump curve - if (head < Pump[k].xMin || head > Pump[k].xMax) - Link[j].flowClass = YES; - break; - - case PUMP4_CURVE: - depth = Node[n1].newDepth; - qIn = table_lookup(&Curve[m], depth*UCF(LENGTH)) / UCF(FLOW); - - // --- compute dQ/dh (slope of pump curve) - qIn1 = table_lookup(&Curve[m], (depth+dh)*UCF(LENGTH)) / UCF(FLOW); - Link[j].dqdh = (qIn1 - qIn) / dh; - - // --- check if off of pump curve - depth *= UCF(LENGTH); - if ( depth < Pump[k].xMin ) Link[j].flowClass = DN_DRY; - if ( depth > Pump[k].xMax ) Link[j].flowClass = UP_DRY; - break; - - default: qIn = 0.0; - } - - // --- do not allow reverse flow through pump - if ( qIn < 0.0 ) qIn = 0.0; - return qIn * Link[j].setting; -} - - -//============================================================================= -// O R I F I C E M E T H O D S -//============================================================================= - -int orifice_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = orifice index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads orifice parameters from a tokenized line of input. -// -{ - int m; - int n1, n2; - double x[5]; - char* id; - - // --- check for valid ID and end node IDs - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- parse orifice parameters - m = findmatch(tok[3], OrificeTypeWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); - x[0] = m; // type - if ( LinkOffsets == ELEV_OFFSET && *tok[4] == '*' ) x[1] = MISSING; - else if ( ! getDouble(tok[4], &x[1]) ) // crest height - return error_setInpError(ERR_NUMBER, tok[4]); - if ( ! getDouble(tok[5], &x[2]) || x[2] < 0.0 ) // cDisch - return error_setInpError(ERR_NUMBER, tok[5]); - x[3] = 0.0; - if ( ntoks >= 7 ) - { - m = findmatch(tok[6], NoYesWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); - x[3] = m; // flap gate - } - x[4] = 0.0; - if ( ntoks >= 8 ) - { - if ( ! getDouble(tok[7], &x[4]) || x[4] < 0.0 ) // orate - return error_setInpError(ERR_NUMBER, tok[7]); - } - - // --- add parameters to orifice object - Link[j].ID = id; - link_setParams(j, ORIFICE, n1, n2, k, x); - return 0; -} - -//============================================================================= - -void orifice_validate(int j, int k) -// -// Input: j = link index -// k = orifice index -// Output: none -// Purpose: validates an orifice's properties -// -{ - int err = 0; - - // --- check for valid xsection - if ( Link[j].xsect.type != RECT_CLOSED - && Link[j].xsect.type != CIRCULAR ) err = ERR_REGULATOR_SHAPE; - if ( err > 0 ) - { - report_writeErrorMsg(err, Link[j].ID); - return; - } - - // --- check for negative offset - if ( Link[j].offset1 < 0.0 ) Link[j].offset1 = 0.0; - - // --- compute partial flow adjustment - orifice_setSetting(j, 0.0); - - // --- compute an equivalent length - Orifice[k].length = 2.0 * RouteStep * sqrt(GRAVITY * Link[j].xsect.yFull); - Orifice[k].length = MAX(200.0, Orifice[k].length); - Orifice[k].surfArea = 0.0; -} - -//============================================================================= - -void orifice_setSetting(int j, double tstep) -// -// Input: j = link index -// tstep = time step over which setting is adjusted (sec) -// Output: none -// Purpose: updates an orifice's setting as a result of a control action. -// -{ - int k = Link[j].subIndex; - double delta, step; - double h, f; - - // --- case where adjustment rate is instantaneous - if ( Orifice[k].orate == 0.0 || tstep == 0.0) - Link[j].setting = Link[j].targetSetting; - - // --- case where orifice setting depends on time step - else - { - delta = Link[j].targetSetting - Link[j].setting; - step = tstep / Orifice[k].orate; - if ( step + 0.001 >= fabs(delta) ) - Link[j].setting = Link[j].targetSetting; - else Link[j].setting += SGN(delta) * step; - } - - // --- find effective orifice discharge coeff. - h = Link[j].setting * Link[j].xsect.yFull; - f = xsect_getAofY(&Link[j].xsect, h) * sqrt(2.0 * GRAVITY); - Orifice[k].cOrif = Orifice[k].cDisch * f; - - // --- find equiv. discharge coeff. for when weir flow occurs - Orifice[k].cWeir = orifice_getWeirCoeff(j, k, h) * f; -} - -//============================================================================= - -double orifice_getWeirCoeff(int j, int k, double h) -// -// Input: j = link index -// k = orifice index -// h = height of orifice opening (ft) -// Output: returns a discharge coefficient (ft^1/2) -// Purpose: computes the discharge coefficient for an orifice -// at the critical depth where weir flow begins. -// -{ - double w, aOverL; - - // --- this is for bottom orifices - if ( Orifice[k].type == BOTTOM_ORIFICE ) - { - // --- find critical height above opening where orifice flow - // turns into weir flow. It equals (Co/Cw)*(Area/Length) - // where Co is the orifice coeff., Cw is the weir coeff/sqrt(2g), - // Area is the area of the opening, and Length = circumference - // of the opening. For a basic sharp crested weir, Cw = 0.414. - if (Link[j].xsect.type == CIRCULAR) aOverL = h / 4.0; - else - { - w = Link[j].xsect.wMax; - aOverL = (h*w) / (2.0*(h+w)); - } - h = Orifice[k].cDisch / 0.414 * aOverL; - Orifice[k].hCrit = h; - } - - // --- this is for side orifices - else - { - // --- critical height is simply height of opening - Orifice[k].hCrit = h; - - // --- head on orifice is distance to center line - h = h / 2.0; - } - - // --- return a coefficient for the critical depth - return Orifice[k].cDisch * sqrt(h); -} - -//============================================================================= - -double orifice_getInflow(int j) -// -// Input: j = link index -// Output: returns orifice flow rate (cfs) -// Purpose: finds the flow through an orifice. -// -{ - int k, n1, n2; - double head, h1, h2, y1, dir; - double f; - double hcrest = 0.0; - double hcrown = 0.0; - double hmidpt; - double q, ratio; - - // --- get indexes of end nodes and link's orifice - n1 = Link[j].node1; - n2 = Link[j].node2; - k = Link[j].subIndex; - - // --- find heads at upstream & downstream nodes - if ( RouteModel == DW ) - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - } - else - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n1].invertElev; - } - dir = (h1 >= h2) ? +1.0 : -1.0; - - // --- exchange h1 and h2 for reverse flow - y1 = Node[n1].newDepth; - if ( dir < 0.0 ) - { - head = h1; - h1 = h2; - h2 = head; - y1 = Node[n2].newDepth; - } - - // --- orifice is a bottom orifice (oriented in horizontal plane) - if ( Orifice[k].type == BOTTOM_ORIFICE ) - { - // --- compute crest elevation - hcrest = Node[n1].invertElev + Link[j].offset1; - - // --- compute head on orifice - if (h1 < hcrest) head = 0.0; - else if (h2 > hcrest) head = h1 - h2; - else head = h1 - hcrest; - - // --- find fraction of critical height for which weir flow occurs - f = head / Orifice[k].hCrit; - f = MIN(f, 1.0); - } - - // --- otherwise orifice is a side orifice (oriented in vertical plane) - else - { - // --- compute elevations of orifice crest and crown - hcrest = Node[n1].invertElev + Link[j].offset1; - hcrown = hcrest + Link[j].xsect.yFull * Link[j].setting; - hmidpt = (hcrest + hcrown) / 2.0; - - // --- compute degree of inlet submergence - if ( h1 < hcrown && hcrown > hcrest ) - f = (h1 - hcrest) / (hcrown - hcrest); - else f = 1.0; - - // --- compute head on orifice - if ( f < 1.0 ) head = h1 - hcrest; - else if ( h2 < hmidpt ) head = h1 - hmidpt; - else head = h1 - h2; - } - - // --- return if head is negligible or flap gate closed - if ( head <= FUDGE || y1 <= FUDGE || - link_setFlapGate(j, n1, n2, dir) ) - { - Link[j].newDepth = 0.0; - Link[j].flowClass = DRY; - Orifice[k].surfArea = FUDGE * Orifice[k].length; - Link[j].dqdh = 0.0; - return 0.0; - } - - // --- determine flow class - Link[j].flowClass = SUBCRITICAL; - if ( hcrest > h2 ) - { - if ( dir == 1.0 ) Link[j].flowClass = DN_CRITICAL; - else Link[j].flowClass = UP_CRITICAL; - } - - // --- compute flow depth and surface area - y1 = Link[j].xsect.yFull * Link[j].setting; - if ( Orifice[k].type == SIDE_ORIFICE ) - { - Link[j].newDepth = y1 * f; - Orifice[k].surfArea = - xsect_getWofY(&Link[j].xsect, Link[j].newDepth) * - Orifice[k].length; - } - else - { - Link[j].newDepth = y1; - Orifice[k].surfArea = xsect_getAofY(&Link[j].xsect, y1); - } - - // --- find flow through the orifice - q = dir * orifice_getFlow(j, k, head, f, Link[j].hasFlapGate); - - // --- apply Villemonte eqn. to correct for submergence - if ( f < 1.0 && h2 > hcrest ) - { - ratio = (h2 - hcrest) / (h1 - hcrest); - q *= pow( (1.0 - pow(ratio, 1.5)), 0.385); - } - return q; -} - -//============================================================================= - -double orifice_getFlow(int j, int k, double head, double f, int hasFlapGate) -// -// Input: j = link index -// k = orifice index -// head = head across orifice -// f = fraction of critical depth filled -// hasFlapGate = flap gate indicator -// Output: returns flow through an orifice -// Purpose: computes flow through an orifice as a function of head. -// -{ - double area, q; - double veloc, hLoss; - - // --- case where orifice is closed - if ( head == 0.0 || f <= 0.0 ) - { - Link[j].dqdh = 0.0; - return 0.0; - } - - // --- case where inlet depth is below critical depth; - // orifice behaves as a weir - else if ( f < 1.0 ) - { - q = Orifice[k].cWeir * pow(f, 1.5); - Link[j].dqdh = 1.5 * q / (f * Orifice[k].hCrit); - } - - // --- case where normal orifice flow applies - else - { - q = Orifice[k].cOrif * sqrt(head); - Link[j].dqdh = q / (2.0 * head); - } - - // --- apply ARMCO adjustment for headloss from flap gate - if ( hasFlapGate ) - { - // --- compute velocity for current orifice flow - area = xsect_getAofY(&Link[j].xsect, - Link[j].setting * Link[j].xsect.yFull); - veloc = q / area; - - // --- compute head loss from gate - hLoss = (4.0 / GRAVITY) * veloc * veloc * - exp(-1.15 * veloc / sqrt(head) ); - - // --- update head (for orifice flow) - // or critical depth fraction (for weir flow) - if ( f < 1.0 ) - { - f = f - hLoss/Orifice[k].hCrit; - if ( f < 0.0 ) f = 0.0; - } - else - { - head = head - hLoss; - if ( head < 0.0 ) head = 0.0; - } - - // --- make recursive call to this function, with hasFlapGate - // set to false, to find flow values at adjusted head value - q = orifice_getFlow(j, k, head, f, FALSE); - } - return q; -} - -//============================================================================= -// W E I R M E T H O D S -//============================================================================= - -int weir_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = weir index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads weir parameters from a tokenized line of input. -// -{ - int m; - int n1, n2; - double x[10]; - char* id; - - // --- check for valid ID and end node IDs - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- parse weir parameters - m = findmatch(tok[3], WeirTypeWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[3]); - x[0] = m; // type - if ( LinkOffsets == ELEV_OFFSET && *tok[4] == '*' ) x[1] = MISSING; - else if ( ! getDouble(tok[4], &x[1]) ) // height - return error_setInpError(ERR_NUMBER, tok[4]); - if ( ! getDouble(tok[5], &x[2]) || x[2] < 0.0 ) // cDisch1 - return error_setInpError(ERR_NUMBER, tok[5]); - x[3] = 0.0; - x[4] = 0.0; - x[5] = 0.0; - x[6] = 1.0; - x[7] = 0.0; - x[8] = 0.0; - x[9] = -1.0; - if ( ntoks >= 7 && *tok[6] != '*' ) - { - m = findmatch(tok[6], NoYesWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[6]); - x[3] = m; // flap gate - } - if ( ntoks >= 8 && *tok[7] != '*' ) - { - if ( ! getDouble(tok[7], &x[4]) || x[4] < 0.0 ) // endCon - return error_setInpError(ERR_NUMBER, tok[7]); - } - if ( ntoks >= 9 && *tok[8] != '*' ) - { - if ( ! getDouble(tok[8], &x[5]) || x[5] < 0.0 ) // cDisch2 - return error_setInpError(ERR_NUMBER, tok[8]); - } - - if ( ntoks >= 10 && *tok[9] != '*' ) - { - m = findmatch(tok[9], NoYesWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[9]); - x[6] = m; // canSurcharge - } - - if ( (m = (int)x[0]) == ROADWAY_WEIR ) - { - if ( ntoks >= 11 ) // road width - { - if ( ! getDouble(tok[10], &x[7]) || x[7] < 0.0 ) - return error_setInpError(ERR_NUMBER, tok[10]); - } - if ( ntoks >= 12 ) // road surface - { - if ( strcomp(tok[11], "PAVED") ) x[8] = 1.0; - else if ( strcomp(tok[11], "GRAVEL") ) x[8] = 2.0; - } - } - - if (ntoks >= 13 && *tok[12] != '*') - { - m = project_findObject(CURVE, tok[12]); // coeff. curve - if (m < 0) return error_setInpError(ERR_NAME, tok[12]); - x[9] = m; - } - - // --- add parameters to weir object - Link[j].ID = id; - link_setParams(j, WEIR, n1, n2, k, x); - return 0; -} - -//============================================================================= - -void weir_validate(int j, int k) -// -// Input: j = link index -// k = weir index -// Output: none -// Purpose: validates a weir's properties -// -{ - int err = 0; - double q, q1, q2, head; - - // --- check for valid cross section - switch ( Weir[k].type) - { - case TRANSVERSE_WEIR: - case SIDEFLOW_WEIR: - case ROADWAY_WEIR: - if ( Link[j].xsect.type != RECT_OPEN ) err = ERR_REGULATOR_SHAPE; - Weir[k].slope = 0.0; - break; - - case VNOTCH_WEIR: - if ( Link[j].xsect.type != TRIANGULAR ) err = ERR_REGULATOR_SHAPE; - else - { - Weir[k].slope = Link[j].xsect.sBot; - } - break; - - case TRAPEZOIDAL_WEIR: - if ( Link[j].xsect.type != TRAPEZOIDAL ) err = ERR_REGULATOR_SHAPE; - else - { - Weir[k].slope = Link[j].xsect.sBot; - } - break; - } - if ( err > 0 ) - { - report_writeErrorMsg(err, Link[j].ID); - return; - } - - // --- check for negative offset - if ( Link[j].offset1 < 0.0 ) Link[j].offset1 = 0.0; - - // --- compute an equivalent length - Weir[k].length = 2.0 * RouteStep * sqrt(GRAVITY * Link[j].xsect.yFull); - Weir[k].length = MAX(200.0, Weir[k].length); - Weir[k].surfArea = 0.0; - - // --- find flow through weir when water level equals weir height - head = Link[j].xsect.yFull; - weir_getFlow(j, k, head, 1.0, FALSE, &q1, &q2); - q = q1 + q2; - - // --- compute equivalent orifice coeff. (for CFS flow units) - head = head / 2.0; // head seen by equivalent orifice - Weir[k].cSurcharge = q / sqrt(head); -} - -//============================================================================= - -void weir_setSetting(int j) -// -// Input: j = link index -// Output: none -// Purpose: updates a weir's setting as a result of a control action. -// -{ - int k = Link[j].subIndex; - double h, q, q1, q2; - - // --- adjust weir setting - Link[j].setting = Link[j].targetSetting; - if ( !Weir[k].canSurcharge ) return; - if ( Weir[k].type == ROADWAY_WEIR ) return; - - // --- find orifice coeff. for surcharged flow - if ( Link[j].setting == 0.0 ) Weir[k].cSurcharge = 0.0; - else - { - // --- find flow through weir when water level equals weir height - h = Link[j].setting * Link[j].xsect.yFull; - weir_getFlow(j, k, h, 1.0, FALSE, &q1, &q2); - q = q1 + q2; - - // --- compute equivalent orifice coeff. (for CFS flow units) - h = h / 2.0; // head seen by equivalent orifice - Weir[k].cSurcharge = q / sqrt(h); - } -} - -//============================================================================= - -double weir_getInflow(int j) -// -// Input: j = link index -// Output: returns weir flow rate (cfs) -// Purpose: finds the flow over a weir. -// -{ - int n1; // index of upstream node - int n2; // index of downstream node - int k; // index of weir - double q1; // flow through central part of weir (cfs) - double q2; // flow through end sections of weir (cfs) - double head; // head on weir (ft) - double h1; // upstrm nodal head (ft) - double h2; // downstrm nodal head (ft) - double hcrest; // head at weir crest (ft) - double hcrown; // head at weir crown (ft) - double y; // water depth in weir (ft) - double dir; // direction multiplier - double ratio; - double weirPower[] = {1.5, // transverse weir - 5./3., // side flow weir - 2.5, // v-notch weir - 1.5}; // trapezoidal weir - - n1 = Link[j].node1; - n2 = Link[j].node2; - k = Link[j].subIndex; - if ( RouteModel == DW ) - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - } - else - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n1].invertElev; - } - dir = (h1 > h2) ? +1.0 : -1.0; - - // --- exchange h1 and h2 for reverse flow - if ( dir < 0.0 ) - { - head = h1; - h1 = h2; - h2 = head; - } - - // --- find head of weir's crest and crown - hcrest = Node[n1].invertElev + Link[j].offset1; - hcrown = hcrest + Link[j].xsect.yFull; - - // --- treat a roadway weir as a special case - if ( Weir[k].type == ROADWAY_WEIR ) - return roadway_getInflow(j, dir, hcrest, h1, h2); - - // --- adjust crest ht. for partially open weir - hcrest += (1.0 - Link[j].setting) * Link[j].xsect.yFull; - - // --- compute head relative to weir crest - head = h1 - hcrest; - - // --- return if head is negligible or flap gate closed - Link[j].dqdh = 0.0; - if ( head <= FUDGE || hcrest >= hcrown || - link_setFlapGate(j, n1, n2, dir) ) - { - Link[j].newDepth = 0.0; - Link[j].flowClass = DRY; - return 0.0; - } - - // --- determine flow class - Link[j].flowClass = SUBCRITICAL; - if ( hcrest > h2 ) - { - if ( dir == 1.0 ) Link[j].flowClass = DN_CRITICAL; - else Link[j].flowClass = UP_CRITICAL; - } - - // --- compute new equivalent surface area - y = Link[j].xsect.yFull - (hcrown - MIN(h1, hcrown)); - Weir[k].surfArea = xsect_getWofY(&Link[j].xsect, y) * Weir[k].length; - - // --- head is above crown - if ( h1 >= hcrown ) - { - // --- use equivalent orifice if weir can surcharge - if ( Weir[k].canSurcharge ) - { - y = (hcrest + hcrown) / 2.0; - if ( h2 < y ) head = h1 - y; - else head = h1 - h2; - y = hcrown - hcrest; - q1 = weir_getOrificeFlow(j, head, y, Weir[k].cSurcharge); - Link[j].newDepth = y; - return dir * q1; - } - - // --- otherwise limit head to height of weir opening - else head = hcrown - hcrest; - } - - // --- use weir eqn. to find flows through central (q1) - // and end sections (q2) of weir - weir_getFlow(j, k, head, dir, Link[j].hasFlapGate, &q1, &q2); - - // --- apply Villemonte eqn. to correct for submergence - if ( h2 > hcrest ) - { - ratio = (h2 - hcrest) / (h1 - hcrest); - q1 *= pow( (1.0 - pow(ratio, weirPower[Weir[k].type])), 0.385); - if ( q2 > 0.0 ) - q2 *= pow( (1.0 - pow(ratio, weirPower[VNOTCH_WEIR])), 0.385); - } - - // --- return total flow through weir - Link[j].newDepth = MIN((h1 - hcrest), Link[j].xsect.yFull); - return dir * (q1 + q2); -} - -//============================================================================= - -void weir_getFlow(int j, int k, double head, double dir, int hasFlapGate, - double* q1, double* q2) -// -// Input: j = link index -// k = weir index -// head = head across weir (ft) -// dir = flow direction indicator -// hasFlapGate = flap gate indicator -// Output: q1 = flow through central portion of weir (cfs) -// q2 = flow through end sections of weir (cfs) -// Purpose: computes flow over weir given head. -// -{ - double length; - double h; - double y; - double hLoss; - double area; - double veloc; - int wType; - int cdCurve = Weir[k].cdCurve; - double cDisch1 = Weir[k].cDisch1; - - // --- q1 = flow through central portion of weir, - // q2 = flow through end sections of trapezoidal weir - *q1 = 0.0; - *q2 = 0.0; - Link[j].dqdh = 0.0; - if ( head <= 0.0 ) return; - - // --- convert weir length & head to original units - length = Link[j].xsect.wMax * UCF(LENGTH); - h = head * UCF(LENGTH); - - // --- lookup tabulated discharge coeff. - if ( cdCurve >= 0 ) cDisch1 = table_lookup(&Curve[cdCurve], h); - - // --- use appropriate formula for weir flow - wType = Weir[k].type; - if ( wType == VNOTCH_WEIR && - Link[j].setting < 1.0 ) wType = TRAPEZOIDAL_WEIR; - switch (wType) - { - case TRANSVERSE_WEIR: - - // --- reduce length when end contractions present - length -= 0.1 * Weir[k].endCon * h; - length = MAX(length, 0.0); - *q1 = cDisch1 * length * pow(h, 1.5); - break; - - case SIDEFLOW_WEIR: - - // --- reduce length when end contractions present - length -= 0.1 * Weir[k].endCon * h; - length = MAX(length, 0.0); - - // --- weir behaves as a transverse weir under reverse flow - if ( dir < 0.0 ) - *q1 = cDisch1 * length * pow(h, 1.5); - else - - // Corrected formula (see Metcalf & Eddy, Inc., - // Wastewater Engineering, McGraw-Hill, 1972 p. 164). - *q1 = cDisch1 * pow(length, 0.83) * pow(h, 1.67); - - break; - - case VNOTCH_WEIR: - *q1 = cDisch1 * Weir[k].slope * pow(h, 2.5); - break; - - case TRAPEZOIDAL_WEIR: - y = (1.0 - Link[j].setting) * Link[j].xsect.yFull; - length = xsect_getWofY(&Link[j].xsect, y) * UCF(LENGTH); - *q1 = cDisch1 * length * pow(h, 1.5); - *q2 = Weir[k].cDisch2 * Weir[k].slope * pow(h, 2.5); - } - - // --- convert CMS flows to CFS - if ( UnitSystem == SI ) - { - *q1 /= M3perFT3; - *q2 /= M3perFT3; - } - - // --- apply ARMCO adjustment for headloss from flap gate - if ( hasFlapGate ) - { - // --- compute flow area & velocity for current weir flow - area = weir_getOpenArea(j, head); - if ( area > TINY ) - { - veloc = (*q1 + *q2) / area; - - // --- compute headloss and subtract from original head - hLoss = (4.0 / GRAVITY) * veloc * veloc * - exp(-1.15 * veloc / sqrt(head) ); - head = head - hLoss; - if ( head < 0.0 ) head = 0.0; - - // --- make recursive call to this function, with hasFlapGate - // set to false, to find flow values at adjusted head value - weir_getFlow(j, k, head, dir, FALSE, q1, q2); - } - } - Link[j].dqdh = weir_getdqdh(k, dir, head, *q1, *q2); -} - -//============================================================================= - -double weir_getOrificeFlow(int j, double head, double y, double cOrif) -// -// Input: j = link index -// head = head across weir (ft) -// y = height of upstream water level above weir crest (ft) -// cOrif = orifice flow coefficient -// Output: returns flow through weir -// Purpose: finds flow through a surcharged weir using the orifice equation. -// -{ - double a, q, v, hloss; - - // --- evaluate the orifice flow equation - q = cOrif * sqrt(head); - - // --- apply Armco adjustment if weir has a flap gate - if ( Link[j].hasFlapGate ) - { - a = weir_getOpenArea(j, y); - if ( a > 0.0 ) - { - v = q / a; - hloss = (4.0 / GRAVITY) * v * v * exp(-1.15 * v / sqrt(y) ); - head -= hloss; - head = MAX(head, 0.0); - q = cOrif * sqrt(head); - } - } - if ( head > 0.0 ) Link[j].dqdh = q / (2.0 * head); - else Link[j].dqdh = 0.0; - return q; -} - -//============================================================================= - -double weir_getOpenArea(int j, double y) -// -// Input: j = link index -// y = depth of water above weir crest (ft) -// Output: returns area between weir crest and y (ft2) -// Purpose: finds flow area through a weir. -// -{ - double z, zy; - - // --- find offset of weir crest due to control setting - z = (1.0 - Link[j].setting) * Link[j].xsect.yFull; - - // --- ht. of crest + ht of water above crest - zy = z + y; - zy = MIN(zy, Link[j].xsect.yFull); - - // --- return difference between area of offset + water depth - // and area of just the offset - return xsect_getAofY(&Link[j].xsect, zy) - - xsect_getAofY(&Link[j].xsect, z); -} - -//============================================================================= - -double weir_getdqdh(int k, double dir, double h, double q1, double q2) -{ - double q1h; - double q2h; - - if ( fabs(h) < FUDGE ) return 0.0; - q1h = fabs(q1/h); - q2h = fabs(q2/h); - - switch (Weir[k].type) - { - case TRANSVERSE_WEIR: return 1.5 * q1h; - - case SIDEFLOW_WEIR: - // --- weir behaves as a transverse weir under reverse flow - if ( dir < 0.0 ) return 1.5 * q1h; - else return 1.67 * q1h; - - case VNOTCH_WEIR: - if ( q2h == 0.0 ) return 2.5 * q1h; // Fully open - else return 1.5 * q1h + 2.5 * q2h; // Partly open - - case TRAPEZOIDAL_WEIR: return 1.5 * q1h + 2.5 * q2h; - } - return 0.0; -} - - -//============================================================================= -// O U T L E T D E V I C E M E T H O D S -//============================================================================= - -int outlet_readParams(int j, int k, char* tok[], int ntoks) -// -// Input: j = link index -// k = outlet index -// tok[] = array of string tokens -// ntoks = number of tokens -// Output: returns an error code -// Purpose: reads outlet parameters from a tokenized line of input. -// -{ - int i, m, n; - int n1, n2; - double x[6]; - char* id; - char* s; - - // --- check for valid ID and end node IDs - if ( ntoks < 6 ) return error_setInpError(ERR_ITEMS, ""); - id = project_findID(LINK, tok[0]); - if ( id == NULL ) return error_setInpError(ERR_NAME, tok[0]); - n1 = project_findObject(NODE, tok[1]); - if ( n1 < 0 ) return error_setInpError(ERR_NAME, tok[1]); - n2 = project_findObject(NODE, tok[2]); - if ( n2 < 0 ) return error_setInpError(ERR_NAME, tok[2]); - - // --- get height above invert - if ( LinkOffsets == ELEV_OFFSET && *tok[3] == '*' ) x[0] = MISSING; - else - { - if ( ! getDouble(tok[3], &x[0]) ) - return error_setInpError(ERR_NUMBER, tok[3]); - if ( LinkOffsets == DEPTH_OFFSET && x[0] < 0.0 ) x[0] = 0.0; - } - - // --- see if outlet flow relation is tabular or functional - m = findmatch(tok[4], RelationWords); - if ( m < 0 ) return error_setInpError(ERR_KEYWORD, tok[4]); - x[1] = 0.0; - x[2] = 0.0; - x[3] = -1.0; - x[4] = 0.0; - - // --- see if rating curve is head or depth based - x[5] = NODE_DEPTH; //default is depth-based - s = strtok(tok[4], "/"); //parse token for - s = strtok(NULL, "/"); // qualifier term - if ( strcomp(s, w_HEAD) ) x[5] = NODE_HEAD; //check if its "HEAD" - - // --- get params. for functional outlet device - if ( m == FUNCTIONAL ) - { - if ( ntoks < 7 ) return error_setInpError(ERR_ITEMS, ""); - if ( ! getDouble(tok[5], &x[1]) ) - return error_setInpError(ERR_NUMBER, tok[5]); - if ( ! getDouble(tok[6], &x[2]) ) - return error_setInpError(ERR_NUMBER, tok[6]); - n = 7; - } - - // --- get name of outlet rating curve - else - { - i = project_findObject(CURVE, tok[5]); - if ( i < 0 ) return error_setInpError(ERR_NAME, tok[5]); - x[3] = i; - n = 6; - } - - // --- check if flap gate specified - if ( ntoks > n) - { - i = findmatch(tok[n], NoYesWords); - if ( i < 0 ) return error_setInpError(ERR_KEYWORD, tok[n]); - x[4] = i; - } - - // --- add parameters to outlet object - Link[j].ID = id; - link_setParams(j, OUTLET, n1, n2, k, x); - return 0; -} - -//============================================================================= - -double outlet_getInflow(int j) -// -// Input: j = link index -// Output: outlet flow rate (cfs) -// Purpose: finds the flow through an outlet. -// -{ - int k, n1, n2; - double head, hcrest, h1, h2, y1, dir; - - // --- get indexes of end nodes - n1 = Link[j].node1; - n2 = Link[j].node2; - k = Link[j].subIndex; - - // --- find heads at upstream & downstream nodes - if ( RouteModel == DW ) - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n2].newDepth + Node[n2].invertElev; - } - else - { - h1 = Node[n1].newDepth + Node[n1].invertElev; - h2 = Node[n1].invertElev; - } - dir = (h1 >= h2) ? +1.0 : -1.0; - - // --- exchange h1 and h2 for reverse flow - y1 = Node[n1].newDepth; - if ( dir < 0.0 ) - { - y1 = h1; - h1 = h2; - h2 = y1; - y1 = Node[n2].newDepth; - } - - // --- for a NODE_DEPTH rating curve the effective head across the - // outlet is the depth above the crest elev. while for a NODE_HEAD - // curve it is the difference between upstream & downstream heads - hcrest = Node[n1].invertElev + Link[j].offset1; - if ( Outlet[k].curveType == NODE_HEAD && RouteModel == DW ) - head = h1 - MAX(h2, hcrest); - else head = h1 - hcrest; - - // --- no flow if either no effective head difference, - // no upstream water available, or closed flap gate - if ( head <= FUDGE || y1 <= FUDGE || - link_setFlapGate(j, n1, n2, dir) ) - { - Link[j].newDepth = 0.0; - Link[j].flowClass = DRY; - return 0.0; - } - - // --- otherwise use rating curve to compute flow - Link[j].newDepth = head; - Link[j].flowClass = SUBCRITICAL; - return dir * Link[j].setting * outlet_getFlow(k, head); -} - -//============================================================================= - -double outlet_getFlow(int k, double head) -// -// Input: k = outlet index -// head = head across outlet (ft) -// Output: returns outlet flow rate (cfs) -// Purpose: computes flow rate through an outlet given head. -// -{ - int m; - double h; - - // --- convert head to original units - h = head * UCF(LENGTH); - - // --- look-up flow in rating curve table if provided - m = Outlet[k].qCurve; - if ( m >= 0 ) return table_lookup(&Curve[m], h) / UCF(FLOW); - - // --- otherwise use function to find flow - else return Outlet[k].qCoeff * pow(h, Outlet[k].qExpon) / UCF(FLOW); -} diff --git a/src/macros.h b/src/macros.h deleted file mode 100644 index c15749e4b..000000000 --- a/src/macros.h +++ /dev/null @@ -1,45 +0,0 @@ -//----------------------------------------------------------------------------- -// macros.h -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 11/01/21 (Build 5.2.0) -// Author: L. Rossman -//----------------------------------------------------------------------------- - -#ifndef MACROS_H -#define MACROS_H - - -//-------------------------------------------------- -// Macro to test for successful allocation of memory -//-------------------------------------------------- -#define MEMCHECK(x) (((x) == NULL) ? 101 : 0 ) - -//-------------------------------------------------- -// Macro to free a non-null pointer -//-------------------------------------------------- -#define FREE(x) { if (x) { free(x); x = NULL; } } - -//--------------------------------------------------- -// Conversion macros to be used in place of functions -//--------------------------------------------------- -#define ABS(x) (((x)<0) ? -(x) : (x)) /* absolute value of x */ -#define MIN(x,y) (((x)<=(y)) ? (x) : (y)) /* minimum of x and y */ -#define MAX(x,y) (((x)>=(y)) ? (x) : (y)) /* maximum of x and y */ -#define MOD(x,y) ((x)%(y)) /* x modulus y */ -#define LOG10(x) ((x) > 0.0 ? log10((x)) : (x)) /* safe log10 of x */ -#define SQR(x) ((x)*(x)) /* x-squared */ -#define SGN(x) (((x)<0) ? (-1) : (1)) /* sign of x */ -#define SIGN(x,y) ((y) >= 0.0 ? fabs(x) : -fabs(x)) -#define UCHAR(x) (((x) >= 'a' && (x) <= 'z') ? ((x)&~32) : (x)) - /* uppercase char of x */ -#define ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0])) /* length of array x */ - -//------------------------------------------------- -// Macro to evaluate function x with error checking -//------------------------------------------------- -#define CALL(x) (ErrorCode = ((ErrorCode>0) ? (ErrorCode) : (x))) - - -#endif //MACROS_H diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 0fe1b40b7..000000000 --- a/src/main.c +++ /dev/null @@ -1,104 +0,0 @@ -//----------------------------------------------------------------------------- -// main.c -// -// Project: EPA SWMM5 -// Version: 5.2 -// Date: 03/24/2021 -// Author: L. Rossman - -// Main stub for the command line version of EPA SWMM 5.2 -// to be run with swmm5.dll. - -#include -#include -#include -#include "swmm5.h" - -int main(int argc, char *argv[]) -// -// Input: argc = number of command line arguments -// argv = array of command line arguments -// Output: returns error status -// Purpose: runs the command line version of EPA SWMM 5.2. -// -// Command line is: runswmm f1 f2 f3 -// where f1 = name of input file, f2 = name of report file, and -// f3 = name of binary output file if saved (or blank if not saved). -// -{ - char *inputFile; - char *reportFile; - char *binaryFile; - char *arg1; - char blank[] = ""; - int version, vMajor, vMinor, vRelease; - char errMsg[128]; - int msgLen = 127; - time_t start; - double runTime; - - version = swmm_getVersion(); - vMajor = version / 10000; - vMinor = (version - 10000 * vMajor) / 1000; - vRelease = (version - 10000 * vMajor - 1000 * vMinor); - start = time(0); - - // --- check for proper number of command line arguments - if (argc == 1) - { - printf("\nNot Enough Arguments (See Help --help)\n\n"); - } - else if (argc == 2) - { - // --- extract first argument - arg1 = argv[1]; - - if (strcmp(arg1, "--help") == 0 || strcmp(arg1, "-h") == 0) - { - // Help - printf("\n\nSTORMWATER MANAGEMENT MODEL (SWMM) HELP\n\n"); - printf("COMMANDS:\n"); - printf("\t--help (-h) SWMM Help\n"); - printf("\t--version (-v) Build Version\n"); - printf("\nRUNNING A SIMULATION:\n"); - printf("\t runswmm